Add move procs.
'makeMove' advances the game by one turn, switches the current player and updates the game result.
This commit is contained in:
@@ -1,10 +1,13 @@
|
|||||||
import sets, strformat, options
|
import sets, strformat, options, sequtils
|
||||||
|
|
||||||
type
|
type
|
||||||
## A game board is a simple 2 dimensional array.
|
## A game board is a simple 2 dimensional array.
|
||||||
## Markers of any type can be placed inside the cells
|
## Markers of any type can be placed inside the cells
|
||||||
Board*[T] = array[3, array[3, T]]
|
Board*[T] = array[3, array[3, T]]
|
||||||
|
|
||||||
|
## A Position in a board
|
||||||
|
Coordinate* = tuple[x: int, y:int]
|
||||||
|
|
||||||
Player* = ref object
|
Player* = ref object
|
||||||
mark*: Mark
|
mark*: Mark
|
||||||
name*: string
|
name*: string
|
||||||
@@ -20,13 +23,18 @@ type
|
|||||||
board*: Board[Board[Mark]]
|
board*: Board[Board[Mark]]
|
||||||
players*: array[2, Player]
|
players*: array[2, Player]
|
||||||
currentPlayer*: Player
|
currentPlayer*: Player
|
||||||
currentBoard*: Option[(int, int)]
|
currentBoard*: Option[Coordinate]
|
||||||
result*: Mark
|
result*: Mark
|
||||||
turn*: int
|
turn*: int
|
||||||
|
|
||||||
|
IllegalMoveError* = object of Exception
|
||||||
|
|
||||||
proc `$`*(player: Player): string =
|
proc `$`*(player: Player): string =
|
||||||
$player.name
|
$player.name
|
||||||
|
|
||||||
|
proc `$`*(player: ref Player): string =
|
||||||
|
$player.name
|
||||||
|
|
||||||
proc `$`*(game: GameState): string =
|
proc `$`*(game: GameState): string =
|
||||||
&"""
|
&"""
|
||||||
Game is in turn {$game.turn}
|
Game is in turn {$game.turn}
|
||||||
@@ -51,8 +59,10 @@ proc newMetaBoard[T](val: T): Board[Board[T]] =
|
|||||||
[newBoard(val), newBoard(val), newBoard(val)]
|
[newBoard(val), newBoard(val), newBoard(val)]
|
||||||
]
|
]
|
||||||
|
|
||||||
proc newGame*(player1, player2: Player): GameState =
|
proc newGame*(player1: var Player, player2: var Player): GameState =
|
||||||
## Create a new game board
|
## Create a new game board
|
||||||
|
player1.mark = mPlayer1
|
||||||
|
player2.mark = mPlayer2
|
||||||
GameState(
|
GameState(
|
||||||
players: [player1, player2],
|
players: [player1, player2],
|
||||||
currentPlayer: player1,
|
currentPlayer: player1,
|
||||||
@@ -126,7 +136,6 @@ proc checkBoard*(board: Board[Mark]): Mark =
|
|||||||
|
|
||||||
return selectResult(states)
|
return selectResult(states)
|
||||||
|
|
||||||
|
|
||||||
proc checkBoard*(board: Board[Board[Mark]]): Mark =
|
proc checkBoard*(board: Board[Board[Mark]]): Mark =
|
||||||
## Perform a check on a metaboard to see the overall game result
|
## Perform a check on a metaboard to see the overall game result
|
||||||
|
|
||||||
@@ -138,3 +147,46 @@ proc checkBoard*(board: Board[Board[Mark]]): Mark =
|
|||||||
for y in 0 .. 2:
|
for y in 0 .. 2:
|
||||||
subResults[x][y] = checkBoard(board[x][y])
|
subResults[x][y] = checkBoard(board[x][y])
|
||||||
return checkBoard(subResults)
|
return checkBoard(subResults)
|
||||||
|
|
||||||
|
###################################################
|
||||||
|
# Process Player Moves
|
||||||
|
###################################################
|
||||||
|
|
||||||
|
proc makeMove*(state: GameState, cell: Coordinate): GameState =
|
||||||
|
if cell.x > 2 or cell.y > 2:
|
||||||
|
raise newException(IndexError, "Move target not in bounds of the board")
|
||||||
|
if state.currentBoard.isNone:
|
||||||
|
raise newException(ValueError, "No board value passed")
|
||||||
|
|
||||||
|
let board = state.currentBoard.get()
|
||||||
|
var currBoard = state.board[board.x][board.y]
|
||||||
|
|
||||||
|
if currBoard[cell.x][cell.y] != mFree:
|
||||||
|
raise newException(IllegalMoveError, "Chosen cell is not free")
|
||||||
|
|
||||||
|
state.board[board.x][board.y][cell.x][cell.y] = state.currentPlayer.mark
|
||||||
|
state.turn += 1
|
||||||
|
state.result = checkBoard(state.board)
|
||||||
|
|
||||||
|
# Exit early. The game has ended.
|
||||||
|
if state.result != mFree:
|
||||||
|
return state
|
||||||
|
|
||||||
|
let nextBoard = checkBoard(state.board[cell.x][cell.y])
|
||||||
|
if nextBoard == mFree:
|
||||||
|
state.currentBoard = cell.some()
|
||||||
|
else:
|
||||||
|
state.currentBoard = none(Coordinate)
|
||||||
|
|
||||||
|
state.currentPlayer = state.players.filter(proc (p: Player): bool =
|
||||||
|
p.mark != state.currentPlayer.mark)[0]
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
proc makeMove*(state: GameState, cell: Coordinate, boardChoice: Coordinate): GameState =
|
||||||
|
if checkBoard(state.board[boardChoice.x][boardChoice.y]) != mFree:
|
||||||
|
raise newException(IllegalMoveError, "Player must choose an open board to play in")
|
||||||
|
if state.currentBoard.isSome:
|
||||||
|
raise newException(IllegalMoveError, "Player does not have free choice for board")
|
||||||
|
state.currentBoard = boardChoice.some()
|
||||||
|
return state.makeMove(cell)
|
||||||
@@ -12,113 +12,113 @@ import libmttt
|
|||||||
suite "Test the board result checker":
|
suite "Test the board result checker":
|
||||||
setup:
|
setup:
|
||||||
var
|
var
|
||||||
player1 = Player(name: "Max")
|
player1 = Player(name: "Adam")
|
||||||
player2 = Player(name: "Adam")
|
player2 = Player(name: "Eve")
|
||||||
state: GameState = newGame(player1, player2)
|
game: GameState = newGame(player1, player2)
|
||||||
|
|
||||||
test "winning row":
|
test "winning row":
|
||||||
state.board[0][0] = [
|
game.board[0][0] = [
|
||||||
[mFree, mFree, mFree],
|
[mFree, mFree, mFree],
|
||||||
[mPlayer1, mPlayer1, mPlayer1],
|
[mPlayer1, mPlayer1, mPlayer1],
|
||||||
[mFree, mFree, mFree]
|
[mFree, mFree, mFree]
|
||||||
]
|
]
|
||||||
check checkBoard(state.board[0][0]) == mPlayer1
|
check checkBoard(game.board[0][0]) == mPlayer1
|
||||||
|
|
||||||
test "winning column":
|
test "winning column":
|
||||||
state.board[0][0] = [
|
game.board[0][0] = [
|
||||||
[mPlayer2, mFree, mPlayer1],
|
[mPlayer2, mFree, mPlayer1],
|
||||||
[mPlayer2, mPlayer1, mFree],
|
[mPlayer2, mPlayer1, mFree],
|
||||||
[mPlayer2, mPlayer1, mFree]
|
[mPlayer2, mPlayer1, mFree]
|
||||||
]
|
]
|
||||||
check checkBoard(state.board[0][0]) == mPlayer2
|
check checkBoard(game.board[0][0]) == mPlayer2
|
||||||
|
|
||||||
test "winning diagonals":
|
test "winning diagonals":
|
||||||
state.board[0][0] = [
|
game.board[0][0] = [
|
||||||
[mFree, mFree, mPlayer2],
|
[mFree, mFree, mPlayer2],
|
||||||
[mFree, mPlayer2, mFree],
|
[mFree, mPlayer2, mFree],
|
||||||
[mPlayer2, mFree, mFree]
|
[mPlayer2, mFree, mFree]
|
||||||
]
|
]
|
||||||
check(checkBoard(state.board[0][0]) == mPlayer2)
|
check(checkBoard(game.board[0][0]) == mPlayer2)
|
||||||
|
|
||||||
state.board[0][0] = [
|
game.board[0][0] = [
|
||||||
[mPlayer1, mPlayer2, mPlayer2],
|
[mPlayer1, mPlayer2, mPlayer2],
|
||||||
[mPlayer2, mPlayer1, mPlayer1],
|
[mPlayer2, mPlayer1, mPlayer1],
|
||||||
[mPlayer2, mFree, mPlayer1]
|
[mPlayer2, mFree, mPlayer1]
|
||||||
]
|
]
|
||||||
check(checkBoard(state.board[0][0]) == mPlayer1)
|
check(checkBoard(game.board[0][0]) == mPlayer1)
|
||||||
|
|
||||||
test "board is a draw":
|
test "board is a draw":
|
||||||
state.board[0][0] = [
|
game.board[0][0] = [
|
||||||
[mPlayer1, mPlayer2, mPlayer1],
|
[mPlayer1, mPlayer2, mPlayer1],
|
||||||
[mPlayer2, mPlayer1, mPlayer1],
|
[mPlayer2, mPlayer1, mPlayer1],
|
||||||
[mPlayer2, mPlayer1, mPlayer2]
|
[mPlayer2, mPlayer1, mPlayer2]
|
||||||
]
|
]
|
||||||
check checkBoard(state.board[0][0]) == mDraw
|
check checkBoard(game.board[0][0]) == mDraw
|
||||||
|
|
||||||
test "board is open":
|
test "board is open":
|
||||||
state.board[0][0] = [
|
game.board[0][0] = [
|
||||||
[mPlayer1, mPlayer2, mFree],
|
[mPlayer1, mPlayer2, mFree],
|
||||||
[mPlayer1, mFree, mPlayer1],
|
[mPlayer1, mFree, mPlayer1],
|
||||||
[mFree, mPlayer1, mPlayer2]
|
[mFree, mPlayer1, mPlayer2]
|
||||||
]
|
]
|
||||||
check checkBoard(state.board[0][0]) == mFree
|
check checkBoard(game.board[0][0]) == mFree
|
||||||
|
|
||||||
test "free inital metaboard":
|
test "free inital metaboard":
|
||||||
check checkBoard(state.board) == mFree
|
check checkBoard(game.board) == mFree
|
||||||
|
|
||||||
test "winning metaboard row":
|
test "winning metaboard row":
|
||||||
state.board[1][0] = [
|
game.board[1][0] = [
|
||||||
[mPlayer1, mPlayer2, mFree],
|
[mPlayer1, mPlayer2, mFree],
|
||||||
[mPlayer1, mPlayer1, mPlayer1],
|
[mPlayer1, mPlayer1, mPlayer1],
|
||||||
[mFree, mPlayer1, mPlayer2]
|
[mFree, mPlayer1, mPlayer2]
|
||||||
]
|
]
|
||||||
state.board[1][1] = [
|
game.board[1][1] = [
|
||||||
[mFree, mFree, mPlayer1],
|
[mFree, mFree, mPlayer1],
|
||||||
[mFree, mPlayer1, mFree],
|
[mFree, mPlayer1, mFree],
|
||||||
[mPlayer1, mFree, mFree]
|
[mPlayer1, mFree, mFree]
|
||||||
]
|
]
|
||||||
state.board[1][2] = [
|
game.board[1][2] = [
|
||||||
[mFree, mFree, mPlayer1],
|
[mFree, mFree, mPlayer1],
|
||||||
[mFree, mFree, mPlayer1],
|
[mFree, mFree, mPlayer1],
|
||||||
[mFree, mFree, mPlayer1]
|
[mFree, mFree, mPlayer1]
|
||||||
]
|
]
|
||||||
check checkBoard(state.board) == mPlayer1
|
check checkBoard(game.board) == mPlayer1
|
||||||
|
|
||||||
test "winning metaboard column":
|
test "winning metaboard column":
|
||||||
state.board[0][1] = [
|
game.board[0][1] = [
|
||||||
[mPlayer1, mPlayer2, mFree],
|
[mPlayer1, mPlayer2, mFree],
|
||||||
[mPlayer1, mPlayer1, mPlayer1],
|
[mPlayer1, mPlayer1, mPlayer1],
|
||||||
[mFree, mPlayer1, mPlayer2]
|
[mFree, mPlayer1, mPlayer2]
|
||||||
]
|
]
|
||||||
state.board[1][1] = [
|
game.board[1][1] = [
|
||||||
[mFree, mFree, mFree],
|
[mFree, mFree, mFree],
|
||||||
[mPlayer1, mPlayer1, mPlayer1],
|
[mPlayer1, mPlayer1, mPlayer1],
|
||||||
[mFree, mFree, mFree]
|
[mFree, mFree, mFree]
|
||||||
]
|
]
|
||||||
state.board[2][1] = [
|
game.board[2][1] = [
|
||||||
[mPlayer1, mFree, mFree],
|
[mPlayer1, mFree, mFree],
|
||||||
[mPlayer1, mFree, mFree],
|
[mPlayer1, mFree, mFree],
|
||||||
[mPlayer1, mFree, mFree]
|
[mPlayer1, mFree, mFree]
|
||||||
]
|
]
|
||||||
check checkBoard(state.board) == mPlayer1
|
check checkBoard(game.board) == mPlayer1
|
||||||
|
|
||||||
test "winning metaboard diagonal":
|
test "winning metaboard diagonal":
|
||||||
state.board[0][0] = [
|
game.board[0][0] = [
|
||||||
[mPlayer2, mPlayer2, mFree],
|
[mPlayer2, mPlayer2, mFree],
|
||||||
[mPlayer2, mPlayer2, mPlayer2],
|
[mPlayer2, mPlayer2, mPlayer2],
|
||||||
[mFree, mPlayer2, mPlayer2]
|
[mFree, mPlayer2, mPlayer2]
|
||||||
]
|
]
|
||||||
state.board[1][1] = [
|
game.board[1][1] = [
|
||||||
[mFree, mFree, mFree],
|
[mFree, mFree, mFree],
|
||||||
[mPlayer2, mPlayer2, mPlayer2],
|
[mPlayer2, mPlayer2, mPlayer2],
|
||||||
[mFree, mFree, mFree]
|
[mFree, mFree, mFree]
|
||||||
]
|
]
|
||||||
state.board[2][2] = [
|
game.board[2][2] = [
|
||||||
[mPlayer2, mFree, mFree],
|
[mPlayer2, mFree, mFree],
|
||||||
[mPlayer2, mFree, mFree],
|
[mPlayer2, mFree, mFree],
|
||||||
[mPlayer2, mFree, mFree]
|
[mPlayer2, mFree, mFree]
|
||||||
]
|
]
|
||||||
check checkBoard(state.board) == mPlayer2
|
check checkBoard(game.board) == mPlayer2
|
||||||
|
|
||||||
test "winning metaboard with some boards in draw":
|
test "winning metaboard with some boards in draw":
|
||||||
let winner = [
|
let winner = [
|
||||||
@@ -131,12 +131,12 @@ suite "Test the board result checker":
|
|||||||
[mPlayer1, mPlayer1, mPlayer2],
|
[mPlayer1, mPlayer1, mPlayer2],
|
||||||
[mPlayer2, mPlayer1, mPlayer2]
|
[mPlayer2, mPlayer1, mPlayer2]
|
||||||
]
|
]
|
||||||
state.board[0][0] = winner
|
game.board[0][0] = winner
|
||||||
state.board[1][1] = winner
|
game.board[1][1] = winner
|
||||||
state.board[2][2] = winner
|
game.board[2][2] = winner
|
||||||
state.board[1][0] = drawn
|
game.board[1][0] = drawn
|
||||||
state.board[2][0] = drawn
|
game.board[2][0] = drawn
|
||||||
check checkBoard(state.board) == mPlayer2
|
check checkBoard(game.board) == mPlayer2
|
||||||
|
|
||||||
test "metaboard is drawn":
|
test "metaboard is drawn":
|
||||||
let drawn = [
|
let drawn = [
|
||||||
@@ -146,14 +146,14 @@ suite "Test the board result checker":
|
|||||||
]
|
]
|
||||||
for x in 0 .. 2:
|
for x in 0 .. 2:
|
||||||
for y in 0 .. 2:
|
for y in 0 .. 2:
|
||||||
state.board[x][y] = drawn
|
game.board[x][y] = drawn
|
||||||
check checkBoard(state.board) == mDraw
|
check checkBoard(game.board) == mDraw
|
||||||
|
|
||||||
test "illegal situation: both players win board":
|
test "illegal situation: both players win board":
|
||||||
state.board[1][1] = [
|
game.board[1][1] = [
|
||||||
[mPlayer1, mPlayer1, mPlayer1],
|
[mPlayer1, mPlayer1, mPlayer1],
|
||||||
[mPlayer2, mPlayer2, mPlayer2],
|
[mPlayer2, mPlayer2, mPlayer2],
|
||||||
[mFree, mFree, mFree]
|
[mFree, mFree, mFree]
|
||||||
]
|
]
|
||||||
expect(Exception):
|
expect(Exception):
|
||||||
discard checkBoard(state.board)
|
discard checkBoard(game.board)
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import unittest, options
|
||||||
|
|
||||||
|
import libmttt
|
||||||
|
|
||||||
|
suite "Test the move procedures":
|
||||||
|
setup:
|
||||||
|
var
|
||||||
|
player1 = Player(name: "Adam")
|
||||||
|
player2 = Player(name: "Eve")
|
||||||
|
game: GameState = newGame(player1, player2)
|
||||||
|
|
||||||
|
test "Inital state":
|
||||||
|
check game.currentPlayer == player1
|
||||||
|
check game.players == [player1, player2]
|
||||||
|
check game.turn == 0
|
||||||
|
check game.currentBoard.isNone
|
||||||
|
check game.result == mFree
|
||||||
|
|
||||||
|
test "First move":
|
||||||
|
game = game.makeMove((1, 1), (1, 1))
|
||||||
|
check game.currentPlayer == player2
|
||||||
|
check game.turn == 1
|
||||||
|
check game.currentBoard == (1, 1).some()
|
||||||
|
check game.result == mFree
|
||||||
|
|
||||||
|
test "Second move":
|
||||||
|
game = game.makeMove((1, 1), (1, 1))
|
||||||
|
game = game.makeMove((0, 0))
|
||||||
|
check game.currentPlayer == player1
|
||||||
|
check game.turn == 2
|
||||||
|
check game.currentBoard == (0, 0).some()
|
||||||
|
check game.result == mFree
|
||||||
|
|
||||||
|
test "Move on wrong board":
|
||||||
|
game = game.makeMove((1, 1), (1, 1))
|
||||||
|
expect(IllegalMoveError):
|
||||||
|
game = game.makeMove((0, 0), (2, 2))
|
||||||
|
|
||||||
|
test "Move on a taken cell":
|
||||||
|
game = game.makeMove((1, 1), (1, 1))
|
||||||
|
expect(IllegalMoveError):
|
||||||
|
game = game.makeMove((1, 1))
|
||||||
|
|
||||||
|
test "Move out of bounds":
|
||||||
|
expect(IndexError):
|
||||||
|
game = game.makeMove((3, 0), (0, 0))
|
||||||
|
|
||||||
|
test "Player 1 wins the game":
|
||||||
|
let winner = [
|
||||||
|
[mPlayer1, mFree, mFree],
|
||||||
|
[mPlayer1, mFree, mFree],
|
||||||
|
[mPlayer1, mFree, mFree]
|
||||||
|
]
|
||||||
|
game.board[0][0] = winner
|
||||||
|
game.board[1][1] = winner
|
||||||
|
|
||||||
|
game.board[2][2] = [
|
||||||
|
[mPlayer1, mFree, mFree],
|
||||||
|
[mFree, mFree, mFree],
|
||||||
|
[mPlayer1, mFree, mFree]
|
||||||
|
]
|
||||||
|
check checkBoard(game.board) == mFree
|
||||||
|
game = game.makeMove((1, 0), (2, 2))
|
||||||
|
check game.result == mPlayer1
|
||||||
|
check game.currentPlayer == player1
|
||||||
|
|
||||||
|
test "The game ends in a draw":
|
||||||
|
let drawn = [
|
||||||
|
[mPlayer2, mPlayer2, mPlayer1],
|
||||||
|
[mPlayer1, mPlayer1, mPlayer2],
|
||||||
|
[mPlayer2, mPlayer1, mPlayer2]
|
||||||
|
]
|
||||||
|
for x in 0 .. 2:
|
||||||
|
for y in 0 .. 2:
|
||||||
|
game.board[x][y] = drawn
|
||||||
|
|
||||||
|
game.board[2][2] = [
|
||||||
|
[mPlayer2, mPlayer2, mPlayer1],
|
||||||
|
[mPlayer1, mPlayer1, mPlayer2],
|
||||||
|
[mPlayer2, mFree, mPlayer2]
|
||||||
|
]
|
||||||
|
check checkBoard(game.board) == mFree
|
||||||
|
game = game.makeMove((2, 1), (2, 2))
|
||||||
|
check game.result == mDraw
|
||||||
|
check game.currentPlayer == player1
|
||||||
Reference in New Issue
Block a user