使用 Copilot AI + Blazor 编一个五子棋游戏

news/2025/10/3 20:03:24/文章来源:https://www.cnblogs.com/densen2014/p/19124794

1. 创建 Blazor web工程,选Auto

image

2. 打开 GitHub Copilot 窗口,输入提示词

使用 Blazor 编一个五子棋游戏

image

3.复制代码测试

为了方便调试, 我们先把运行模式由 InteractiveAuto 改为 InteractiveServer

打开 App.razor 编辑 两行 @rendermode="InteractiveAuto" 改为 @rendermode="InteractiveServer"

image

4.不出意外, 代码出现了意外

image

无脑使用AI修复一下

image

结果发现不是这个问题. 是逻辑代码 @onclick="() => PlacePiece(x, y)" 问题. 原因是PlacePiece闭包捕获的 x、y 是当前循环的值

既然人工发现了问题, 我们继续提醒一下AI, 尝试修复

image

最后还是没修复成功, 手动处理一下

@page "/"<PageTitle>五子棋</PageTitle><h1>五子棋游戏</h1><div style="margin-bottom:10px;">当前玩家: <b>@(currentPlayer == 1 ? "黑子" : "白子")</b>@if (winner != 0){<span style="color:red;margin-left:20px;">胜者: @(winner == 1 ? "黑子" : "白子")</span><button class="btn btn-primary" style="margin-left:20px;" @onclick="Restart">重新开始</button>}
</div><table style="border-collapse:collapse;">@for (int y = 0; y < BoardSize; y++){<tr>@for (int x = 0; x < BoardSize; x++){int _x = x;int _y = y;<td style="width:32px;height:32px;border:1px solid #888;text-align:center;vertical-align:middle;cursor:pointer;background:@GetCellBg(x, y);"@onclick="@(() => PlacePiece(_x, _y))">@if (board[x, y] == 1){<span style="font-size:24px;color:black;">●</span>}else if (board[x, y] == 2){<span style="font-size:24px;color:#eee;text-shadow:0 0 2px #888;">●</span>}</td>}</tr>}
</table>@code {const int BoardSize = 15;int[,] board = new int[BoardSize, BoardSize];int currentPlayer = 1; // 1: 黑子, 2: 白子int winner = 0;void PlacePiece(int x, int y){// 修复:增加边界检查,防止越界if (x < 0 || x >= BoardSize || y < 0 || y >= BoardSize)return;if (winner != 0 || board[x, y] != 0)return;board[x, y] = currentPlayer;if (CheckWin(x, y, currentPlayer)){winner = currentPlayer;}else{currentPlayer = 3 - currentPlayer; // 切换玩家}}bool CheckWin(int x, int y, int player){int[][] directions = new int[][]{new int[]{1,0}, // 横new int[]{0,1}, // 竖new int[]{1,1}, // 斜new int[]{1,-1} // 反斜};foreach (var dir in directions){int count = 1;count += CountDirection(x, y, dir[0], dir[1], player);count += CountDirection(x, y, -dir[0], -dir[1], player);if (count >= 5)return true;}return false;}int CountDirection(int x, int y, int dx, int dy, int player){int count = 0;for (int step = 1; step < 5; step++){int nx = x + dx * step;int ny = y + dy * step;if (nx < 0 || nx >= BoardSize || ny < 0 || ny >= BoardSize)break;if (board[nx, ny] == player)count++;elsebreak;}return count;}void Restart(){board = new int[BoardSize, BoardSize];currentPlayer = 1;winner = 0;}string GetCellBg(int x, int y){return (x + y) % 2 == 0 ? "#f9d77e" : "#eac066";}
}

说明:

  1. 添加 int _x = x; int _y = y; 确保闭包捕获的 _x_y 是当前循环的值,避免 Blazor 渲染时闭包变量引用错误。
  2. PlacePiece 方法开头增加边界检查,防止非法坐标导致数组越界异常。

这样可以彻底修复 PlacePiece 拿到的变量不正确和越界的问题。

5. 运行效果

image

6. 加入显示当前玩家的得分功能

你可以为每个玩家增加一个分数变量,每次有玩家获胜时将其分数加一,并在界面上显示。下面是具体实现方法:

  1. @code 区域添加分数字段:
int blackScore = 0;
int whiteScore = 0;
  1. 在玩家获胜时更新分数:
