实现和使用 MinMax 与四排 (connect4) 游戏
Posted
技术标签:
【中文标题】实现和使用 MinMax 与四排 (connect4) 游戏【英文标题】:Implementing and using MinMax with four in row (connect4) game 【发布时间】:2016-08-16 00:38:48 【问题描述】:我正在尝试为连续四个(或 connect4 或连接四个)游戏实现 MinMax 算法。
我想我明白了,它应该建立一个可能的板子树直到一定深度,评估它们并返回它们的分数,然后我们只取这些分数的最大值。
因此,aiChooseCol()
通过调用 MinMax()
检查每个可能列的分数,并返回具有最高分数的列。
现在我不确定,这是拨打MinMax()
的正确方式吗?
检查temp = Math.Max(temp, 1000);
是否正确?
我还没有制作启发式函数,但这至少应该识别一个获胜的列并选择它,但目前它只是选择左边的第一个空闲列......我不知道我在做什么错了。
private int AiChooseCol()
int best = -1000;
int col=0;
for (int i = 0; i < m_Board.Cols; i++)
if (m_Board.CheckIfColHasRoom(i))
m_Board.FillSignInBoardAccordingToCol(i, m_Sign);
int t = MinMax(5, m_Board, board.GetOtherPlayerSign(m_Sign));
if (t > best)
best = t;
col = i;
m_Board.RemoveTopCoinFromCol(i);
return col;
private int MinMax(int Depth, board Board, char PlayerSign)
int temp=0;
if (Depth <= 0)
// return from heurisitic function
return temp;
char otherPlayerSign = board.GetOtherPlayerSign(PlayerSign);
char checkBoard = Board.CheckBoardForWin();
if (checkBoard == PlayerSign)
return 1000;
else if (checkBoard == otherPlayerSign)
return -1000;
else if (!Board.CheckIfBoardIsNotFull())
return 0; // tie
if (PlayerSign == m_Sign) // maximizing Player is myself
temp = -1000;
for (int i = 0; i < Board.Cols; i++)
if (Board.FillSignInBoardAccordingToCol(i, PlayerSign)) // so we don't open another branch in a full column
var v = MinMax(Depth - 1, Board, otherPlayerSign);
temp = Math.Max(temp, v);
Board.RemoveTopCoinFromCol(i);
else
temp = 1000;
for (int i = 0; i < Board.Cols; i++)
if (Board.FillSignInBoardAccordingToCol(i, PlayerSign)) // so we don't open another branch in a full column
var v = MinMax(Depth - 1, Board, otherPlayerSign);
temp = Math.Min(temp, v);
Board.RemoveTopCoinFromCol(i);
return temp;
一些注意事项:
FillSignInBoardAccordingToCol()
如果成功则返回一个布尔值。
board
类型有一个 char[,]
数组,其中包含实际的棋盘和玩家的标志。
此代码在 AI Player 类中。
【问题讨论】:
在AiChooseCol
中,您没有将i
列传递给MinMax
,那么它如何知道您要求它评估哪一列?
哦,对了,也许我应该在调用MinMax()
之前在i
列中放置一个硬币? @juharr
是的,我知道它可以重构。它仍然无法修复AiChooseCol
。 @juharr
您应该在else
分支中使用Min
值,而不是Max
。因为这是唯一的区别,所以你应该把 if
-else
放在周围。
@juharr 仍然无法正常工作,我也尝试在 1000
和 -1000
之间切换。
【参考方案1】:
所以我决定编写自己的 MinMax Connect 4。我使用深度来确定输赢的价值,以便让您更接近赢或阻止输的动作优先。如果不止一个具有相同的启发式,我还决定我将随机选择移动。最后我将深度扩展到 6,因为这是从一开始就需要多少步才能找到可能的获胜路径。
private static void Main(string[] args)
var board = new Board(8,7);
var random = new Random();
while (true)
Console.WriteLine("Pick a column 1 -8");
int move;
if (!int.TryParse(Console.ReadLine(), out move) || move < 1 || move > 8)
Console.WriteLine("Must enter a number 1-8.");
continue;
if (!board.DropCoin(1, move-1))
Console.WriteLine("That column is full, pick another one");
continue;
if (board.Winner == 1)
Console.WriteLine(board);
Console.WriteLine("You win!");
break;
if (board.IsFull)
Console.WriteLine(board);
Console.WriteLine("Tie!");
break;
var moves = new List<Tuple<int, int>>();
for (int i = 0; i < board.Columns; i++)
if (!board.DropCoin(2, i))
continue;
moves.Add(Tuple.Create(i, MinMax(6, board, false)));
board.RemoveTopCoin(i);
int maxMoveScore = moves.Max(t => t.Item2);
var bestMoves = moves.Where(t => t.Item2 == maxMoveScore).ToList();
board.DropCoin(2, bestMoves[random.Next(0,bestMoves.Count)].Item1);
Console.WriteLine(board);
if (board.Winner == 2)
Console.WriteLine("You lost!");
break;
if (board.IsFull)
Console.WriteLine("Tie!");
break;
Console.WriteLine("DONE");
Console.ReadKey();
private static int MinMax(int depth, Board board, bool maximizingPlayer)
if (depth <= 0)
return 0;
var winner = board.Winner;
if (winner == 2)
return depth;
if (winner == 1)
return -depth;
if (board.IsFull)
return 0;
int bestValue = maximizingPlayer ? -1 : 1;
for (int i = 0; i < board.Columns; i++)
if (!board.DropCoin(maximizingPlayer ? 2 : 1, i))
continue;
int v = MinMax(depth - 1, board, !maximizingPlayer);
bestValue = maximizingPlayer ? Math.Max(bestValue, v) : Math.Min(bestValue, v);
board.RemoveTopCoin(i);
return bestValue;
public class Board
private readonly int?[,] _board;
private int? _winner;
private bool _changed;
public Board(int cols, int rows)
Columns = cols;
Rows = rows;
_board = new int?[cols, rows];
public int Columns get;
public int Rows get;
public bool ColumnFree(int column)
return !_board[column, 0].HasValue;
public bool DropCoin(int playerId, int column)
int row = 0;
while (row < Rows && !_board[column,row].HasValue)
row++;
if (row == 0)
return false;
_board[column, row - 1] = playerId;
_changed = true;
return true;
public bool RemoveTopCoin(int column)
int row = 0;
while (row < Rows && !_board[column, row].HasValue)
row++;
if (row == Rows)
return false;
_board[column, row] = null;
_changed = true;
return true;
public int? Winner
get
if (!_changed)
return _winner;
_changed = false;
for (int i = 0; i < Columns; i++)
for (int j = 0; j < Rows; j++)
if (!_board[i, j].HasValue)
continue;
bool horizontal = i + 3 < Columns;
bool vertical = j + 3 < Rows;
if (!horizontal && !vertical)
continue;
bool forwardDiagonal = horizontal && vertical;
bool backwardDiagonal = vertical && i - 3 >= 0;
for (int k = 1; k < 4; k++)
horizontal = horizontal && _board[i, j] == _board[i + k, j];
vertical = vertical && _board[i, j] == _board[i, j + k];
forwardDiagonal = forwardDiagonal && _board[i, j] == _board[i + k, j + k];
backwardDiagonal = backwardDiagonal && _board[i, j] == _board[i - k, j + k];
if (!horizontal && !vertical && !forwardDiagonal && !backwardDiagonal)
break;
if (horizontal || vertical || forwardDiagonal || backwardDiagonal)
_winner = _board[i, j];
return _winner;
_winner = null;
return _winner;
public bool IsFull
get
for (int i = 0; i < Columns; i++)
if (!_board[i, 0].HasValue)
return false;
return true;
public override string ToString()
var builder = new StringBuilder();
for (int j = 0; j < Rows; j++)
builder.Append('|');
for (int i = 0; i < Columns; i++)
builder.Append(_board[i, j].HasValue ? _board[i,j].Value.ToString() : " ").Append('|');
builder.AppendLine();
return builder.ToString();
【讨论】:
你的在深度 6 处真的很快,我的在那个深度处真的很慢,即使没有调用启发式函数。深度5前期需要1秒左右,你的快很多,怎么会? 你把isFull
变成了一个属性,非常聪明。 winner
方法不是多次检查许多坐标吗? (即使是同一个方向)
Winner
循环遍历每个位置,将其视为向下、向右、向下对角线向右或向下对角线向左的连接四的起点。然后它可能会查看这些方向上的多达 12 个位置,所以在某种程度上它确实不止一次查看位置,但这是因为一个位置可以是多达 16 种不同可能的连接 4 场景的一部分(在一块板上至少 7x7)。
这对我完成任务帮助很大。谢谢以上是关于实现和使用 MinMax 与四排 (connect4) 游戏的主要内容,如果未能解决你的问题,请参考以下文章