Created
March 8, 2017 08:12
-
-
Save chheller/99b95842bf821396e70072e5b9decf9a to your computer and use it in GitHub Desktop.
This is an implementation of MiniMax for Tic Tac Toe in C#. It's pretty simple, fairly crude, but works. The entire code is sourced, and should build after pasting.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Linq; | |
namespace Minimax | |
{ | |
class Game | |
{ | |
static char[] board; | |
static bool isPlayerTurn; | |
static char CPUSymbol = 'X'; | |
static char PlayerSymbol = 'O'; | |
static void Main(string[] args) | |
{ | |
Random R = new Random(); | |
int winner; | |
//Initialization of main game loop. | |
while (true) | |
{ | |
InitGame(); | |
winner = PlayGame(); | |
DrawBoard(); | |
switch (winner) | |
{ | |
case 0: | |
Console.WriteLine("Another Draw and I'll go M.A.D.!"); | |
break; | |
case 10: | |
Console.WriteLine(R.NextDouble() < .30 ? "Perhaps a game of Thermonuclear War instead?" : "I win again, how boring."); | |
break; | |
case -10: | |
Console.WriteLine("Oh, you won. I am fundamentally and profoundly flawed."); | |
break; | |
} | |
Console.Write("Play again? (y/n)>>> "); | |
var input = Console.ReadLine(); | |
while (!input.ToLower().Contains("y") && !input.ToLower().Contains("n")) | |
{ | |
Console.WriteLine("You must have made a typo. Did you mean to launch the nukes instead...?"); | |
Console.Write("Play again? (y/n)>>> "); | |
input = Console.ReadLine(); | |
} | |
if (input.ToLower() == "n") return; | |
} | |
} | |
public static void InitGame() | |
{ | |
isPlayerTurn = true; | |
board = new char[] { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }; | |
} | |
public static int PlayGame() | |
{ | |
while (true) | |
{ | |
DrawBoard(); | |
if (!board.Contains(' ')) return 0; | |
//Take Turn | |
if (isPlayerTurn) PlayerTurn(); | |
else CPUTurn(); | |
//Return game result if over | |
if (GameOver(isPlayerTurn) != 0) return GameOver(isPlayerTurn); | |
//Change turns | |
isPlayerTurn = !isPlayerTurn; | |
} | |
} | |
public static void PlayerTurn() | |
{ | |
//TODO: More user friendliness and input checking... maybe | |
Console.Write("Select a cell from 0 to 8 >>> "); | |
int move; | |
var inp = Console.ReadLine(); | |
//Ugh, input sanitization | |
while (!int.TryParse(inp, out move) || move > 8 || move < 0 || !char.IsWhiteSpace(board[move])) | |
{ | |
Console.Write("try again...>>> "); | |
inp = Console.ReadLine(); | |
} | |
board[move] = PlayerSymbol; | |
} | |
public static void CPUTurn() | |
{ | |
//Arbitrarily small numbers | |
var bestScore = int.MinValue; | |
var move = -1; | |
//Must try to move to every possible space and calculate the resulting branches. | |
for (var i = 0; i < 9; i++) | |
{ | |
//The move is only valid if it's empty | |
if (char.IsWhiteSpace(board[i])) | |
{ | |
//Place the CPU Symbol | |
board[i] = CPUSymbol; | |
//Start a recursive branch for the move i | |
int temp_score = MiniMax(true, 0); | |
//Remove the symbol, after all we're just trying a move out , not committing to it | |
board[i] = ' '; | |
//Set the best move to be i, when minimax returns a score larger than our current score | |
if (bestScore < temp_score) | |
{ | |
bestScore = temp_score; | |
move = i; | |
} | |
} | |
} | |
//Make the move | |
board[move] = CPUSymbol; | |
} | |
public static int MiniMax(bool player, int depth) | |
{ | |
/** | |
* Check to see if the last move won the game. If so we need to return the value | |
* corresponding to the player that won. Otherwise we need to check if there are any valid moves left. | |
* If there are no moves left, and no one has one, then it is a draw. | |
**/ | |
var score = GameOver(player); | |
if (score != 0) | |
return score + (score < 0 ? depth : -depth); | |
if (!board.Contains(' ')) return 0; | |
/** | |
* The max part of minimax. The CPU is the maximizer here, and scores | |
* points by winning and nothing else | |
**/ | |
if (!player) | |
{ | |
var bestScore = int.MinValue; | |
for (var i = 0; i < 9; i++) | |
{ | |
if (char.IsWhiteSpace(board[i])) | |
{ | |
board[i] = CPUSymbol; | |
var t = MiniMax(!player, ++depth); | |
bestScore = Math.Max(bestScore, t); | |
board[i] = ' '; | |
} | |
} | |
return bestScore; | |
} | |
/** | |
*This is the minimizer, when it is the player's turn, this will seek to reduce the score as | |
* much as possible. This way, your algorithm will automatically play the best move against itself when simulating, | |
* and thus no player could make better moves against it. | |
**/ | |
else | |
{ | |
var bestScore = int.MaxValue; | |
for (var i = 0; i < 9; i++) | |
{ | |
if (char.IsWhiteSpace(board[i])) | |
{ | |
board[i] = PlayerSymbol; | |
var t = MiniMax(!player, ++depth); | |
bestScore = Math.Min(bestScore, t); | |
board[i] = ' '; | |
} | |
} | |
return bestScore; | |
} | |
} | |
public static void DrawBoard() | |
{ | |
Console.WriteLine(string.Format( | |
"*--+---+--*\n" + | |
" {0} | {1} | {2} \n" + | |
"---+---+---\n" + | |
" {3} | {4} | {5} \n" + | |
"---+---+---\n" + | |
" {6} | {7} | {8} \n" + | |
"*--+---+--*\n", | |
board[0], board[1], board[2], | |
board[3], board[4], board[5], | |
board[6], board[7], board[8]) | |
); | |
} | |
public static int GameOver(bool player) | |
{ | |
//Hard coding the game over states because doing it eloquently introduced errors | |
//Horizontal | |
if (board[0] != ' ' && board[0] == board[1] && board[1] == board[2]) | |
return 10 * (board[0] == PlayerSymbol ? -1 : 1); | |
if (board[3] != ' ' && board[3] == board[4] && board[4] == board[5]) | |
return 10 * (board[3] == PlayerSymbol ? -1 : 1); | |
if (board[6] != ' ' && board[6] == board[7] && board[7] == board[8]) | |
return 10 * (board[6] == PlayerSymbol ? -1 : 1); | |
//Vertical | |
if (board[0] != ' ' && board[0] == board[3] && board[3] == board[6]) | |
return 10 * (board[0] == PlayerSymbol ? -1 : 1); | |
if (board[1] != ' ' && board[1] == board[4] && board[4] == board[7]) | |
return 10 * (board[1] == PlayerSymbol ? -1 : 1); | |
if (board[2] != ' ' && board[2] == board[5] && board[5] == board[8]) | |
return 10 * (board[2] == PlayerSymbol ? -1 : 1); | |
//Diagonal | |
if (board[0] != ' ' && board[0] == board[4] && board[4] == board[8]) | |
return 10 * (board[0] == PlayerSymbol ? -1 : 1); | |
if (board[2] != ' ' && board[2] == board[4] && board[4] == board[6]) | |
return 10 * (board[2] == PlayerSymbol ? -1 : 1); | |
//Draw | |
return 0; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment