Github Copilot 實(shí)戰(zhàn): 使用 Copilot AI + Blazor 編一個(gè)五子棋游戲
開(kāi)源地址
demo
https://game.app1.es 用blazor auto發(fā)布的
1. 創(chuàng)建 Blazor web工程,選Auto

2. 打開(kāi) GitHub Copilot 窗口,輸入提示詞
使用 Blazor 編一個(gè)五子棋游戲

3.復(fù)制代碼測(cè)試
為了方便調(diào)試, 我們先把運(yùn)行模式由 InteractiveAuto 改為 InteractiveServer
打開(kāi) App.razor 編輯 兩行 @rendermode="InteractiveAuto" 改為 @rendermode="InteractiveServer"

4.不出意外, 代碼出現(xiàn)了意外

無(wú)腦使用AI修復(fù)一下

結(jié)果發(fā)現(xiàn)不是這個(gè)問(wèn)題. 是邏輯代碼 @onclick="() => PlacePiece(x, y)" 問(wèn)題. 原因是PlacePiece閉包捕獲的 x、y 是當(dāng)前循環(huán)的值
既然人工發(fā)現(xiàn)了問(wèn)題, 我們繼續(xù)提醒一下AI, 嘗試修復(fù)

最后還是沒(méi)修復(fù)成功, 手動(dòng)處理一下
@page "/"
<PageTitle>五子棋</PageTitle>
<h1>五子棋游戲</h1>
<div style="margin-bottom:10px;">
當(dāng)前玩家: <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">重新開(kāi)始</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)
{
// 修復(fù):增加邊界檢查,防止越界
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++;
else
break;
}
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";
}
}
說(shuō)明:
- 添加 int _x = x; int _y = y; 確保閉包捕獲的
_x、_y是當(dāng)前循環(huán)的值,避免 Blazor 渲染時(shí)閉包變量引用錯(cuò)誤。 - 在
PlacePiece方法開(kāi)頭增加邊界檢查,防止非法坐標(biāo)導(dǎo)致數(shù)組越界異常。
這樣可以徹底修復(fù) PlacePiece 拿到的變量不正確和越界的問(wèn)題。
5. 運(yùn)行效果

6. 加入顯示當(dāng)前玩家的得分功能
你可以為每個(gè)玩家增加一個(gè)分?jǐn)?shù)變量,每次有玩家獲勝時(shí)將其分?jǐn)?shù)加一,并在界面上顯示。下面是具體實(shí)現(xiàn)方法:
- 在
@code區(qū)域添加分?jǐn)?shù)字段:
int blackScore = 0;
int whiteScore = 0;
- 在玩家獲勝時(shí)更新分?jǐn)?shù):
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; // 切換玩家
}
}
- 在頁(yè)面上顯示分?jǐn)?shù)(比如在標(biāo)題下方):
<div style="margin-bottom:10px;">
當(dāng)前玩家: <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">重新開(kāi)始</button>
}
</div>
這樣每次有玩家獲勝時(shí),分?jǐn)?shù)都會(huì)自動(dòng)更新并顯示在頁(yè)面上。

