2016-04-22 156 views
1

使用MINMAX有四个我想实现连续最小最大算法四个(或connect4或连接四)游戏。实施和行(connect4)游戏

我想我找到了它的想法,它应该建立尽可能板的树达到一定的深度,对其进行评估并返回自己的分数,那么我们只取那些得分最高。

所以,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类。

+0

在'AiChooseCol'你不及格列'i'成'MinMax',所以它是如何知道你问它来评估哪些列? – juharr

+0

哦,也许我应该在调用'MinMax()'之前在'i'列中放置一个硬币? @juharr – shinzou

+0

是的,我知道它可以被重构。它仍然无法修复'AiChooseCol'。 @juharr – shinzou

回答

3

所以我决定写我自己的MINMAX连接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(); 
    } 
} 
+0

你们是非常快的深度6,我的是在该深度很慢,即使没有调用启发式功能。游戏早期的深度5需要1秒左右,但你的速度要快很多,怎么回事? – shinzou

+0

你把'isFull'变成了一个属性,非常聪明。 '赢家'方法不是多次检查多个坐标吗?(即使在相同的方向) – shinzou

+0

'赢家'循环通过每个位置,考虑它作为一个连接四下降,右侧,对角线向右下,或对角线向左下的起点。然后,它可能会在这些方向上查看多达12个位置,所以从某种意义上说,它不止一次查看位置,但这是因为一个位置可以是多达16个不同可能连接4个场景的一部分(在板上至少7x7)。 – juharr