void PlacePiece(int x, int y)
{if (x < 0 || x >= BoardSize || y < 0 || y >= BoardSize)return;if (winner != 0 || board[x, y] != 0)return;board[x, y] = currentPlayer;if (CheckWin(x, y, currentPlayer)){winner = currentPlayer;if (winner == 1)blackScore++;else if (winner == 2)whiteScore++;}else{currentPlayer = 3 - currentPlayer; // 切换玩家}
}
  1. 在页面上显示分数(比如在标题下方):
<div style="margin-bottom:10px;">当前玩家: <b>@(currentPlayer == 1 ? "黑子" : "白子")</b><span style="margin-left:20px;">黑子得分: <b>@blackScore</b></span><span style="margin-left:20px;">白子得分: <b>@whiteScore</b></span>@if (winner != 0){<span style="color:red;margin-left:20px;">胜者: @(winner == 1 ? "黑子" : "白子")</span><button class="btn btn-primary" style="margin-left:20px;" @onclick="Restart">重新开始</button>}
</div>

这样每次有玩家获胜时,分数都会自动更新并显示在页面上。

image

7. 加入重置得分功能

你可以在 Restart 方法中增加对分数的重置,或者单独添加一个“重置得分”按钮和方法。推荐做法如下:

  1. 添加重置得分方法:
void ResetScore()
{blackScore = 0;whiteScore = 0;
}
  1. 在页面上添加按钮:
<button class="btn btn-danger" style="margin-left:20px;" @onclick="ResetScore">重置得分</button>

建议将此按钮放在分数显示附近,例如:

<div style="margin-bottom:10px;">当前玩家: <b>@(currentPlayer == 1 ? "黑子" : "白子")</b><span style="margin-left:20px;">黑子得分: <b>@blackScore</b></span><span style="margin-left:20px;">白子得分: <b>@whiteScore</b></span><button class="btn btn-danger" style="margin-left:20px;" @onclick="ResetScore">重置得分</button>@if (winner != 0){<span style="color:red;margin-left:20px;">胜者: @(winner == 1 ? "黑子" : "白子")</span><button class="btn btn-primary" style="margin-left:20px;" @onclick="Restart">重新开始</button>}
</div>

这样点击“重置得分”按钮即可将分数清零。

8. 在这个五子棋游戏中添加 AI 对手功能

在你的 Blazor 五子棋游戏中添加 AI 对手功能,常见做法是让一方(如白子)由 AI 自动落子。实现思路如下:

  1. 判断当前玩家是否为 AI:如 currentPlayer == 2 时,自动调用 AI 落子逻辑。
  2. 实现 AI 落子算法:可以先用简单的随机落子,后续可扩展为更智能的算法。
  3. 在玩家落子后自动触发 AI 落子:用 StateHasChanged() 刷新界面,必要时用 await Task.Delay() 增加落子延迟。

下面是具体实现方法(以白子为 AI):

@page "/"<PageTitle>五子棋</PageTitle><h1>五子棋游戏</h1><div style="margin-bottom:10px;">当前玩家: <b>@(currentPlayer == 1 ? "黑子" : "白子(电脑)")</b><span style="margin-left:20px;">黑子得分: <b>@blackScore</b></span><span style="margin-left:20px;">白子得分: <b>@whiteScore</b></span><button class="btn btn-danger" style="margin-left:20px;" @onclick="ResetScore">重置得分</button>@if (winner != 0){<span style="color:red;margin-left:20px;">胜者: @(winner == 1 ? "黑子" : "白子(电脑)")</span><button class="btn btn-primary" style="margin-left:20px;" @onclick="Restart">重新开始</button>}
</div><table style="border-collapse:collapse;">@for (int y = 0; y < BoardSize; y++){<tr>@for (int x = 0; x < BoardSize; x++){int _x = x;int _y = y;<td style="width:32px;height:32px;border:1px solid #888;text-align:center;vertical-align:middle;cursor:pointer;background:@GetCellBg(x, y);"@onclick="@(() => PlacePiece(_x, _y))">@if (board[x, y] == 1){<span style="font-size:24px;color:black;">●</span>}else if (board[x, y] == 2){<span style="font-size:24px;color:#eee;text-shadow:0 0 2px #888;">●</span>}</td>}</tr>}
</table>@code {const int BoardSize = 15;int[,] board = new int[BoardSize, BoardSize];int currentPlayer = 1; // 1: 黑子, 2: 白子(电脑)int winner = 0;int blackScore = 0;int whiteScore = 0;Random rand = new();void PlacePiece(int x, int y){if (winner != 0 || board[x, y] != 0 || currentPlayer == 2)return;board[x, y] = currentPlayer;if (CheckWin(x, y, currentPlayer)){winner = currentPlayer;if (winner == 1)blackScore++;else if (winner == 2)whiteScore++;}else{currentPlayer = 2;StateHasChanged();_ = AITurnAsync();}}async Task AITurnAsync(){await Task.Delay(500); // AI 落子延迟if (winner != 0) return;// 简单随机落子var empty = new List<(int x, int y)>();for (int i = 0; i < BoardSize; i++)for (int j = 0; j < BoardSize; j++)if (board[i, j] == 0)empty.Add((i, j));if (empty.Count == 0) return;var (aiX, aiY) = empty[rand.Next(empty.Count)];board[aiX, aiY] = 2;if (CheckWin(aiX, aiY, 2)){winner = 2;whiteScore++;}else{currentPlayer = 1;}StateHasChanged();}// 其余代码不变...bool CheckWin(int x, int y, int player){int[][] directions = new int[][]{new int[]{1,0}, new int[]{0,1}, new int[]{1,1}, new int[]{1,-1}};foreach (var dir in directions){int count = 1;count += CountDirection(x, y, dir[0], dir[1], player);count += CountDirection(x, y, -dir[0], -dir[1], player);if (count >= 5)return true;}return false;}int CountDirection(int x, int y, int dx, int dy, int player){int count = 0;for (int step = 1; step < 5; step++){int nx = x + dx * step;int ny = y + dy * step;if (nx < 0 || nx >= BoardSize || ny < 0 || ny >= BoardSize)break;if (board[nx, ny] == player)count++;elsebreak;}return count;}void Restart(){board = new int[BoardSize, BoardSize];currentPlayer = 1;winner = 0;}void ResetScore(){blackScore = 0;whiteScore = 0;}string GetCellBg(int x, int y){return (x + y) % 2 == 0 ? "#f9d77e" : "#eac066";}
}

要点说明:

  • 玩家(黑子)落子后自动触发 AI(白子)落子。
  • AI 采用随机空位落子,简单易懂,后续可扩展为更智能算法。
  • 若要让 AI 先手,只需将 currentPlayer = 2 并在 OnInitializedRestart 时调用 AITurnAsync()

image

用是能用了, 就是这个AI太傻了,继续优化一下

要优化五子棋 AI 的落子策略,可以采用“优先级评分法”来提升难度。基本思路如下:

  1. 优先考虑能直接获胜的位置(如 AI 连成五子)。
  2. 阻止玩家获胜(如玩家有四连,AI 必须堵住)。
  3. 优先进攻(如自己有三连、二连)。
  4. 其次防守(如玩家有三连、二连)。
  5. 否则随机或选择评分最高的位置

下面是一个简单的评分法实现,AI 会优先选择能赢、能防守、能进攻的位置:

async Task AITurnAsync()
{await Task.Delay(500);if (winner != 0) return;var (aiX, aiY) = FindBestMove();board[aiX, aiY] = 2;if (CheckWin(aiX, aiY, 2)){winner = 2;whiteScore++;}else{currentPlayer = 1;}StateHasChanged();
}// 简单评分法:优先赢棋、阻挡对方、进攻
(int, int) FindBestMove()
{int maxScore = int.MinValue;var bestMoves = new List<(int x, int y)>();for (int x = 0; x < BoardSize; x++){for (int y = 0; y < BoardSize; y++){if (board[x, y] != 0) continue;int score = EvaluatePoint(x, y, 2); // AI进攻score = Math.Max(score, EvaluatePoint(x, y, 1)); // 防守if (score > maxScore){maxScore = score;bestMoves.Clear();bestMoves.Add((x, y));}else if (score == maxScore){bestMoves.Add((x, y));}}}// 多个最佳点时随机选一个return bestMoves[rand.Next(bestMoves.Count)];
}// 评分函数:可根据实际需要调整权重
int EvaluatePoint(int x, int y, int player)
{int score = 0;int opponent = player == 1 ? 2 : 1;// 检查4连、3连、2连等foreach (var dir in new[] { (1, 0), (0, 1), (1, 1), (1, -1) }){int count = 1 + CountDirection(x, y, dir.Item1, dir.Item2, player)+ CountDirection(x, y, -dir.Item1, -dir.Item2, player);if (count >= 5) score += 10000; // 直接获胜else if (count == 4) score += 1000;else if (count == 3) score += 100;else if (count == 2) score += 10;// 防守分数int oppCount = 1 + CountDirection(x, y, dir.Item1, dir.Item2, opponent)+ CountDirection(x, y, -dir.Item1, -dir.Item2, opponent);if (oppCount >= 5) score += 9000; // 必须防守else if (oppCount == 4) score += 900;else if (oppCount == 3) score += 90;else if (oppCount == 2) score += 9;}return score;
}

集成方法:

  1. 替换你的 AITurnAsync 方法为上述实现。
  2. @code 区域添加 FindBestMoveEvaluatePoint 方法。
  3. 可根据实际效果调整评分权重。

效果说明:

  • AI 会优先选择能赢的位置,其次堵住玩家的四连,再考虑进攻和防守三连、二连。
  • 这种评分法简单高效,能显著提升 AI 难度。
  • 若需更高难度,可进一步实现“活三”、“活四”识别、双向评分、甚至 Minimax 算法。

image

9. 添加难度切换按钮

添加三个难度选择按钮以及切换人机按钮

下面是实现“三个难度选择按钮(简单、中等、困难)”和“人机/人人切换按钮”的完整方法。
难度通过调整 AI 的评分权重实现,人机切换通过 isHumanVsAI 控制。请直接替换你的 Home.razor 文件内容:

@page "/"<PageTitle>五子棋</PageTitle><h1>五子棋游戏</h1><div style="margin-bottom:10px;"><b>难度:</b><button class="btn btn-sm @(aiLevel==0?"btn-primary":"btn-outline-primary")" @onclick="() => SetAILevel(0)">简单</button><button class="btn btn-sm @(aiLevel==1?"btn-primary":"btn-outline-primary")" @onclick="() => SetAILevel(1)">中等</button><button class="btn btn-sm @(aiLevel==2?"btn-primary":"btn-outline-primary")" @onclick="() => SetAILevel(2)">困难</button><span style="margin-left:20px;"><b>模式:</b><button class="btn btn-sm @(isHumanVsAI?"btn-success":"btn-outline-success")" @onclick="ToggleMode">@(isHumanVsAI ? "人机对战" : "人人对战")</button></span>
</div><div style="margin-bottom:10px;">当前玩家: <b>@(currentPlayer == 1 ? "黑子" : (isHumanVsAI ? "白子(电脑)" : "白子"))</b><span style="margin-left:20px;">黑子得分: <b>@blackScore</b></span><span style="margin-left:20px;">白子得分: <b>@whiteScore</b></span><button class="btn btn-danger" style="margin-left:20px;" @onclick="ResetScore">重置得分</button>@if (winner != 0){<span style="color:red;margin-left:20px;">胜者: @(winner == 1 ? "黑子" : (isHumanVsAI ? "白子(电脑)" : "白子"))</span><button class="btn btn-primary" style="margin-left:20px;" @onclick="Restart">重新开始</button>}
</div><table style="border-collapse:collapse;">@for (int y = 0; y < BoardSize; y++){<tr>@for (int x = 0; x < BoardSize; x++){int _x = x;int _y = y;<td style="width:32px;height:32px;border:1px solid #888;text-align:center;vertical-align:middle;cursor:pointer;background:@GetCellBg(x, y);"@onclick="@(() => PlacePiece(_x, _y))">@if (board[x, y] == 1){<span style="font-size:24px;color:black;">●</span>}else if (board[x, y] == 2){<span style="font-size:24px;color:#eee;text-shadow:0 0 2px #888;">●</span>}</td>}</tr>}
</table>@code {const int BoardSize = 15;int[,] board = new int[BoardSize, BoardSize];int currentPlayer = 1; // 1: 黑子, 2: 白子(电脑/玩家)int winner = 0;int blackScore = 0;int whiteScore = 0;Random rand = new();bool isHumanVsAI = true;int aiLevel = 1; // 0:简单 1:中等 2:困难void PlacePiece(int x, int y){if (winner != 0 || board[x, y] != 0)return;// 人机模式下,只有当前为玩家时可落子if (isHumanVsAI && currentPlayer == 2)return;board[x, y] = currentPlayer;if (CheckWin(x, y, currentPlayer)){winner = currentPlayer;if (winner == 1)blackScore++;else if (winner == 2)whiteScore++;}else{currentPlayer = 3 - currentPlayer;if (isHumanVsAI && currentPlayer == 2 && winner == 0){StateHasChanged();_ = AITurnAsync();}}}async Task AITurnAsync(){await Task.Delay(500);if (winner != 0) return;var (aiX, aiY) = FindBestMove();board[aiX, aiY] = 2;if (CheckWin(aiX, aiY, 2)){winner = 2;whiteScore++;}else{currentPlayer = 1;}StateHasChanged();}(int, int) FindBestMove(){if (aiLevel == 0){// 简单:随机落子var empty = new List<(int x, int y)>();for (int i = 0; i < BoardSize; i++)for (int j = 0; j < BoardSize; j++)if (board[i, j] == 0)empty.Add((i, j));return empty[rand.Next(empty.Count)];}else{// 中等/困难:评分法int maxScore = int.MinValue;var bestMoves = new List<(int x, int y)>();for (int x = 0; x < BoardSize; x++){for (int y = 0; y < BoardSize; y++){if (board[x, y] != 0) continue;int score = EvaluatePoint(x, y, 2);score = Math.Max(score, EvaluatePoint(x, y, 1));if (score > maxScore){maxScore = score;bestMoves.Clear();bestMoves.Add((x, y));}else if (score == maxScore){bestMoves.Add((x, y));}}}return bestMoves[rand.Next(bestMoves.Count)];}}int EvaluatePoint(int x, int y, int player){int score = 0;int opponent = player == 1 ? 2 : 1;// 不同难度权重int[] winWeight = { 100, 10000, 10000 };int[] fourWeight = { 10, 1000, 3000 };int[] threeWeight = { 5, 100, 500 };int[] twoWeight = { 2, 10, 50 };int[] blockWinWeight = { 90, 9000, 9000 };int[] blockFourWeight = { 9, 900, 2500 };int[] blockThreeWeight = { 4, 90, 400 };int[] blockTwoWeight = { 1, 9, 40 };foreach (var dir in new[] { (1, 0), (0, 1), (1, 1), (1, -1) }){int count = 1 + CountDirection(x, y, dir.Item1, dir.Item2, player)+ CountDirection(x, y, -dir.Item1, -dir.Item2, player);if (count >= 5) score += winWeight[aiLevel];else if (count == 4) score += fourWeight[aiLevel];else if (count == 3) score += threeWeight[aiLevel];else if (count == 2) score += twoWeight[aiLevel];int oppCount = 1 + CountDirection(x, y, dir.Item1, dir.Item2, opponent)+ CountDirection(x, y, -dir.Item1, -dir.Item2, opponent);if (oppCount >= 5) score += blockWinWeight[aiLevel];else if (oppCount == 4) score += blockFourWeight[aiLevel];else if (oppCount == 3) score += blockThreeWeight[aiLevel];else if (oppCount == 2) score += blockTwoWeight[aiLevel];}return score;}bool CheckWin(int x, int y, int player){int[][] directions = new int[][]{new int[]{1,0}, new int[]{0,1}, new int[]{1,1}, new int[]{1,-1}};foreach (var dir in directions){int count = 1;count += CountDirection(x, y, dir[0], dir[1], player);count += CountDirection(x, y, -dir[0], -dir[1], player);if (count >= 5)return true;}return false;}int CountDirection(int x, int y, int dx, int dy, int player){int count = 0;for (int step = 1; step < 5; step++){int nx = x + dx * step;int ny = y + dy * step;if (nx < 0 || nx >= BoardSize || ny < 0 || ny >= BoardSize)break;if (board[nx, ny] == player)count++;elsebreak;}return count;}void Restart(){board = new int[BoardSize, BoardSize];currentPlayer = 1;winner = 0;if (isHumanVsAI && currentPlayer == 2){_ = AITurnAsync();}}void ResetScore(){blackScore = 0;whiteScore = 0;}void SetAILevel(int level){aiLevel = level;Restart();}void ToggleMode(){isHumanVsAI = !isHumanVsAI;Restart();}string GetCellBg(int x, int y){return (x + y) % 2 == 0 ? "#f9d77e" : "#eac066";}
}

说明:

  • “简单”AI为随机落子,“中等/困难”AI为评分法,困难权重更高更难。
  • “人机对战”时白子为AI,“人人对战”时双方均为玩家。
  • 切换难度或模式会自动重开棋局。
  • 你可以根据需要调整权重数组以微调难度。

10. 最后贴上完整代码

@page "/"<PageTitle>五子棋</PageTitle><h1>五子棋游戏</h1><div style="margin-bottom:10px;"><b>难度:</b><button class="btn btn-sm @(aiLevel==0?"btn-primary":"btn-outline-primary")" @onclick="() => SetAILevel(0)">简单</button><button class="btn btn-sm @(aiLevel==1?"btn-primary":"btn-outline-primary")" @onclick="() => SetAILevel(1)">中等</button><button class="btn btn-sm @(aiLevel==2?"btn-primary":"btn-outline-primary")" @onclick="() => SetAILevel(2)">困难</button><span style="margin-left:20px;"><b>模式:</b><button class="btn btn-sm @(isHumanVsAI?"btn-success":"btn-outline-success")" @onclick="ToggleMode">@(isHumanVsAI ? "人机对战" : "人人对战")</button></span>
</div><div style="margin-bottom:10px;">当前玩家: <b>@(currentPlayer == 1 ? "黑子" : (isHumanVsAI ? "白子(电脑)" : "白子"))</b><span style="margin-left:20px;">黑子得分: <b>@blackScore</b></span><span style="margin-left:20px;">白子得分: <b>@whiteScore</b></span><button class="btn btn-danger" style="margin-left:20px;" @onclick="ResetScore">重置得分</button>@if (winner != 0){<span style="color:red;margin-left:20px;">胜者: @(winner == 1 ? "黑子" : (isHumanVsAI ? "白子(电脑)" : "白子"))</span><button class="btn btn-primary" style="margin-left:20px;" @onclick="Restart">重新开始</button>}
</div><table style="border-collapse:collapse;">@for (int y = 0; y < BoardSize; y++){<tr>@for (int x = 0; x < BoardSize; x++){int _x = x;int _y = y;<td style="width:32px;height:32px;border:1px solid #888;text-align:center;vertical-align:middle;cursor:pointer;background:@GetCellBg(x, y);"@onclick="@(() => PlacePiece(_x, _y))">@if (board[x, y] == 1){<span style="font-size:24px;color:black;">●</span>}else if (board[x, y] == 2){<span style="font-size:24px;color:#eee;text-shadow:0 0 2px #888;">●</span>}</td>}</tr>}
</table>@code {const int BoardSize = 15;int[,] board = new int[BoardSize, BoardSize];int currentPlayer = 1; // 1: 黑子, 2: 白子(电脑/玩家)int winner = 0;int blackScore = 0;int whiteScore = 0;Random rand = new();bool isHumanVsAI = true;int aiLevel = 1; // 0:简单 1:中等 2:困难void PlacePiece(int x, int y){if (winner != 0 || board[x, y] != 0)return;// 人机模式下,只有当前为玩家时可落子if (isHumanVsAI && currentPlayer == 2)return;board[x, y] = currentPlayer;if (CheckWin(x, y, currentPlayer)){winner = currentPlayer;if (winner == 1)blackScore++;else if (winner == 2)whiteScore++;}else{currentPlayer = 3 - currentPlayer;if (isHumanVsAI && currentPlayer == 2 && winner == 0){StateHasChanged();_ = AITurnAsync();}}}async Task AITurnAsync(){await Task.Delay(500);if (winner != 0) return;var (aiX, aiY) = FindBestMove();board[aiX, aiY] = 2;if (CheckWin(aiX, aiY, 2)){winner = 2;whiteScore++;}else{currentPlayer = 1;}StateHasChanged();}(int, int) FindBestMove(){if (aiLevel == 0){// 简单:随机落子var empty = new List<(int x, int y)>();for (int i = 0; i < BoardSize; i++)for (int j = 0; j < BoardSize; j++)if (board[i, j] == 0)empty.Add((i, j));return empty[rand.Next(empty.Count)];}else{// 中等/困难:评分法int maxScore = int.MinValue;var bestMoves = new List<(int x, int y)>();for (int x = 0; x < BoardSize; x++){for (int y = 0; y < BoardSize; y++){if (board[x, y] != 0) continue;int score = EvaluatePoint(x, y, 2);score = Math.Max(score, EvaluatePoint(x, y, 1));if (score > maxScore){maxScore = score;bestMoves.Clear();bestMoves.Add((x, y));}else if (score == maxScore){bestMoves.Add((x, y));}}}return bestMoves[rand.Next(bestMoves.Count)];}}int EvaluatePoint(int x, int y, int player){int score = 0;int opponent = player == 1 ? 2 : 1;// 不同难度权重int[] winWeight = { 100, 10000, 10000 };int[] fourWeight = { 10, 1000, 3000 };int[] threeWeight = { 5, 100, 500 };int[] twoWeight = { 2, 10, 50 };int[] blockWinWeight = { 90, 9000, 9000 };int[] blockFourWeight = { 9, 900, 2500 };int[] blockThreeWeight = { 4, 90, 400 };int[] blockTwoWeight = { 1, 9, 40 };foreach (var dir in new[] { (1, 0), (0, 1), (1, 1), (1, -1) }){int count = 1 + CountDirection(x, y, dir.Item1, dir.Item2, player)+ CountDirection(x, y, -dir.Item1, -dir.Item2, player);if (count >= 5) score += winWeight[aiLevel];else if (count == 4) score += fourWeight[aiLevel];else if (count == 3) score += threeWeight[aiLevel];else if (count == 2) score += twoWeight[aiLevel];int oppCount = 1 + CountDirection(x, y, dir.Item1, dir.Item2, opponent)+ CountDirection(x, y, -dir.Item1, -dir.Item2, opponent);if (oppCount >= 5) score += blockWinWeight[aiLevel];else if (oppCount == 4) score += blockFourWeight[aiLevel];else if (oppCount == 3) score += blockThreeWeight[aiLevel];else if (oppCount == 2) score += blockTwoWeight[aiLevel];}return score;}bool CheckWin(int x, int y, int player){int[][] directions = new int[][]{new int[]{1,0}, new int[]{0,1}, new int[]{1,1}, new int[]{1,-1}};foreach (var dir in directions){int count = 1;count += CountDirection(x, y, dir[0], dir[1], player);count += CountDirection(x, y, -dir[0], -dir[1], player);if (count >= 5)return true;}return false;}int CountDirection(int x, int y, int dx, int dy, int player){int count = 0;for (int step = 1; step < 5; step++){int nx = x + dx * step;int ny = y + dy * step;if (nx < 0 || nx >= BoardSize || ny < 0 || ny >= BoardSize)break;if (board[nx, ny] == player)count++;elsebreak;}return count;}void Restart(){board = new int[BoardSize, BoardSize];currentPlayer = 1;winner = 0;if (isHumanVsAI && currentPlayer == 2){_ = AITurnAsync();}}void ResetScore(){blackScore = 0;whiteScore = 0;}void SetAILevel(int level){aiLevel = level;Restart();}void ToggleMode(){isHumanVsAI = !isHumanVsAI;Restart();}string GetCellBg(int x, int y){return (x + y) % 2 == 0 ? "#f9d77e" : "#eac066";}
}

image

把运行模式改回来,Auto模式不占用服务器计算资源

打开 App.razor 编辑 两行 @rendermode="InteractiveServer" 改为 @rendermode="InteractiveAuto"

源码g01Gobang

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/926268.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于VMware虚拟机如何下载-2025.10.3

关于VMware虚拟机如何下载-2025.10.3现在要想下载VMware有以下办法: 一:直接到这个链接:https://www.vmware.com/products/desktop-hypervisor/workstation-and-fusion 点击 DOWNLOAD FUSION OR WORKSTATION后在这个…

RAG核心特性:ETL - 指南

RAG核心特性:ETL - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", &q…

国庆集训做题10.1 - 10.3

国庆集训做题 CSP-S模拟25 t1 : 爱丽丝的数位划分 题意简述 : 将序列A划分为k个不相交连续非空子序列,求最大的总优美度。 优美度指子序列中十进制表示数字不同的个数,一个方案的优美度是所有子序列优美度的和 首先…

免费申请网站空间网站转化率分析工具

Java线程6种状态和工作原理详解&#xff0c;Java创建线程的4种方式 目录 一、Java线程的六种状态 二、Java线程是如何工作的&#xff1f; 三、BLOCKED 和 WAITING 的区别 四、start() 和 run() 源码分析 五、Java创建线程的所有方式和代码详解 1. 继承Thread类 2. 实现…

政务公开网站建设情况从哪里下载wordpress

以前安装这个软件的时候&#xff0c; 是在windows和mac上&#xff0c;都是图形化的安装方式&#xff0c;但是ubuntu不太一样&#xff0c;需要增加源&#xff0c;然后执行命令。安装的系统版本是2004。 参考链接1&#xff0c;主要命令包含下面几个部分&#xff1a; 第一步&…

XCSY暑期集训模拟赛2T3善良

XCSY暑期集训模拟赛2T3善良暴力(50pts) 对于每个询问,遍历\([l,r]\),统计其中k的数量,时间复杂度为\(O(nm)\)。 for(int i=1;i<=n;i++) cin>>a[i]; while(m--){cin>>l>>r>>k;int ans=0;…

玳瑁的嵌入式日记---0928(ARM--UART) - 指南

玳瑁的嵌入式日记---0928(ARM--UART) - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", &q…

网站开发需要那些技能电子商务平台中搜索词拆解时

产生原因:因为项目最近设计到了一个Quartz相关的模块&#xff0c;前端需要传递时间参数到后台, 然后后台设置一个新的定时任务, 所以后台需要一个可以实现Date与cron之间的相互转换(因为Quartz需要的Cron格式的数据)&#xff0c;所以就借助java的SimpleDateFormat的格式化,然后…

解决Visual Studio中无法使用scanf和C++万能头的问题

解决Visual Studio中无法使用scanf和C++万能头的问题Visual Studio中无法使用scanf和C++万能头? 来吧,跟我一起操作! 1.在桌面上创建万能头文件, 并命名为stdc++.h: 记得把文件后缀名给打开哟! 2.用记事本打开std…

太仓专业网站建设我想创个网站

目录 项目背景 项目技术栈 项目介绍 项目亮点 项目启动 1.创建SSM&#xff08;省略&#xff09; 2.配置项目信息 3.将前端页面加入到项目中 4.初始化数据库 5.创建标准分层的目录 6.创建和编写项目中的公共代码以及常用配置 7.创建和编写业务的Entity、Mapper、…

网页站点不安全静海网站开发

虚拟机是软件 对于第一次听说虚拟机&#xff08;Virtual Machine&#xff0c;VM&#xff09;的人来说&#xff0c;可能以为还要再花钱买一台计算机&#xff0c;这恐怕是他们最担心的。所谓虚拟机&#xff0c;就是在你的计算机上再虚拟出另一台计算机来。这台虚拟出来的计算机&…

技术培训学校机构做360手机网站优化

1.1. Socket简介 套接字&#xff08;socket&#xff09;是一种通信机制&#xff0c;凭借这种机制&#xff0c; 客户端<->服务器 模型的通信方式既可以在本地设备上进行&#xff0c;也可以跨网络进行。 Socket英文原意是“孔”或者“插座”的意思&#xff0c;在网络编程…

北京正规制作网站公司网页无法访问qq可以登陆

默认情况下&#xff0c;所有的新邮箱数据都是启用Exchange搜索&#xff0c;仅当多个邮箱迁移到该Exchange Server时&#xff0c;才禁用搜索索引。 获取数据库的Exchange搜索索引 使用Get-MailboxDatabase 来获取数据库的搜索索引 Get-MailboxDatabase | Select-Object Name,…

做爰全过程免费网站的视频教程网站资讯创作

本文将介绍以下内容&#xff1a; • 面向对象基本概念 • 类和结构体简介 • 引用类型和值类型区别 1. 引言 提起class和struct&#xff0c;我们首先的感觉是语法几乎相同&#xff0c;待遇却翻天复地。历史将接力棒由面向过程编程传到面向对象编程&#xff0c;class和stru…

学校网站管理与建设办法怎样建立自己购物网站

主流开发语言 Java 简介&#xff1a;Java 是一种广泛使用的面向对象的编程语言&#xff0c;由Sun Microsystems公司于1995年发布&#xff0c;后由Oracle公司接手。Java具有“一次编写&#xff0c;到处运行”的特性&#xff0c;它的跨平台能力得益于Java虚拟机&#xff08;JVM&a…

东莞网站设计公司排名企业网站建设感想

在 Oracle 数据库中&#xff0c;你可以使用 ALTER TABLE 语句来添加字段&#xff0c;并使用 COMMENT ON COLUMN 语句来添加字段注释。以下是一个示例&#xff1a; 假设你有一个名为 employees 的表&#xff0c;你想要添加一个名为 email 的字段&#xff0c;并为其添加注释。 …

最新企业网站开发和设计软件个人域名可以做公司网站么

简介&#xff1a; 淘宝搜索推荐、视频搜索的背后使用了什么样的检索技术&#xff1f;非结构化数据检索&#xff0c;向量检索&#xff0c;以及多模态检索&#xff0c;它们到底解决了什么问题&#xff1f;今天由阿里巴巴达摩院的科学家从业务问题出发&#xff0c;抽丝剥茧&#x…

前端如何优雅地生成唯一标识?——一份跨环境 UUID 设备函数的封装与实战

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

广州网站建设外包公司网站建设情况自查报告

1、syntax"proto3":表明使用的是proto3格式&#xff0c;如果不指定则为proto22、package test:定义包名为test&#xff0c;生成类时&#xff0c;会产生一个目录为test3、message Person:消息主体内容&#xff0c;里面为各个字段的定义二、生成对应的PHP类定义好Perso…

解码红黑树

红黑树全面解析:从设计逻辑到代码落地 红黑树是自平衡二叉搜索树(Self-Balanced BST) 的经典实现,核心解决了普通 BST 失衡、AVL 树过度平衡的问题。它通过 “颜色约束” 实现 “大致平衡”,兼顾查找性能与插入 /…