7. 加入重置得分功能
你可以在 Restart 方法中增加對(duì)分?jǐn)?shù)的重置,或者單獨(dú)添加一個(gè)“重置得分”按鈕和方法。推薦做法如下:
- 添加重置得分方法:
void ResetScore()
{
blackScore = 0;
whiteScore = 0;
}
- 在頁(yè)面上添加按鈕:
<button class="btn btn-danger" style="margin-left:20px;" @onclick="ResetScore">重置得分</button>
建議將此按鈕放在分?jǐn)?shù)顯示附近,例如:
<div style="margin-bottom:10px;">
當(dāng)前玩家: <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">重新開(kāi)始</button>
}
</div>
這樣點(diǎn)擊“重置得分”按鈕即可將分?jǐn)?shù)清零。
8. 在這個(gè)五子棋游戲中添加 AI 對(duì)手功能
在你的 Blazor 五子棋游戲中添加 AI 對(duì)手功能,常見(jiàn)做法是讓一方(如白子)由 AI 自動(dòng)落子。實(shí)現(xiàn)思路如下:
- 判斷當(dāng)前玩家是否為 AI:如 currentPlayer == 2 時(shí),自動(dòng)調(diào)用 AI 落子邏輯。
- 實(shí)現(xiàn) AI 落子算法:可以先用簡(jiǎn)單的隨機(jī)落子,后續(xù)可擴(kuò)展為更智能的算法。
- 在玩家落子后自動(dòng)觸發(fā) AI 落子:用
StateHasChanged()刷新界面,必要時(shí)用await Task.Delay()增加落子延遲。
下面是具體實(shí)現(xiàn)方法(以白子為 AI):
@page "/"
<PageTitle>五子棋</PageTitle>
<h1>五子棋游戲</h1>
<div style="margin-bottom:10px;">
當(dāng)前玩家: <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">重新開(kāi)始</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;
// 簡(jiǎn)單隨機(jī)落子
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++;
else
break;
}
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";
}
}
要點(diǎn)說(shuō)明:
- 玩家(黑子)落子后自動(dòng)觸發(fā) AI(白子)落子。
- AI 采用隨機(jī)空位落子,簡(jiǎn)單易懂,后續(xù)可擴(kuò)展為更智能算法。
- 若要讓 AI 先手,只需將
currentPlayer = 2并在OnInitialized或Restart時(shí)調(diào)用AITurnAsync()。

用是能用了, 就是這個(gè)AI太傻了,繼續(xù)優(yōu)化一下
要優(yōu)化五子棋 AI 的落子策略,可以采用“優(yōu)先級(jí)評(píng)分法”來(lái)提升難度。基本思路如下:
- 優(yōu)先考慮能直接獲勝的位置(如 AI 連成五子)。
- 阻止玩家獲勝(如玩家有四連,AI 必須堵住)。
- 優(yōu)先進(jìn)攻(如自己有三連、二連)。
- 其次防守(如玩家有三連、二連)。
- 否則隨機(jī)或選擇評(píng)分最高的位置。
下面是一個(gè)簡(jiǎn)單的評(píng)分法實(shí)現(xiàn),AI 會(huì)優(yōu)先選擇能贏、能防守、能進(jìn)攻的位置:
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();
}
// 簡(jiǎn)單評(píng)分法:優(yōu)先贏棋、阻擋對(duì)方、進(jìn)攻
(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進(jìn)攻
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));
}
}
}
// 多個(gè)最佳點(diǎn)時(shí)隨機(jī)選一個(gè)
return bestMoves[rand.Next(bestMoves.Count)];
}
// 評(píng)分函數(shù):可根據(jù)實(shí)際需要調(diào)整權(quán)重
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;
// 防守分?jǐn)?shù)
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;
}
集成方法:
- 替換你的
AITurnAsync方法為上述實(shí)現(xiàn)。 - 在
@code區(qū)域添加FindBestMove和EvaluatePoint方法。 - 可根據(jù)實(shí)際效果調(diào)整評(píng)分權(quán)重。
效果說(shuō)明:
- AI 會(huì)優(yōu)先選擇能贏的位置,其次堵住玩家的四連,再考慮進(jìn)攻和防守三連、二連。
- 這種評(píng)分法簡(jiǎn)單高效,能顯著提升 AI 難度。
- 若需更高難度,可進(jìn)一步實(shí)現(xiàn)“活三”、“活四”識(shí)別、雙向評(píng)分、甚至 Minimax 算法。

