public int SwitchPosition() { while (!WinChecker.HasWon(TokenType.Red) && !WinChecker.HasWon(TokenType.Black) && !Board.IsFull()) { Console.Clear(); Renderer.DrawHeader(_currentColumn, _currentType); Renderer.DrawBoard(); var key = Console.ReadKey(); if (key.Key != ConsoleKey.Enter) { _currentColumn = key switch { { KeyChar : var k } when k >= '1' && k <= '7' => k - '0' - 1, { Key : ConsoleKey.LeftArrow } when _currentColumn > 0 => _currentColumn - 1, { Key : ConsoleKey.RightArrow } when _currentColumn < 6 => _currentColumn + 1, var _ => _currentColumn, }; } else { if (Board.CanAdd(_currentColumn)) { Board.Add(_currentColumn, _currentType); _currentType = _currentType == TokenType.Red ? TokenType.Black : TokenType.Red; } } } return(_currentColumn); }
//places the piece public bool PlacePiece(Point location, Colors piece) { if (isGameOver) { return(false); } int column = location.X / squareSize; if (column == columns) { column--; } int row = ProbeY(column); if (row == -1) { return(false); } squares[column, row] = piece; isGameOver = WinChecker.CheckForWin(squares, boardSize, winLength, ref wonXPos, ref wonYPos); return(true); }
/// <summary> /// Gets the best move for the AI /// </summary> /// <param name="field">The field to look in</param> /// <param name="fieldSize">The size of the field</param> /// <param name="winLength">The length needed to win</param> /// <param name="currentPiece">The piece we are currently looking for</param> /// <param name="opponentPiece">The piece of the opponent, when looking from the current piece</param> /// <param name="depth">How deep we want to look</param> /// <param name="isMax">Whether the player we are looking for is the AI or not (true for AI, false for not AI)</param> /// <returns>The x-position of the best move</returns> protected static int GetBestMove(Pieces[,] field, Size fieldSize, int winLength, Pieces currentPiece, Pieces opponentPiece, int depth, bool isMax) { // If we are at max depth, we are going to score the field if (depth <= 0) { // If the current move is a winning move, give it the best value (relative to the current player) // We also add 100 to the min value, so there is a (small) difference between a losing move for the AI, and "you can't put a piece here" when probing for the y-position if (WinChecker.CheckForWin(field, fieldSize, winLength)) { return(isMax ? (int.MaxValue - searchDepth) : (int.MinValue + searchDepth + 100)); } else { int value = GetFieldValue(field, fieldSize, winLength, currentPiece, opponentPiece); return(value); } } int[] moves = new int[fieldSize.Width]; Pieces[,] testField; // Look through each x-position for (int x = 0; x < fieldSize.Width; x++) { // Find the y-position to put the piece at int y = ProbeY(field, fieldSize, x); // If that position is out of bounds, set it as least desirable for the current player if (y <= 1 || y >= fieldSize.Height) { moves[x] = isMax ? int.MinValue : int.MaxValue; continue; } // Copy the field testField = CopyField(field, fieldSize); // Place a piece at the location found testField[x, y] = currentPiece; // Check if that move was a win, if it is, we can set it as desirable // In order to give a difference in how many moves it takes to get to that win, we lower (or increase for the Min-player) the value by how many layers we are deep if (WinChecker.CheckForWin(testField, fieldSize, winLength)) { moves[x] = isMax ? (int.MaxValue - searchDepth + depth) : (int.MinValue + searchDepth - depth + 100); continue; } // If it was not a winning move, look deeper moves[x] = GetBestMove(testField, fieldSize, winLength, opponentPiece, currentPiece, depth - 1, !isMax); } // Then, after looking at all the moves, the max player returns the maximum value, and the min player the minimum value return(isMax ? moves.Max() : moves.Min()); }
public static void Turn(Player player) { Game.ClearScreen(); WriteLine("-------------------------------------------------------------------"); WriteLine("Player Turn: " + player.GetPlayerNum()); WriteLine("-------------------------------------------------------------------"); Game.PrintBoard(); PlayerPickColumn(player); WinChecker.CheckForWin(player); }
private static void Main() { new Board(); var winChecker = new WinChecker(); const TokenType currentType = TokenType.Red; var position = new Position(); var currentColumn = position.SwitchPosition(); Console.Clear(); Renderer.DrawHeader(currentColumn, currentType); Renderer.DrawBoard(); winChecker.PrintWinConditions(); Console.ReadKey(); }
/// <summary> /// Gets the best move for the AI /// </summary> /// <param name="field">The field to place a piece on</param> /// <param name="fieldSize">The size of the field</param> /// <param name="winLength">The length needed to win a game</param> /// <param name="currentPiece">The current piece to look for (the AI piece)</param> /// <param name="opponentPiece">The piece the opponent uses</param> /// <returns>The x-position of the best move</returns> protected static int GetBestMove(Pieces[,] field, Size fieldSize, int winLength, Pieces currentPiece, Pieces opponentPiece) { int[] moves = new int[fieldSize.Width]; Pieces[,] testField; // Go through each x-position for (int x = 0; x < fieldSize.Width; x++) { // Find the y-position the piece will fall to int y = ProbeY(field, fieldSize, x); // If the piece is out-of-bounds, we can't place a piece here if (y <= -1 || y >= fieldSize.Height) { moves[x] = int.MinValue; continue; } // Makes a copy of the field testField = CopyField(field, fieldSize); // Places the current piece at the place testField[x, y] = currentPiece; // If that placement is a winning move, that means it is a move we really want to make, and that we won't have to look further after this move if (WinChecker.CheckForWin(testField, fieldSize, winLength)) { moves[x] = int.MaxValue; continue; } // If it is not a winning move, look at all the possible moves after this and see how well they hold up moves[x] = GetBestMove(testField, fieldSize, winLength, opponentPiece, currentPiece, searchDepth - 1, false); } // Here, we look for the best index/indeces List <int> bestIndeces = new List <int>(); int bestValue = int.MinValue; for (int i = 0; i < fieldSize.Width; i++) { if (moves[i] > bestValue) { bestIndeces.Clear(); bestIndeces.Add(i); bestValue = moves[i]; } else if (moves[i] == bestValue) { bestIndeces.Add(i); } } int bestIndex = 0; // After we have a list of indeces (which will be at least 1), we either get the best move if there is 1 move, or a random one of the best moves if there are more if (bestIndeces.Count == 1) { bestIndex = bestIndeces[0]; } else if (bestIndeces.Count >= 2) { bestIndex = bestIndeces[random.Next(0, bestIndeces.Count)]; } // There is a random chance the AI will make a random move // We do this at the end so that the AI will always seem to think, even when it will make a random move // We make a random move to give the opponent a more fair chance, otherwhise you would have to "trick" the AI in order to win if (random.Next(0, 100) <= randomMoveChance) { List <int> availableColumns = new List <int>(); for (int x = 0; x < fieldSize.Width; x++) { if (ProbeY(field, fieldSize, x) != -1) { availableColumns.Add(x); } } bestIndex = availableColumns[random.Next(0, availableColumns.Count)]; } return(bestIndex); }