115 lines
3.4 KiB
D
115 lines
3.4 KiB
D
import std.stdio, std.string, std.algorithm, std.conv, std.random,
|
|
std.ascii, std.array, std.range, std.math;
|
|
|
|
struct GameBoard {
|
|
dchar[9] board = "123456789";
|
|
enum : dchar { human = 'X', computer = 'O' }
|
|
enum Game { going, humanWins, computerWins, draw }
|
|
|
|
const pure nothrow @safe @nogc invariant() {
|
|
int nHuman = 0, nComputer = 0;
|
|
foreach (immutable i, immutable c; board)
|
|
if (c.isDigit)
|
|
assert(i == c - '1'); // In correct position?
|
|
else {
|
|
assert(c == human || c == computer);
|
|
(c == human ? nHuman : nComputer)++;
|
|
}
|
|
assert(abs(nHuman - nComputer) <= 1);
|
|
}
|
|
|
|
string toString() const pure {
|
|
return format("%(%-(%s|%)\n-+-+-\n%)", board[].chunks(3));
|
|
}
|
|
|
|
bool isAvailable(in int i) const pure nothrow @safe @nogc {
|
|
return i >= 0 && i < 9 && board[i].isDigit;
|
|
}
|
|
|
|
auto availablePositions() const pure nothrow @safe /*@nogc*/ {
|
|
return 9.iota.filter!(i => isAvailable(i));
|
|
}
|
|
|
|
Game winner() const pure nothrow @safe /*@nogc*/ {
|
|
static immutable wins = [[0, 1, 2], [3, 4, 5], [6, 7, 8],
|
|
[0, 3, 6], [1, 4, 7], [2, 5, 8],
|
|
[0, 4, 8], [2, 4, 6]];
|
|
|
|
foreach (immutable win; wins) {
|
|
immutable bw0 = board[win[0]];
|
|
if (bw0.isDigit)
|
|
continue; // Nobody wins on this one.
|
|
|
|
if (bw0 == board[win[1]] && bw0 == board[win[2]])
|
|
return bw0 == GameBoard.human ?
|
|
Game.humanWins :
|
|
Game.computerWins;
|
|
}
|
|
|
|
return availablePositions.empty ? Game.draw: Game.going;
|
|
}
|
|
|
|
bool isFinished() const pure nothrow @safe /*@nogc*/ {
|
|
return winner != Game.going;
|
|
}
|
|
|
|
int computerMove() const // Random move.
|
|
out(res) {
|
|
assert(res >= 0 && res < 9 && isAvailable(res));
|
|
} body {
|
|
// return availablePositions.array.choice;
|
|
return availablePositions.array[uniform(0, $)];
|
|
}
|
|
}
|
|
|
|
|
|
GameBoard playGame() {
|
|
GameBoard board;
|
|
bool playsHuman = true;
|
|
|
|
while (!board.isFinished) {
|
|
board.writeln;
|
|
|
|
int move;
|
|
if (playsHuman) {
|
|
do {
|
|
writef("Your move (available moves: %s)? ",
|
|
board.availablePositions.map!q{ a + 1 });
|
|
readf("%d\n", &move);
|
|
move--; // Zero based indexing.
|
|
if (move < 0)
|
|
return board;
|
|
} while (!board.isAvailable(move));
|
|
} else
|
|
move = board.computerMove;
|
|
|
|
assert(board.isAvailable(move));
|
|
writefln("\n%s chose %d", playsHuman ? "You" : "I", move + 1);
|
|
board.board[move] = playsHuman ? GameBoard.human :
|
|
GameBoard.computer;
|
|
playsHuman = !playsHuman; // Switch player.
|
|
}
|
|
|
|
return board;
|
|
}
|
|
|
|
|
|
void main() {
|
|
"Tic-tac-toe game player.\n".writeln;
|
|
immutable outcome = playGame.winner;
|
|
|
|
final switch (outcome) {
|
|
case GameBoard.Game.going:
|
|
"Game stopped.".writeln;
|
|
break;
|
|
case GameBoard.Game.humanWins:
|
|
"\nYou win!".writeln;
|
|
break;
|
|
case GameBoard.Game.computerWins:
|
|
"\nI win.".writeln;
|
|
break;
|
|
case GameBoard.Game.draw:
|
|
"\nDraw".writeln;
|
|
break;
|
|
}
|
|
}
|