2016-04-22 22 views
1

4行(またはconnect4または4つ)のゲームでMinMaxアルゴリズムを実装しようとしています。4行(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()

タイプはchar[,]の配列を持ち、実際のボードとプレーヤーの兆候があります。

このコードはAI Playerクラスにあります。

+0

'AiChooseCol'では' iMix'を 'MinMax'に渡していないので、どの列に評価を求めているのかはどうやって分かりますか? – juharr

+0

ああ、おそらく、私は 'MinMax()'を呼び出す前に既に 'i'列にコインを置いておくべきでしょうか? @juharr – shinzou

+0

ええ、私はそれがリファクタリングできることを知っています。 'AiChooseCol'の修正ではまだ動作しません。 @juharr – shinzou

答えて

3

私は自分自身のMinMax Connect 4を書くことにしました。私は、あなたの勝利に近づく、または損失をブロックする動きが優先されるように、賞金額または損失額を決定するために奥行きを使用しました。複数の人が同じヒューリスティックを持っている場合は、ランダムに選択します。最後に、私は6から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'をプロパティに変えました。とても賢いです。 'winner'メソッドが多くの座標を何度もチェックしていませんか?(同じ方向であっても) – shinzou

+0

「勝者」は、4つの下り坂、右下、右斜め下り、左斜め下りの4つの接続の開始点とみなして、各位置をループします。そうすれば、それらの方向で最大12の位置を見ることができるので、位置を2回以上見ていますが、それは1つの位置が最大16の異なる4つの接続可能なシナリオの一部になる可能性があるからです少なくとも7x7)。 – juharr

関連する問題