Skip to content

Instantly share code, notes, and snippets.

@chheller
Created March 8, 2017 08:12
Show Gist options
  • Save chheller/99b95842bf821396e70072e5b9decf9a to your computer and use it in GitHub Desktop.
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.
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