Alpha Beta Pruning Tic Tac Toe不阻塞,是eval问题吗?
AlphaBeta Pruning TicTacToe is not blocking, is it eval problem?
我已经调试了好几天了,我不知道我在这段井字游戏和 AI 代码中做错了什么(好吧,我知道它不是真正的 AI,但是...)我为此选择的是 Alpha-Beta 修剪。它的 7x7 板,因此对于纯 minimax 实现来说太重了。
我的问题是我无法弄清楚为什么 Alpha-Beta 不阻止玩家拖延游戏并等待玩家移动并使用对他有利的适当移动,或者只是简单地平局。
我决定棋盘中央的点数(最终得分)比棋盘边缘的点数多。我相信更多地向中心移动会比边缘的移动有更多的成功机会,这就是为什么我制作了 AddScoreToMove 函数来评估该移动。
为了确保 eval 函数将检查棋盘上的每一个可能的移动,我没有将该函数用作 find first xxx(例如在 row0 和 col0,col1,col2)和 return(因为可能有 4X 或4O)。另外 4X 或 4O 给出的分数比其他分数高得多,应被视为获胜。
此时我的 PCplayer(O's) 是这样玩的
谁能告诉我我做错了什么?这是我的第二个 AI 程序,第一个是在 3x3 板上使用 minimax,效果很好。
C#代码如下
VB.NET 代码:
Public Class Form1
Dim board As Char(,) = {
{" ", " ", " ", " ", " ", " ", " "},
{" ", " ", " ", " ", " ", " ", " "},
{" ", " ", " ", " ", " ", " ", " "},
{" ", " ", " ", " ", " ", " ", " "},
{" ", " ", " ", " ", " ", " ", " "},
{" ", " ", " ", " ", " ", " ", " "},
{" ", " ", " ", " ", " ", " ", " "}}
Class Move
Public row, col As Integer
End Class
Dim BestMoveRow As Integer = 0
Dim BestMoveCol As Integer = 0
Dim BestMoveScore As Integer = 0
Shared player As Char = "X", opponent As Char = "O"
Shared Function AddScoreToMove(thatMove As Move) As Integer
Dim row As Integer = thatMove.row
Dim col As Integer = thatMove.col
'0 score, move is at border
If ((row >= 1 And row <= 5) And col = 0) Then
Return 0
ElseIf ((row >= 1 And row <= 5) And col = 6) Then
Return 0
ElseIf (row = 0 And (col >= 0 And col <= 6)) Then
Return 0
ElseIf (row = 6 And (col >= 0 And col <= 6)) Then
Return 0
End If
'1 score, thatMove is at border +1
If ((row >= 2 And row <= 4) And col = 1) Then
Return 1
ElseIf ((row >= 2 And row <= 4) And col = 5) Then
Return 1
ElseIf (row = 1 And (col >= 1 And col <= 5)) Then
Return 1
ElseIf (row = 5 And (col >= 1 And col <= 5)) Then
Return 1
End If
'2 score, thatMove is at border +2
If (row = 2 And col = 2) Then
Return 2
ElseIf (row = 2 And col = 4) Then
Return 2
ElseIf (row = 2 And (col >= 2 And col <= 4)) Then
Return 2
ElseIf (row = 4 And (col >= 2 And col <= 4)) Then
Return 2
End If
'3 Center thatMove
If (row = 3 And col <= 3) Then
Return 3
End If
Return 0 'error not added lane
End Function
Private Shared Function eval(ByVal b As Char(,)) As Integer
Dim playerScorerow As Integer = 0
Dim playerScorecol As Integer = 0
Dim playerScorecross As Integer = 0
Dim pcScorerow As Integer = 0
Dim pcScorecol As Integer = 0
Dim pcScorecross As Integer = 0
''EVALUATE rows
For row As Integer = 0 To 3
For col As Integer = 0 To 6
'initialize moves to evaluate
Dim move3 As New Move With {
.row = row + 3,
.col = col
}
Dim move2 As New Move With {
.row = row + 2,
.col = col
}
Dim move1 As New Move With {
.row = row + 1,
.col = col
}
Dim move0 As New Move With {
.row = row,
.col = col
}
If Not b(row, col) = " " Then 'ITS NOT EMPTY - PLAYER OR PC MOVED HERE
Dim moveScore As Integer = AddScoreToMove(move0) 'EVALUATE THAT MOVE
If b(row, col) = b(row + 1, col) Then 'THERE IS 2 X or 2 O
Dim move1Score As Integer = AddScoreToMove(move1)
If b(row + 1, col) = b(row + 2, col) Then 'THERE IS 3x or 3O
Dim move2Score As Integer = AddScoreToMove(move2)
If b(row + 2, col) = b(row + 3, col) Then 'THERE IS 4X or 4O
Dim move3Score As Integer = AddScoreToMove(move3)
If b(row, col) = player Then 'PLAYER HAVE 4X HERE
playerScorerow = Math.Max(playerScorerow, 100 + move3Score + move2Score + move1Score + moveScore) 'GET HIGHEST OF ALL EVALUATIONS OF THAT FOR LOOPS
ElseIf b(row, col) = opponent Then 'PC HAVE 4O HERE
pcScorerow = Math.Min(pcScorerow, -100 - move3Score - move2Score - move1Score - moveScore)
End If
End If
If b(row, col) = player Then
playerScorerow = Math.Max(playerScorerow, 5 + move2Score + move1Score + moveScore)
ElseIf b(row, col) = opponent Then
pcScorerow = Math.Min(pcScorerow, -5 - move2Score - move1Score - moveScore)
End If
End If
If b(row, col) = player Then
playerScorerow = Math.Max(playerScorerow, 2 + move1Score + moveScore)
ElseIf b(row, col) = opponent Then
pcScorerow = Math.Min(pcScorerow, -2 - move1Score - moveScore)
End If
End If
If b(row, col) = player Then
playerScorerow = Math.Max(playerScorerow, moveScore)
ElseIf b(row, col) = opponent Then
pcScorerow = Math.Min(pcScorerow, -moveScore)
End If
End If
Next
Next
''col win
For row As Integer = 0 To 6
For col As Integer = 0 To 3
Dim move3 As New Move With {
.row = row + 3,
.col = col
}
Dim move2 As New Move With {
.row = row + 2,
.col = col
}
Dim move1 As New Move With {
.row = row + 1,
.col = col
}
Dim move0 As New Move With {
.row = row,
.col = col
}
If Not b(row, col) = " " Then
Dim moveScore As Integer = AddScoreToMove(move0)
If b(row, col) = b(row, col + 1) Then
Dim moveScore1 As Integer = AddScoreToMove(move1)
If b(row, col + 1) = b(row, col + 2) Then
Dim moveScore2 As Integer = AddScoreToMove(move2)
If b(row, col + 2) = b(row, col + 3) Then
Dim moveScore3 As Integer = AddScoreToMove(move3)
If b(row, col) = player Then
playerScorerow = Math.Max(playerScorerow, 100 + moveScore3 + moveScore2 + moveScore1 + moveScore)
ElseIf b(row, col) = opponent Then
pcScorerow = Math.Min(pcScorerow, -100 - moveScore3 - moveScore2 - moveScore1 - moveScore)
End If
End If
If b(row, col) = player Then
playerScorerow = Math.Max(playerScorerow, 5 + moveScore2 + moveScore1 + moveScore)
ElseIf b(row, col) = opponent Then
pcScorerow = Math.Min(pcScorerow, -5 - moveScore2 - moveScore1 - moveScore)
End If
End If
If b(row, col) = player Then
playerScorerow = Math.Max(playerScorerow, 2 + moveScore1 + moveScore)
ElseIf b(row, col) = opponent Then
pcScorerow = Math.Min(pcScorerow, -2 - moveScore1 - moveScore)
End If
End If
If b(row, col) = player Then
playerScorerow = Math.Max(playerScorerow, moveScore)
ElseIf b(row, col) = opponent Then
pcScorerow = Math.Min(pcScorerow, -moveScore)
End If
End If
Next
Next
'NOT FULLY IMPLEMENTED
'cross win
For row As Integer = 0 To 3
For col As Integer = 0 To 3
If Not b(row, col) = " " Then
If (b(row, col) = b(row + 1, col + 1) AndAlso b(row + 1, col + 1) = b(row + 2, col + 2) AndAlso b(row + 2, col + 2) = b(row + 3, col + 3)) Then
If b(row, col) = player Then
Return +10
ElseIf b(row, col) = opponent Then
Return -10
End If
End If
End If
Next
Next
'NOT FULLY IMPLEMENTED
'cross win
For row As Integer = 0 To 3
For col As Integer = 3 To 6
If Not b(row, col) = " " Then
If (b(row, col) = b(row + 1, col - 1) AndAlso b(row + 1, col - 1) = b(row + 2, col - 2) AndAlso b(row + 2, col - 2) = b(row + 3, col - 3)) Then
If b(row, col) = player Then
Return +10
ElseIf b(row, col) = opponent Then
Return -10
End If
End If
End If
Next
Next
Dim scoreValues() As Integer = {playerScorerow, playerScorecol, playerScorecross, pcScorerow, pcScorecol, pcScorecross}
Dim max = scoreValues.OrderByDescending(Function(z) Math.Abs(z)).FirstOrDefault()
Return max
End Function
Private Shared Function MiniMax(ByVal board As Char(,), ByVal machineMove As Boolean, ByVal depth As Integer) As Integer
Const alpha As Integer = -10_000
Const beta As Integer = 10_000
Return AlphaBetaPruning(board, machineMove, depth, beta, alpha)
End Function
Private Shared Function AlphaBetaPruning(ByVal board As Char(,), ByVal machineMove As Boolean, ByVal depth As Integer, ByVal beta As Integer, ByVal alpha As Integer) As Integer
If depth = 0 Then Return eval(board)
If machineMove Then 'min PC MOVE
For i As Integer = 0 To 6
For j As Integer = 0 To 6
If board(i, j) = " " Then
board(i, j) = opponent
Dim score As Integer = Math.Min(AlphaBetaPruning(board, Not machineMove, depth - 1, beta, alpha), eval(board))
board(i, j) = " "
If score < beta Then
Form1.BestMoveRow = i
Form1.BestMoveCol = j
Form1.BestMoveScore = score
beta = score
End If
If alpha >= beta Then Exit For 'cutoff
End If
Next
Next
Return beta
Else 'max PLAYER MOVE
For i As Integer = 0 To 6
For j As Integer = 0 To 6
If board(i, j) = " " Then
board(i, j) = player
Dim score As Integer = Math.Max(AlphaBetaPruning(board, Not machineMove, depth - 1, beta, alpha), eval(board))
board(i, j) = " "
If score > alpha Then
alpha = score
End If
If alpha >= beta Then Exit For
End If
Next
Next
Return alpha
End If
End Function
End Class
C# 代码
public class Form1
{
private char[,] board = new[] { { " ", " ", " ", " ", " ", " ", " " }, { " ", " ", " ", " ", " ", " ", " " }, { " ", " ", " ", " ", " ", " ", " " }, { " ", " ", " ", " ", " ", " ", " " }, { " ", " ", " ", " ", " ", " ", " " }, { " ", " ", " ", " ", " ", " ", " " }, { " ", " ", " ", " ", " ", " ", " " } };
class Move
{
public int row, col;
}
private int BestMoveRow = 0;
private int BestMoveCol = 0;
private int BestMoveScore = 0;
private static char player = "X";
private static char opponent = "O";
public static int AddScoreToMove(Move thatMove)
{
int row = thatMove.row;
int col = thatMove.col;
// 0 score, move is at border
if (((row >= 1 & row <= 5) & col == 0))
return 0;
else if (((row >= 1 & row <= 5) & col == 6))
return 0;
else if ((row == 0 & (col >= 0 & col <= 6)))
return 0;
else if ((row == 6 & (col >= 0 & col <= 6)))
return 0;
// 1 score, thatMove is at border +1
if (((row >= 2 & row <= 4) & col == 1))
return 1;
else if (((row >= 2 & row <= 4) & col == 5))
return 1;
else if ((row == 1 & (col >= 1 & col <= 5)))
return 1;
else if ((row == 5 & (col >= 1 & col <= 5)))
return 1;
// 2 score, thatMove is at border +2
if ((row == 2 & col == 2))
return 2;
else if ((row == 2 & col == 4))
return 2;
else if ((row == 2 & (col >= 2 & col <= 4)))
return 2;
else if ((row == 4 & (col >= 2 & col <= 4)))
return 2;
// 3 Center thatMove
if ((row == 3 & col <= 3))
return 3;
return 0; // error not added lane
}
private static int eval(char[,] b)
{
int playerScorerow = 0;
int playerScorecol = 0;
int playerScorecross = 0;
int pcScorerow = 0;
int pcScorecol = 0;
int pcScorecross = 0;
// 'EVALUATE rows
for (int row = 0; row <= 3; row++)
{
for (int col = 0; col <= 6; col++)
{
// initialize moves to evaluate
Move move3 = new Move()
{
row = row + 3,
col = col
};
Move move2 = new Move()
{
row = row + 2,
col = col
};
Move move1 = new Move()
{
row = row + 1,
col = col
};
Move move0 = new Move()
{
row = row,
col = col
};
if (!b[row, col] == " ")
{
int moveScore = AddScoreToMove(move0); // EVALUATE THAT MOVE
if (b[row, col] == b[row + 1, col])
{
int move1Score = AddScoreToMove(move1);
if (b[row + 1, col] == b[row + 2, col])
{
int move2Score = AddScoreToMove(move2);
if (b[row + 2, col] == b[row + 3, col])
{
int move3Score = AddScoreToMove(move3);
if (b[row, col] == player)
playerScorerow = Math.Max(playerScorerow, 100 + move3Score + move2Score + move1Score + moveScore); // GET HIGHEST OF ALL EVALUATIONS OF THAT FOR LOOPS
else if (b[row, col] == opponent)
pcScorerow = Math.Min(pcScorerow, -100 - move3Score - move2Score - move1Score - moveScore);
}
if (b[row, col] == player)
playerScorerow = Math.Max(playerScorerow, 5 + move2Score + move1Score + moveScore);
else if (b[row, col] == opponent)
pcScorerow = Math.Min(pcScorerow, -5 - move2Score - move1Score - moveScore);
}
if (b[row, col] == player)
playerScorerow = Math.Max(playerScorerow, 2 + move1Score + moveScore);
else if (b[row, col] == opponent)
pcScorerow = Math.Min(pcScorerow, -2 - move1Score - moveScore);
}
if (b[row, col] == player)
playerScorerow = Math.Max(playerScorerow, moveScore);
else if (b[row, col] == opponent)
pcScorerow = Math.Min(pcScorerow, -moveScore);
}
}
}
// 'col win
for (int row = 0; row <= 6; row++)
{
for (int col = 0; col <= 3; col++)
{
Move move3 = new Move()
{
row = row + 3,
col = col
};
Move move2 = new Move()
{
row = row + 2,
col = col
};
Move move1 = new Move()
{
row = row + 1,
col = col
};
Move move0 = new Move()
{
row = row,
col = col
};
if (!b[row, col] == " ")
{
int moveScore = AddScoreToMove(move0);
if (b[row, col] == b[row, col + 1])
{
int moveScore1 = AddScoreToMove(move1);
if (b[row, col + 1] == b[row, col + 2])
{
int moveScore2 = AddScoreToMove(move2);
if (b[row, col + 2] == b[row, col + 3])
{
int moveScore3 = AddScoreToMove(move3);
if (b[row, col] == player)
playerScorerow = Math.Max(playerScorerow, 100 + moveScore3 + moveScore2 + moveScore1 + moveScore);
else if (b[row, col] == opponent)
pcScorerow = Math.Min(pcScorerow, -100 - moveScore3 - moveScore2 - moveScore1 - moveScore);
}
if (b[row, col] == player)
playerScorerow = Math.Max(playerScorerow, 5 + moveScore2 + moveScore1 + moveScore);
else if (b[row, col] == opponent)
pcScorerow = Math.Min(pcScorerow, -5 - moveScore2 - moveScore1 - moveScore);
}
if (b[row, col] == player)
playerScorerow = Math.Max(playerScorerow, 2 + moveScore1 + moveScore);
else if (b[row, col] == opponent)
pcScorerow = Math.Min(pcScorerow, -2 - moveScore1 - moveScore);
}
if (b[row, col] == player)
playerScorerow = Math.Max(playerScorerow, moveScore);
else if (b[row, col] == opponent)
pcScorerow = Math.Min(pcScorerow, -moveScore);
}
}
}
// NOT FULLY IMPLEMENTED
// cross win
for (int row = 0; row <= 3; row++)
{
for (int col = 0; col <= 3; col++)
{
if (!b[row, col] == " ")
{
if ((b[row, col] == b[row + 1, col + 1] && b[row + 1, col + 1] == b[row + 2, col + 2] && b[row + 2, col + 2] == b[row + 3, col + 3]))
{
if (b[row, col] == player)
return +10;
else if (b[row, col] == opponent)
return -10;
}
}
}
}
// NOT FULLY IMPLEMENTED
// cross win
for (int row = 0; row <= 3; row++)
{
for (int col = 3; col <= 6; col++)
{
if (!b[row, col] == " ")
{
if ((b[row, col] == b[row + 1, col - 1] && b[row + 1, col - 1] == b[row + 2, col - 2] && b[row + 2, col - 2] == b[row + 3, col - 3]))
{
if (b[row, col] == player)
return +10;
else if (b[row, col] == opponent)
return -10;
}
}
}
}
int[] scoreValues = new[] { playerScorerow, playerScorecol, playerScorecross, pcScorerow, pcScorecol, pcScorecross };
var max = scoreValues.OrderByDescending(z => Math.Abs(z)).FirstOrDefault();
return max;
}
private static int MiniMax(char[,] board, bool machineMove, int depth)
{
const int alpha = -10_000;
const int beta = 10_000;
return AlphaBetaPruning(board, machineMove, depth, beta, alpha);
}
private static int AlphaBetaPruning(char[,] board, bool machineMove, int depth, int beta, int alpha)
{
if (depth == 0)
return eval(board);
if (machineMove)
{
for (int i = 0; i <= 6; i++)
{
for (int j = 0; j <= 6; j++)
{
if (board[i, j] == " ")
{
board[i, j] = opponent;
int score = Math.Min(AlphaBetaPruning(board, !machineMove, depth - 1, beta, alpha), eval(board));
board[i, j] = " ";
if (score < beta)
{
Form1.BestMoveRow = i;
Form1.BestMoveCol = j;
Form1.BestMoveScore = score;
beta = score;
}
if (alpha >= beta)
break; // cutoff
}
}
}
return beta;
}
else
{
for (int i = 0; i <= 6; i++)
{
for (int j = 0; j <= 6; j++)
{
if (board[i, j] == " ")
{
board[i, j] = player;
int score = Math.Max(AlphaBetaPruning(board, !machineMove, depth - 1, beta, alpha), eval(board));
board[i, j] = " ";
if (score > alpha)
alpha = score;
if (alpha >= beta)
break;
}
}
}
return alpha;
}
}
}
又调试了一天之后,我有点迷路了,但我想出了绕过它的方法,这可能是解决这个问题的真正方法。
AlphaBeta 只专注于获胜。这实际上与 eval 函数有关,如果我们对 eval 函数采取更多因素来考虑,那么该函数就更好了。
这就是为什么我们
- 基本获胜因素 -> 即评估赢得比赛的动作
- 阻止敌人因素 -> 拖延游戏
- 还有一个分叉因素我还没有实现。
相关信息在这里:
https://en.wikipedia.org/wiki/Tic-tac-toe#Strategy
从
简而言之 - 简单的 AlphaBeta 函数(只)对获胜进行简单评估,不考虑游戏时间。
我们必须编写适当的阻塞和分叉功能。
Private Shared Function evalBlock(ByVal b As Char(,)) As Move
Dim blockingMove As New Move With {
.row = -1,
.col = -1
}
''row block
For row As Integer = 0 To 4
For col As Integer = 0 To 6
If Not b(row, col) = " " Then
If b(row, col) = b(row + 1, col) Then '2 X or 2 O
If b(row, col) = player Then
If b(row + 2, col) = " " Then
blockingMove.row = row + 2
blockingMove.col = col
Return blockingMove
End If
If row > 0 Then
If b(row - 1, col) = " " Then
blockingMove.row = row - 1
blockingMove.col = col
Return blockingMove
End If
End If
End If
End If
End If
Next
Next
''col block
For row As Integer = 0 To 6
For col As Integer = 0 To 4
If Not b(row, col) = " " Then
If b(row, col) = b(row, col + 1) Then '2 X or 2 O
If b(row, col) = player Then
If b(row, col + 2) = " " Then
blockingMove.row = row
blockingMove.col = col + 2
Return blockingMove
End If
If col > 1 Then
If b(row, col - 1) = " " Then
blockingMove.row = row
blockingMove.col = col - 1
Return blockingMove
End If
End If
End If
End If
End If
Next
Next
'\ cross block
For row As Integer = 0 To 4
For col As Integer = 0 To 4
If Not b(row, col) = " " Then
If (b(row, col) = b(row + 1, col + 1)) Then
If b(row, col) = player Then
If b(row + 2, col + 2) = " " Then
blockingMove.row = row + 2
blockingMove.col = col + 2
End If
If (row > 0 And col > 0) Then
If b(row - 1, col - 1) = " " Then
blockingMove.row = row - 1
blockingMove.col = col - 1
End If
End If
End If
End If
End If
Next
Next
'/ cross block
For row As Integer = 0 To 4
For col As Integer = 2 To 6
If Not b(row, col) = " " Then
If (b(row, col) = b(row + 1, col - 1)) Then
If b(row, col) = player Then
If b(row, col) = " " Then
blockingMove.row = row + 2
blockingMove.col = col - 2
End If
End If
End If
End If
Next
Next
blockingMove.row = -1
blockingMove.col = -1
Return blockingMove
End Function
当然回格挡什么都不做。在 AlphaBeta 函数中,我们必须为此分配适当的值。所以我发现我们的 AI 会阻止玩家,如果他做出 2 个获胜动作并且阻止比获胜更可取。在 2 个玩家移动后阻塞比在 3 个玩家移动后阻塞更有意义,因为如果我们使用 4 步获胜的规则进行游戏,可能会迟到。
像这样:
Dim score As Integer = Math.Min(AlphaBetaPruning(board, Not machineMove, depth - 1, beta, alpha), eval(board) + depth)
Dim BlockingMove As Move = evalBlock(board)
If BlockingMove.row <> -1 Then
Dim blockingMoveScore As Integer = 5000
If score < blockingMoveScore Then
Form1.BestMoveRow = BlockingMove.row
Form1.BestMoveCol = BlockingMove.col
Form1.BestMoveScore = score
End If
End If
这就是我在与我的 AI 进行第一场比赛后输掉比赛的原因。
我已经调试了好几天了,我不知道我在这段井字游戏和 AI 代码中做错了什么(好吧,我知道它不是真正的 AI,但是...)我为此选择的是 Alpha-Beta 修剪。它的 7x7 板,因此对于纯 minimax 实现来说太重了。
我的问题是我无法弄清楚为什么 Alpha-Beta 不阻止玩家拖延游戏并等待玩家移动并使用对他有利的适当移动,或者只是简单地平局。
我决定棋盘中央的点数(最终得分)比棋盘边缘的点数多。我相信更多地向中心移动会比边缘的移动有更多的成功机会,这就是为什么我制作了 AddScoreToMove 函数来评估该移动。
为了确保 eval 函数将检查棋盘上的每一个可能的移动,我没有将该函数用作 find first xxx(例如在 row0 和 col0,col1,col2)和 return(因为可能有 4X 或4O)。另外 4X 或 4O 给出的分数比其他分数高得多,应被视为获胜。
此时我的 PCplayer(O's) 是这样玩的
谁能告诉我我做错了什么?这是我的第二个 AI 程序,第一个是在 3x3 板上使用 minimax,效果很好。
C#代码如下
VB.NET 代码:
Public Class Form1
Dim board As Char(,) = {
{" ", " ", " ", " ", " ", " ", " "},
{" ", " ", " ", " ", " ", " ", " "},
{" ", " ", " ", " ", " ", " ", " "},
{" ", " ", " ", " ", " ", " ", " "},
{" ", " ", " ", " ", " ", " ", " "},
{" ", " ", " ", " ", " ", " ", " "},
{" ", " ", " ", " ", " ", " ", " "}}
Class Move
Public row, col As Integer
End Class
Dim BestMoveRow As Integer = 0
Dim BestMoveCol As Integer = 0
Dim BestMoveScore As Integer = 0
Shared player As Char = "X", opponent As Char = "O"
Shared Function AddScoreToMove(thatMove As Move) As Integer
Dim row As Integer = thatMove.row
Dim col As Integer = thatMove.col
'0 score, move is at border
If ((row >= 1 And row <= 5) And col = 0) Then
Return 0
ElseIf ((row >= 1 And row <= 5) And col = 6) Then
Return 0
ElseIf (row = 0 And (col >= 0 And col <= 6)) Then
Return 0
ElseIf (row = 6 And (col >= 0 And col <= 6)) Then
Return 0
End If
'1 score, thatMove is at border +1
If ((row >= 2 And row <= 4) And col = 1) Then
Return 1
ElseIf ((row >= 2 And row <= 4) And col = 5) Then
Return 1
ElseIf (row = 1 And (col >= 1 And col <= 5)) Then
Return 1
ElseIf (row = 5 And (col >= 1 And col <= 5)) Then
Return 1
End If
'2 score, thatMove is at border +2
If (row = 2 And col = 2) Then
Return 2
ElseIf (row = 2 And col = 4) Then
Return 2
ElseIf (row = 2 And (col >= 2 And col <= 4)) Then
Return 2
ElseIf (row = 4 And (col >= 2 And col <= 4)) Then
Return 2
End If
'3 Center thatMove
If (row = 3 And col <= 3) Then
Return 3
End If
Return 0 'error not added lane
End Function
Private Shared Function eval(ByVal b As Char(,)) As Integer
Dim playerScorerow As Integer = 0
Dim playerScorecol As Integer = 0
Dim playerScorecross As Integer = 0
Dim pcScorerow As Integer = 0
Dim pcScorecol As Integer = 0
Dim pcScorecross As Integer = 0
''EVALUATE rows
For row As Integer = 0 To 3
For col As Integer = 0 To 6
'initialize moves to evaluate
Dim move3 As New Move With {
.row = row + 3,
.col = col
}
Dim move2 As New Move With {
.row = row + 2,
.col = col
}
Dim move1 As New Move With {
.row = row + 1,
.col = col
}
Dim move0 As New Move With {
.row = row,
.col = col
}
If Not b(row, col) = " " Then 'ITS NOT EMPTY - PLAYER OR PC MOVED HERE
Dim moveScore As Integer = AddScoreToMove(move0) 'EVALUATE THAT MOVE
If b(row, col) = b(row + 1, col) Then 'THERE IS 2 X or 2 O
Dim move1Score As Integer = AddScoreToMove(move1)
If b(row + 1, col) = b(row + 2, col) Then 'THERE IS 3x or 3O
Dim move2Score As Integer = AddScoreToMove(move2)
If b(row + 2, col) = b(row + 3, col) Then 'THERE IS 4X or 4O
Dim move3Score As Integer = AddScoreToMove(move3)
If b(row, col) = player Then 'PLAYER HAVE 4X HERE
playerScorerow = Math.Max(playerScorerow, 100 + move3Score + move2Score + move1Score + moveScore) 'GET HIGHEST OF ALL EVALUATIONS OF THAT FOR LOOPS
ElseIf b(row, col) = opponent Then 'PC HAVE 4O HERE
pcScorerow = Math.Min(pcScorerow, -100 - move3Score - move2Score - move1Score - moveScore)
End If
End If
If b(row, col) = player Then
playerScorerow = Math.Max(playerScorerow, 5 + move2Score + move1Score + moveScore)
ElseIf b(row, col) = opponent Then
pcScorerow = Math.Min(pcScorerow, -5 - move2Score - move1Score - moveScore)
End If
End If
If b(row, col) = player Then
playerScorerow = Math.Max(playerScorerow, 2 + move1Score + moveScore)
ElseIf b(row, col) = opponent Then
pcScorerow = Math.Min(pcScorerow, -2 - move1Score - moveScore)
End If
End If
If b(row, col) = player Then
playerScorerow = Math.Max(playerScorerow, moveScore)
ElseIf b(row, col) = opponent Then
pcScorerow = Math.Min(pcScorerow, -moveScore)
End If
End If
Next
Next
''col win
For row As Integer = 0 To 6
For col As Integer = 0 To 3
Dim move3 As New Move With {
.row = row + 3,
.col = col
}
Dim move2 As New Move With {
.row = row + 2,
.col = col
}
Dim move1 As New Move With {
.row = row + 1,
.col = col
}
Dim move0 As New Move With {
.row = row,
.col = col
}
If Not b(row, col) = " " Then
Dim moveScore As Integer = AddScoreToMove(move0)
If b(row, col) = b(row, col + 1) Then
Dim moveScore1 As Integer = AddScoreToMove(move1)
If b(row, col + 1) = b(row, col + 2) Then
Dim moveScore2 As Integer = AddScoreToMove(move2)
If b(row, col + 2) = b(row, col + 3) Then
Dim moveScore3 As Integer = AddScoreToMove(move3)
If b(row, col) = player Then
playerScorerow = Math.Max(playerScorerow, 100 + moveScore3 + moveScore2 + moveScore1 + moveScore)
ElseIf b(row, col) = opponent Then
pcScorerow = Math.Min(pcScorerow, -100 - moveScore3 - moveScore2 - moveScore1 - moveScore)
End If
End If
If b(row, col) = player Then
playerScorerow = Math.Max(playerScorerow, 5 + moveScore2 + moveScore1 + moveScore)
ElseIf b(row, col) = opponent Then
pcScorerow = Math.Min(pcScorerow, -5 - moveScore2 - moveScore1 - moveScore)
End If
End If
If b(row, col) = player Then
playerScorerow = Math.Max(playerScorerow, 2 + moveScore1 + moveScore)
ElseIf b(row, col) = opponent Then
pcScorerow = Math.Min(pcScorerow, -2 - moveScore1 - moveScore)
End If
End If
If b(row, col) = player Then
playerScorerow = Math.Max(playerScorerow, moveScore)
ElseIf b(row, col) = opponent Then
pcScorerow = Math.Min(pcScorerow, -moveScore)
End If
End If
Next
Next
'NOT FULLY IMPLEMENTED
'cross win
For row As Integer = 0 To 3
For col As Integer = 0 To 3
If Not b(row, col) = " " Then
If (b(row, col) = b(row + 1, col + 1) AndAlso b(row + 1, col + 1) = b(row + 2, col + 2) AndAlso b(row + 2, col + 2) = b(row + 3, col + 3)) Then
If b(row, col) = player Then
Return +10
ElseIf b(row, col) = opponent Then
Return -10
End If
End If
End If
Next
Next
'NOT FULLY IMPLEMENTED
'cross win
For row As Integer = 0 To 3
For col As Integer = 3 To 6
If Not b(row, col) = " " Then
If (b(row, col) = b(row + 1, col - 1) AndAlso b(row + 1, col - 1) = b(row + 2, col - 2) AndAlso b(row + 2, col - 2) = b(row + 3, col - 3)) Then
If b(row, col) = player Then
Return +10
ElseIf b(row, col) = opponent Then
Return -10
End If
End If
End If
Next
Next
Dim scoreValues() As Integer = {playerScorerow, playerScorecol, playerScorecross, pcScorerow, pcScorecol, pcScorecross}
Dim max = scoreValues.OrderByDescending(Function(z) Math.Abs(z)).FirstOrDefault()
Return max
End Function
Private Shared Function MiniMax(ByVal board As Char(,), ByVal machineMove As Boolean, ByVal depth As Integer) As Integer
Const alpha As Integer = -10_000
Const beta As Integer = 10_000
Return AlphaBetaPruning(board, machineMove, depth, beta, alpha)
End Function
Private Shared Function AlphaBetaPruning(ByVal board As Char(,), ByVal machineMove As Boolean, ByVal depth As Integer, ByVal beta As Integer, ByVal alpha As Integer) As Integer
If depth = 0 Then Return eval(board)
If machineMove Then 'min PC MOVE
For i As Integer = 0 To 6
For j As Integer = 0 To 6
If board(i, j) = " " Then
board(i, j) = opponent
Dim score As Integer = Math.Min(AlphaBetaPruning(board, Not machineMove, depth - 1, beta, alpha), eval(board))
board(i, j) = " "
If score < beta Then
Form1.BestMoveRow = i
Form1.BestMoveCol = j
Form1.BestMoveScore = score
beta = score
End If
If alpha >= beta Then Exit For 'cutoff
End If
Next
Next
Return beta
Else 'max PLAYER MOVE
For i As Integer = 0 To 6
For j As Integer = 0 To 6
If board(i, j) = " " Then
board(i, j) = player
Dim score As Integer = Math.Max(AlphaBetaPruning(board, Not machineMove, depth - 1, beta, alpha), eval(board))
board(i, j) = " "
If score > alpha Then
alpha = score
End If
If alpha >= beta Then Exit For
End If
Next
Next
Return alpha
End If
End Function
End Class
C# 代码
public class Form1
{
private char[,] board = new[] { { " ", " ", " ", " ", " ", " ", " " }, { " ", " ", " ", " ", " ", " ", " " }, { " ", " ", " ", " ", " ", " ", " " }, { " ", " ", " ", " ", " ", " ", " " }, { " ", " ", " ", " ", " ", " ", " " }, { " ", " ", " ", " ", " ", " ", " " }, { " ", " ", " ", " ", " ", " ", " " } };
class Move
{
public int row, col;
}
private int BestMoveRow = 0;
private int BestMoveCol = 0;
private int BestMoveScore = 0;
private static char player = "X";
private static char opponent = "O";
public static int AddScoreToMove(Move thatMove)
{
int row = thatMove.row;
int col = thatMove.col;
// 0 score, move is at border
if (((row >= 1 & row <= 5) & col == 0))
return 0;
else if (((row >= 1 & row <= 5) & col == 6))
return 0;
else if ((row == 0 & (col >= 0 & col <= 6)))
return 0;
else if ((row == 6 & (col >= 0 & col <= 6)))
return 0;
// 1 score, thatMove is at border +1
if (((row >= 2 & row <= 4) & col == 1))
return 1;
else if (((row >= 2 & row <= 4) & col == 5))
return 1;
else if ((row == 1 & (col >= 1 & col <= 5)))
return 1;
else if ((row == 5 & (col >= 1 & col <= 5)))
return 1;
// 2 score, thatMove is at border +2
if ((row == 2 & col == 2))
return 2;
else if ((row == 2 & col == 4))
return 2;
else if ((row == 2 & (col >= 2 & col <= 4)))
return 2;
else if ((row == 4 & (col >= 2 & col <= 4)))
return 2;
// 3 Center thatMove
if ((row == 3 & col <= 3))
return 3;
return 0; // error not added lane
}
private static int eval(char[,] b)
{
int playerScorerow = 0;
int playerScorecol = 0;
int playerScorecross = 0;
int pcScorerow = 0;
int pcScorecol = 0;
int pcScorecross = 0;
// 'EVALUATE rows
for (int row = 0; row <= 3; row++)
{
for (int col = 0; col <= 6; col++)
{
// initialize moves to evaluate
Move move3 = new Move()
{
row = row + 3,
col = col
};
Move move2 = new Move()
{
row = row + 2,
col = col
};
Move move1 = new Move()
{
row = row + 1,
col = col
};
Move move0 = new Move()
{
row = row,
col = col
};
if (!b[row, col] == " ")
{
int moveScore = AddScoreToMove(move0); // EVALUATE THAT MOVE
if (b[row, col] == b[row + 1, col])
{
int move1Score = AddScoreToMove(move1);
if (b[row + 1, col] == b[row + 2, col])
{
int move2Score = AddScoreToMove(move2);
if (b[row + 2, col] == b[row + 3, col])
{
int move3Score = AddScoreToMove(move3);
if (b[row, col] == player)
playerScorerow = Math.Max(playerScorerow, 100 + move3Score + move2Score + move1Score + moveScore); // GET HIGHEST OF ALL EVALUATIONS OF THAT FOR LOOPS
else if (b[row, col] == opponent)
pcScorerow = Math.Min(pcScorerow, -100 - move3Score - move2Score - move1Score - moveScore);
}
if (b[row, col] == player)
playerScorerow = Math.Max(playerScorerow, 5 + move2Score + move1Score + moveScore);
else if (b[row, col] == opponent)
pcScorerow = Math.Min(pcScorerow, -5 - move2Score - move1Score - moveScore);
}
if (b[row, col] == player)
playerScorerow = Math.Max(playerScorerow, 2 + move1Score + moveScore);
else if (b[row, col] == opponent)
pcScorerow = Math.Min(pcScorerow, -2 - move1Score - moveScore);
}
if (b[row, col] == player)
playerScorerow = Math.Max(playerScorerow, moveScore);
else if (b[row, col] == opponent)
pcScorerow = Math.Min(pcScorerow, -moveScore);
}
}
}
// 'col win
for (int row = 0; row <= 6; row++)
{
for (int col = 0; col <= 3; col++)
{
Move move3 = new Move()
{
row = row + 3,
col = col
};
Move move2 = new Move()
{
row = row + 2,
col = col
};
Move move1 = new Move()
{
row = row + 1,
col = col
};
Move move0 = new Move()
{
row = row,
col = col
};
if (!b[row, col] == " ")
{
int moveScore = AddScoreToMove(move0);
if (b[row, col] == b[row, col + 1])
{
int moveScore1 = AddScoreToMove(move1);
if (b[row, col + 1] == b[row, col + 2])
{
int moveScore2 = AddScoreToMove(move2);
if (b[row, col + 2] == b[row, col + 3])
{
int moveScore3 = AddScoreToMove(move3);
if (b[row, col] == player)
playerScorerow = Math.Max(playerScorerow, 100 + moveScore3 + moveScore2 + moveScore1 + moveScore);
else if (b[row, col] == opponent)
pcScorerow = Math.Min(pcScorerow, -100 - moveScore3 - moveScore2 - moveScore1 - moveScore);
}
if (b[row, col] == player)
playerScorerow = Math.Max(playerScorerow, 5 + moveScore2 + moveScore1 + moveScore);
else if (b[row, col] == opponent)
pcScorerow = Math.Min(pcScorerow, -5 - moveScore2 - moveScore1 - moveScore);
}
if (b[row, col] == player)
playerScorerow = Math.Max(playerScorerow, 2 + moveScore1 + moveScore);
else if (b[row, col] == opponent)
pcScorerow = Math.Min(pcScorerow, -2 - moveScore1 - moveScore);
}
if (b[row, col] == player)
playerScorerow = Math.Max(playerScorerow, moveScore);
else if (b[row, col] == opponent)
pcScorerow = Math.Min(pcScorerow, -moveScore);
}
}
}
// NOT FULLY IMPLEMENTED
// cross win
for (int row = 0; row <= 3; row++)
{
for (int col = 0; col <= 3; col++)
{
if (!b[row, col] == " ")
{
if ((b[row, col] == b[row + 1, col + 1] && b[row + 1, col + 1] == b[row + 2, col + 2] && b[row + 2, col + 2] == b[row + 3, col + 3]))
{
if (b[row, col] == player)
return +10;
else if (b[row, col] == opponent)
return -10;
}
}
}
}
// NOT FULLY IMPLEMENTED
// cross win
for (int row = 0; row <= 3; row++)
{
for (int col = 3; col <= 6; col++)
{
if (!b[row, col] == " ")
{
if ((b[row, col] == b[row + 1, col - 1] && b[row + 1, col - 1] == b[row + 2, col - 2] && b[row + 2, col - 2] == b[row + 3, col - 3]))
{
if (b[row, col] == player)
return +10;
else if (b[row, col] == opponent)
return -10;
}
}
}
}
int[] scoreValues = new[] { playerScorerow, playerScorecol, playerScorecross, pcScorerow, pcScorecol, pcScorecross };
var max = scoreValues.OrderByDescending(z => Math.Abs(z)).FirstOrDefault();
return max;
}
private static int MiniMax(char[,] board, bool machineMove, int depth)
{
const int alpha = -10_000;
const int beta = 10_000;
return AlphaBetaPruning(board, machineMove, depth, beta, alpha);
}
private static int AlphaBetaPruning(char[,] board, bool machineMove, int depth, int beta, int alpha)
{
if (depth == 0)
return eval(board);
if (machineMove)
{
for (int i = 0; i <= 6; i++)
{
for (int j = 0; j <= 6; j++)
{
if (board[i, j] == " ")
{
board[i, j] = opponent;
int score = Math.Min(AlphaBetaPruning(board, !machineMove, depth - 1, beta, alpha), eval(board));
board[i, j] = " ";
if (score < beta)
{
Form1.BestMoveRow = i;
Form1.BestMoveCol = j;
Form1.BestMoveScore = score;
beta = score;
}
if (alpha >= beta)
break; // cutoff
}
}
}
return beta;
}
else
{
for (int i = 0; i <= 6; i++)
{
for (int j = 0; j <= 6; j++)
{
if (board[i, j] == " ")
{
board[i, j] = player;
int score = Math.Max(AlphaBetaPruning(board, !machineMove, depth - 1, beta, alpha), eval(board));
board[i, j] = " ";
if (score > alpha)
alpha = score;
if (alpha >= beta)
break;
}
}
}
return alpha;
}
}
}
又调试了一天之后,我有点迷路了,但我想出了绕过它的方法,这可能是解决这个问题的真正方法。 AlphaBeta 只专注于获胜。这实际上与 eval 函数有关,如果我们对 eval 函数采取更多因素来考虑,那么该函数就更好了。 这就是为什么我们
- 基本获胜因素 -> 即评估赢得比赛的动作
- 阻止敌人因素 -> 拖延游戏
- 还有一个分叉因素我还没有实现。 相关信息在这里: https://en.wikipedia.org/wiki/Tic-tac-toe#Strategy 从
简而言之 - 简单的 AlphaBeta 函数(只)对获胜进行简单评估,不考虑游戏时间。 我们必须编写适当的阻塞和分叉功能。
Private Shared Function evalBlock(ByVal b As Char(,)) As Move
Dim blockingMove As New Move With {
.row = -1,
.col = -1
}
''row block
For row As Integer = 0 To 4
For col As Integer = 0 To 6
If Not b(row, col) = " " Then
If b(row, col) = b(row + 1, col) Then '2 X or 2 O
If b(row, col) = player Then
If b(row + 2, col) = " " Then
blockingMove.row = row + 2
blockingMove.col = col
Return blockingMove
End If
If row > 0 Then
If b(row - 1, col) = " " Then
blockingMove.row = row - 1
blockingMove.col = col
Return blockingMove
End If
End If
End If
End If
End If
Next
Next
''col block
For row As Integer = 0 To 6
For col As Integer = 0 To 4
If Not b(row, col) = " " Then
If b(row, col) = b(row, col + 1) Then '2 X or 2 O
If b(row, col) = player Then
If b(row, col + 2) = " " Then
blockingMove.row = row
blockingMove.col = col + 2
Return blockingMove
End If
If col > 1 Then
If b(row, col - 1) = " " Then
blockingMove.row = row
blockingMove.col = col - 1
Return blockingMove
End If
End If
End If
End If
End If
Next
Next
'\ cross block
For row As Integer = 0 To 4
For col As Integer = 0 To 4
If Not b(row, col) = " " Then
If (b(row, col) = b(row + 1, col + 1)) Then
If b(row, col) = player Then
If b(row + 2, col + 2) = " " Then
blockingMove.row = row + 2
blockingMove.col = col + 2
End If
If (row > 0 And col > 0) Then
If b(row - 1, col - 1) = " " Then
blockingMove.row = row - 1
blockingMove.col = col - 1
End If
End If
End If
End If
End If
Next
Next
'/ cross block
For row As Integer = 0 To 4
For col As Integer = 2 To 6
If Not b(row, col) = " " Then
If (b(row, col) = b(row + 1, col - 1)) Then
If b(row, col) = player Then
If b(row, col) = " " Then
blockingMove.row = row + 2
blockingMove.col = col - 2
End If
End If
End If
End If
Next
Next
blockingMove.row = -1
blockingMove.col = -1
Return blockingMove
End Function
当然回格挡什么都不做。在 AlphaBeta 函数中,我们必须为此分配适当的值。所以我发现我们的 AI 会阻止玩家,如果他做出 2 个获胜动作并且阻止比获胜更可取。在 2 个玩家移动后阻塞比在 3 个玩家移动后阻塞更有意义,因为如果我们使用 4 步获胜的规则进行游戏,可能会迟到。
像这样:
Dim score As Integer = Math.Min(AlphaBetaPruning(board, Not machineMove, depth - 1, beta, alpha), eval(board) + depth)
Dim BlockingMove As Move = evalBlock(board)
If BlockingMove.row <> -1 Then
Dim blockingMoveScore As Integer = 5000
If score < blockingMoveScore Then
Form1.BestMoveRow = BlockingMove.row
Form1.BestMoveCol = BlockingMove.col
Form1.BestMoveScore = score
End If
End If
这就是我在与我的 AI 进行第一场比赛后输掉比赛的原因。