I've tried to take the shortest, simplest path to a C# solution to the Ultimate Tic-Tac-Toe challenge. This implementation plays in the console. I admit the game logic could be hived off into a separate method, but I'm not sure that would be an improvement. I also admit that the fields should be tighter than public
, but that's neither here nor there.
I would appreciate feedback on:
- How readable people find this solution;
- Any suggestions to make the code more succinct;
- Any other comments people would care to make.
void Main()
{
var mb = new MetaBoard();
var board = -1;
var player = Who.X;
while (WinFor(mb) == Who.Neither && mb.Boards.Any(x => WinFor(x) == Who.Neither)) {
mb.Write();
Console.WriteLine(player + " to play.");
// Choose a board if we've just started or the current board is finished.
while (board == -1 || mb[board] != Who.Neither) board = InputNum("an unfinished board");
var cell = -1;
// Choose an empty cell on the board.
while (cell == -1 || mb.Boards[board][cell] != Who.Neither) cell = InputNum("an empty cell on board " + (board + 1));
Console.WriteLine();
mb.Play(board, cell, player);
// The next board is decided by the cell just played. Swap turns between the players.
board = cell;
player = (player == Who.X ? Who.O : Who.X);
}
mb.Write();
Console.WriteLine(WinFor(mb) + " wins!");
}
static int InputNum(string what) {
var i = -1;
do {
Console.Write("Choose " + what + " [1-9]: ");
} while (!int.TryParse(Console.ReadLine().Trim(), out i) || i < 1 || 9 < i);
return i - 1;
}
enum Who { Neither, X, O }
static int[][] Lines = new int[][] {
new int[] {0,1,2}, new int[] {3,4,5}, new int[] {6,7,8},
new int[] {0,3,6}, new int[] {1,4,7}, new int[] {2,5,8},
new int[] {0,4,8}, new int[] {2,4,6}
};
interface IBoard { Who this[int i] { get; } } // Indicate who, if any, has won position i.
static Who Min(Who p, Who q) { return p == q ? p : Who.Neither; }
static Who WinFor(IBoard board) { // X if a line of three Xs, O if a line of three Os.
return Lines.Select(line => Min(board[line[0]], Min(board[line[1]], board[line[2]])))
.FirstOrDefault(who => who != Who.Neither);
}
class Board: IBoard { // A 3x3 grid of cells.
public Who[] Cells = new Who[9];
public Who this[int i] { get { return Cells[i]; } }
public void Play(int i, Who who) { Cells[i] = who; }
}
class MetaBoard: IBoard { // A 3x3 grid of boards.
public Board[] Boards = Enumerable.Range(0, 9).Select(i => new Board()).ToArray();
public Who this[int i] { get { return WinFor(Boards[i]); } }
public void Play(int i, int j, Who who) { Boards[i].Play(j, who); }
public void Write() {
Action<Who> writeCell = w => { Console.Write(w == Who.X ? 'X' : w == Who.O ? 'O' : '.'); };
var rg = Enumerable.Range(0, 3);
foreach (var rr in rg) foreach (var r in rg) foreach (var cc in rg) foreach (var c in rg) {
writeCell(Boards[cc + 3 * rr].Cells[c + 3 * r]);
if (c == 2) Console.Write(" ");
if (c == 2 && cc == 2) Console.WriteLine();
if (c == 2 && cc == 2 && r == 2) Console.WriteLine();
}
}
}