9. 添加難度切換按鈕
添加三個(gè)難度選擇按鈕以及切換人機(jī)按鈕
下面是實(shí)現(xiàn)“三個(gè)難度選擇按鈕(簡(jiǎn)單、中等、困難)”和“人機(jī)/人人切換按鈕”的完整方法。
難度通過(guò)調(diào)整 AI 的評(píng)分權(quán)重實(shí)現(xiàn),人機(jī)切換通過(guò) isHumanVsAI 控制。請(qǐng)直接替換你的 Home.razor 文件內(nèi)容:
@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)">簡(jiǎn)單</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 ? "人機(jī)對(duì)戰(zhàn)" : "人人對(duì)戰(zhàn)")
</button>
</span>
</div>
<div style="margin-bottom:10px;">
當(dāng)前玩家: <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">重新開(kāi)始</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:簡(jiǎn)單 1:中等 2:困難
void PlacePiece(int x, int y)
{
if (winner != 0 || board[x, y] != 0)
return;
// 人機(jī)模式下,只有當(dāng)前為玩家時(shí)可落子
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)
{
// 簡(jiǎn)單:隨機(jī)落子
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
{
// 中等/困難:評(píng)分法
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;
// 不同難度權(quán)重
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++;
else
break;
}
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";
}
}
說(shuō)明:
- “簡(jiǎn)單”AI為隨機(jī)落子,“中等/困難”AI為評(píng)分法,困難權(quán)重更高更難。
- “人機(jī)對(duì)戰(zhàn)”時(shí)白子為AI,“人人對(duì)戰(zhàn)”時(shí)雙方均為玩家。
- 切換難度或模式會(huì)自動(dòng)重開(kāi)棋局。
- 你可以根據(jù)需要調(diào)整權(quán)重?cái)?shù)組以微調(diào)難度。
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)">簡(jiǎn)單</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 ? "人機(jī)對(duì)戰(zhàn)" : "人人對(duì)戰(zhàn)")
</button>
</span>
</div>
<div style="margin-bottom:10px;">
當(dāng)前玩家: <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">重新開(kāi)始</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:簡(jiǎn)單 1:中等 2:困難
void PlacePiece(int x, int y)
{
if (winner != 0 || board[x, y] != 0)
return;
// 人機(jī)模式下,只有當(dāng)前為玩家時(shí)可落子
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)
{
// 簡(jiǎn)單:隨機(jī)落子
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
{
// 中等/困難:評(píng)分法
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;
// 不同難度權(quán)重
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++;
else
break;
}
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";
}
}

把運(yùn)行模式改回來(lái),Auto模式不占用服務(wù)器計(jì)算資源
打開(kāi) App.razor 編輯 兩行 @rendermode="InteractiveServer" 改為 @rendermode="InteractiveAuto"
網(wǎng)友反饋
后續(xù)添加了美化UI, 添加動(dòng)畫, 悔棋
開(kāi)源地址
demo
https://game.app1.es 用blazor auto發(fā)布的
關(guān)聯(lián)項(xiàng)目
FreeSql QQ群:4336577
BA & Blazor QQ群:795206915
Maui Blazor 中文社區(qū) QQ群:645660665
知識(shí)共享許可協(xié)議
本作品采用 知識(shí)共享署名-非商業(yè)性使用-相同方式共享 4.0 國(guó)際許可協(xié)議 進(jìn)行許可。歡迎轉(zhuǎn)載、使用、重新發(fā)布,但務(wù)必保留文章署名AlexChow(包含鏈接: https://github.com/densen2014 ),不得用于商業(yè)目的,基于本文修改后的作品務(wù)必以相同的許可發(fā)布。如有任何疑問(wèn),請(qǐng)與我聯(lián)系 。
轉(zhuǎn)載聲明
本文來(lái)自博客園,作者:周創(chuàng)琳 AlexChow,轉(zhuǎn)載請(qǐng)注明原文鏈接:http://www.rzrgm.cn/densen2014/p/19124794
AlexChow
今日頭條 | 博客園 | 知乎 | Gitee | GitHub


浙公網(wǎng)安備 33010602011771號(hào)