/// <summary> /// Use some heuristics to evaluate how good a board position this is for the current player /// /// Positive scores indicate good outcomes, negative scores are bad /// </summary> /// <returns></returns> public double Evaluate(TakPiece.PieceColor currentPlayer) { double score = 0; TakPiece.PieceColor otherPlayer = currentPlayer == TakPiece.PieceColor.Black ? TakPiece.PieceColor.White : TakPiece.PieceColor.Black; // check for all victory conditions and return extreme values for any of them // (note that flats can result in a perfect draw, so we return 0 in that case if (Board.HasRoad(currentPlayer)) { score = int.MaxValue; } else if (Board.HasRoad(otherPlayer)) { score = int.MinValue; } else if (Board.IsFull() || Black.NumPieces == 0 || White.NumPieces == 0) { int flats = Board.FlatScore(); if ((currentPlayer == TakPiece.PieceColor.White && flats > 0) || (currentPlayer == TakPiece.PieceColor.Black && flats < 0)) { score = int.MaxValue; } else if (flats != 0) { score = int.MinValue; } else { score = 0; } } else { // otherwise use a variety of metrics to evaluate the board state // TODO decide what these metrics are // some ideas: // - flat coverage // - length of longest path through board graph // - number of pieces along same row/column // - number of captives/reserves // - number of spaces threatened // - number of pieces that threaten each space // - number of 2x2 squares of our own pieces // - number of opposing pieces threatened // - number of own pieces threatened int flatScore = Board.FlatScore() * (otherPlayer == TakPiece.PieceColor.White ? -10 : 10); int numSquares = CountSquares(currentPlayer); int maxLine = CountMostOrthogonalInRowOrColumn(currentPlayer); int mostFullRowCol = CountMostDistinctRowsOrColumns(currentPlayer); score = flatScore + numSquares + maxLine + mostFullRowCol; //Console.WriteLine("{0} = {1} {2} {3} {4}", score, deltaCoverage, deltaSquares, deltaOrtho, deltaDistinct); } return(score); }
/// <summary> /// Create a new player with the number of pieces necessary for the size of the board /// </summary> /// <param name="color"></param> /// <param name="boardSize"></param> public PlayerState(TakPiece.PieceColor color, int boardSize) { switch (boardSize) { case 3: NumPieces = 10; NumCapstones = 0; break; case 4: NumPieces = 15; NumCapstones = 0; break; case 5: NumPieces = 21; NumCapstones = 1; break; case 6: NumPieces = 30; NumCapstones = 1; break; case 7: NumPieces = 40; NumCapstones = 2; break; case 8: NumPieces = 50; NumCapstones = 2; break; } }
/// <summary> /// Get the winner, or null if it is a draw /// </summary> public TakPiece.PieceColor?Winner(TakPiece.PieceColor currentPlayer) { TakPiece.PieceColor otherPlayer = currentPlayer == TakPiece.PieceColor.White ? TakPiece.PieceColor.Black : TakPiece.PieceColor.White; // check road victories first (current player always wins if they made a road) if (Board.HasRoad(currentPlayer)) { return(currentPlayer); } else if (Board.HasRoad(otherPlayer)) { return(otherPlayer); } // if no roads, check the flat score int score = Board.FlatScore(); if (score > 0) { return(TakPiece.PieceColor.White); } else if (score < 0) { return(TakPiece.PieceColor.Black); } else { return(null); } }
private TakMove(TakPiece.PieceColor player, int row, int column, MoveType type) { PieceColor = player; Row = row; Column = column; Type = type; }
/// <summary> /// Direct access to each player's pieces by color /// </summary> /// <param name="player"></param> /// <returns></returns> public PlayerState this[TakPiece.PieceColor player] { get { if (player == TakPiece.PieceColor.White) { return(White); } else { return(Black); } } }
public bool HasRoad(TakPiece.PieceColor player) { BoardGraph g = new BoardGraph(this, player); for (int i = 0; i < Size; i++) { for (int j = 0; j < Size; j++) { if (g.IsConnected(0, i, Size - 1, j) || // vertical road g.IsConnected(i, 0, j, Size - 1)) // horizontal road { return(true); } } } // we've checked every possibility, so return false return(false); }
/// <summary> /// Count the number of 2x2 squares of non-wall pieces we own /// </summary> /// <param name="player"></param> /// <returns></returns> private int CountSquares(TakPiece.PieceColor player) { int count = 0; for (int i = 0; i < Board.Size - 1; i++) { for (int j = i; j < Board.Size - 1; j++) { if ((Board[i, j].Owner == player && Board[i, j].Type != TakPiece.PieceType.Wall) && (Board[i + 1, j].Owner == player && Board[i + 1, j].Type != TakPiece.PieceType.Wall) && (Board[i, j + 1].Owner == player && Board[i, j + 1].Type != TakPiece.PieceType.Wall) && (Board[i + 1, j + 1].Owner == player && Board[i + 1, j + 1].Type != TakPiece.PieceType.Wall)) { count++; } } } return(count); }
/// <summary> /// Generate the list of all moves that the stack located at the given coordinate can make /// </summary> /// <param name="gameState"></param> /// <param name="row"></param> /// <param name="column"></param> /// <returns></returns> private static List <TakMove> GenerateStackMoves(TakPiece.PieceColor player, GameState gameState, int row, int column) { TakMove m; List <TakMove> moves = new List <TakMove>(); PieceStack stack = gameState.Board[row, column]; List <List <int> > allDrops = new List <List <int> >(); for (int n = 1; n <= Math.Min(stack.Size, gameState.Board.Size); n++) // for 1 to the carry limit of the board { allDrops.Clear(); MakeDrops(n, allDrops, new List <int>()); foreach (List <int> drops in allDrops) { m = new TakMove(player, row, column, TakMove.MoveDirection.Left, drops); if (m.IsLegal(gameState)) { moves.Add(m); } m = new TakMove(player, row, column, TakMove.MoveDirection.Right, drops); if (m.IsLegal(gameState)) { moves.Add(m); } m = new TakMove(player, row, column, TakMove.MoveDirection.Up, drops); if (m.IsLegal(gameState)) { moves.Add(m); } m = new TakMove(player, row, column, TakMove.MoveDirection.Down, drops); if (m.IsLegal(gameState)) { moves.Add(m); } } } return(moves); }
/// <summary> /// Count the number of distinct rows or columns we have pieces in /// /// i.e. if we have pieces in rows 0, 1, 4 and columns 2,3,4, and 5 we'll return 4 /// </summary> /// <param name="player"></param> /// <returns></returns> private int CountMostDistinctRowsOrColumns(TakPiece.PieceColor player) { int most = 0; int count; for (int i = 0; i < Board.Size; i++) { count = 0; for (int j = 0; j < Board.Size; j++) { if (Board[i, j].Owner == player && Board[i, j].Type != TakPiece.PieceType.Wall) { count++; break; } } if (count > most) { most = count; } count = 0; for (int j = 0; j < Board.Size; j++) { if (Board[j, i].Owner == player && Board[j, i].Type != TakPiece.PieceType.Wall) { count++; break; } } if (count > most) { most = count; } } return(most); }
public BoardGraph(TakBoard board, TakPiece.PieceColor player) { Vertices = new List <Vertex>(); VerticesByCoordinate = new Dictionary <Point, Vertex>(); for (int i = 0; i < board.Size; i++) { for (int j = 0; j < board.Size; j++) { if (board[i, j].Owner == player && !board[i, j].Top.IsWall) { Vertex v = new Vertex() { Row = i, Column = j }; VerticesByCoordinate.Add(new Point(i, j), v); Vertices.Add(v); } } } for (int i = 0; i < Vertices.Count; i++) { Vertex p = Vertices[i]; for (int j = i; j < Vertices.Count; j++) { Vertex q = Vertices[j]; if ((Math.Abs(q.Row - p.Row) == 1 && q.Column == p.Column) || (Math.Abs(q.Column - p.Column) == 1 && q.Row == p.Row)) { q.Adjacencies.Add(p); p.Adjacencies.Add(q); } } } }
/// <summary> /// Enumerate all legal moves possible for this player, given the state of the game /// /// Returned moves are unsorted (and are in fact shuffles to avoid any biases) /// </summary> /// <param name="player"></param> /// <param name="boardState"></param> /// <returns></returns> public static List <TakMove> EnumerateMoves(TakPiece.PieceColor player, GameState gameState) { TakMove m; List <TakMove> moves = new List <TakMove>(); TakPiece.PieceColor otherPlayer = player == TakPiece.PieceColor.Black ? TakPiece.PieceColor.White : TakPiece.PieceColor.Black; // first off, we can drop pieces on any open space on the board // if we're still evaluating moves it must mean we still have at least 1 piece to place for (int i = 0; i < gameState.Board.Size; i++) { for (int j = 0; j < gameState.Board.Size; j++) { if (gameState.Board[i, j].Size == 0) { // from turn 1 on we place our own pieces and can place flats, capstones, or walls if (gameState.TurnNumber > 0) { m = new TakMove(player, i, j, TakPiece.PieceType.Flat); moves.Add(m); m = new TakMove(player, i, j, TakPiece.PieceType.Wall); moves.Add(m); if (gameState[player].NumCapstones > 0) { m = new TakMove(player, i, j, TakPiece.PieceType.Capstone); moves.Add(m); } } else { // on turn 0 we can only place an opposing flat m = new TakMove(otherPlayer, i, j, TakPiece.PieceType.Flat); moves.Add(m); } } } } // generate all possible moves for each stack we own // this is only allowed from turn 1 onward if (gameState.TurnNumber > 0) { for (int i = 0; i < gameState.Board.Size; i++) { for (int j = 0; j < gameState.Board.Size; j++) { if (gameState.Board[i, j].Owner == player) { List <TakMove> stackMoves = GenerateStackMoves(player, gameState, i, j); foreach (TakMove tm in stackMoves) { moves.Add(tm); } } } } } moves.Shuffle(); return(moves); }
/// <summary> /// Create an AI to control the given pieces on the board /// </summary> /// <param name="ownColor"></param> /// <param name="sharedBoard"></param> public PlayerAI(TakPiece.PieceColor ownColor, GameState sharedGame) { MyColor = ownColor; TheirColor = ownColor == TakPiece.PieceColor.White ? TakPiece.PieceColor.Black : TakPiece.PieceColor.White; Game = sharedGame; }
public MoveTree(TakPiece.PieceColor player, GameState state) { MyColor = player; TheirColor = player == TakPiece.PieceColor.Black ? TakPiece.PieceColor.White : TakPiece.PieceColor.Black; Root = new RootNode(state); }
/// <summary> /// Parse a string representing a move /// /// See README.md for details on notation /// /// The notation should not contain any spaces /// </summary> /// <param name="player"></param> /// <param name="notation"></param> public TakMove(TakPiece.PieceColor player, string notation) { notation = notation.Trim(); try { PieceColor = player; if (notation.Contains(">") || notation.Contains("<") || notation.Contains("-") || notation.Contains("+")) { // if we contain any of the move symbols, assume we're a move Type = MoveType.Move; int coordStart = FirstLetterAt(notation); string coord = notation.Substring(coordStart, 2); int row, col; ParseCoord(coord, out row, out col); Row = row; Column = col; char dir = notation[coordStart + 2]; switch (dir) { case '+': Direction = MoveDirection.Up; break; case '-': Direction = MoveDirection.Down; break; case '<': Direction = MoveDirection.Left; break; case '>': Direction = MoveDirection.Right; break; } Drops = new List <int>(); string dropstring = notation.Substring(coordStart + 3); foreach (char ch in dropstring) { Drops.Add(ch - '0'); } } else { if (notation.Length == 2) { Piece = TakPiece.PieceType.Flat; int row, col; ParseCoord(notation, out row, out col); Row = row; Column = col; } else { switch (notation.ToLower()[0]) { case 's': Piece = TakPiece.PieceType.Wall; break; case 'c': Piece = TakPiece.PieceType.Capstone; break; } int row, col; ParseCoord(notation.Substring(1, 2), out row, out col); Row = row; Column = col; } } } catch (Exception e) { throw new Exception("Failed to parse move", e); } }
/// <summary> /// Create a move where we pick up a stack and drop pieces as we move in a direction /// /// The sum of all values in drops must equal the number of pieces picked up. So if /// we pick up a single piece and move it drops should be [1]. /// /// If we pick up 6 pieces, and drop 1, 1, 2, 1 it should be [1, 1, 2, 1] /// </summary> /// <param name="player"></param> /// <param name="row"></param> /// <param name="column"></param> /// <param name="direction"></param> /// <param name="drops"></param> public TakMove(TakPiece.PieceColor player, int row, int column, MoveDirection direction, List <int> drops) : this(player, row, column, MoveType.Move) { Drops = drops; Direction = direction; }
/// <summary> /// Create a placement move where we place a new piece on the location /// </summary> /// <param name="player"></param> /// <param name="row"></param> /// <param name="column"></param> /// <param name="piece"></param> public TakMove(TakPiece.PieceColor player, int row, int column, TakPiece.PieceType piece = TakPiece.PieceType.Flat) : this(player, row, column, MoveType.Place) { Piece = piece; }