From cbac82becd35cb312479578c24e043d0884a0ff0 Mon Sep 17 00:00:00 2001 From: luxick Date: Mon, 28 Oct 2019 21:17:25 +0100 Subject: [PATCH] Add move procs. 'makeMove' advances the game by one turn, switches the current player and updates the game result. --- src/libmttt.nim | 60 ++++++++++++++++++++++++++++--- tests/testChecks.nim | 76 +++++++++++++++++++-------------------- tests/testMoves.nim | 85 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 42 deletions(-) diff --git a/src/libmttt.nim b/src/libmttt.nim index 748f404..b73331d 100644 --- a/src/libmttt.nim +++ b/src/libmttt.nim @@ -1,10 +1,13 @@ -import sets, strformat, options +import sets, strformat, options, sequtils type ## A game board is a simple 2 dimensional array. ## Markers of any type can be placed inside the cells Board*[T] = array[3, array[3, T]] + ## A Position in a board + Coordinate* = tuple[x: int, y:int] + Player* = ref object mark*: Mark name*: string @@ -20,13 +23,18 @@ type board*: Board[Board[Mark]] players*: array[2, Player] currentPlayer*: Player - currentBoard*: Option[(int, int)] + currentBoard*: Option[Coordinate] result*: Mark turn*: int + IllegalMoveError* = object of Exception + proc `$`*(player: Player): string = $player.name +proc `$`*(player: ref Player): string = + $player.name + proc `$`*(game: GameState): string = &""" Game is in turn {$game.turn} @@ -51,8 +59,10 @@ proc newMetaBoard[T](val: T): Board[Board[T]] = [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 + player1.mark = mPlayer1 + player2.mark = mPlayer2 GameState( players: [player1, player2], currentPlayer: player1, @@ -126,7 +136,6 @@ proc checkBoard*(board: Board[Mark]): Mark = return selectResult(states) - proc checkBoard*(board: Board[Board[Mark]]): Mark = ## 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: subResults[x][y] = checkBoard(board[x][y]) 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) \ No newline at end of file diff --git a/tests/testChecks.nim b/tests/testChecks.nim index b05b078..4b5539f 100644 --- a/tests/testChecks.nim +++ b/tests/testChecks.nim @@ -12,113 +12,113 @@ import libmttt suite "Test the board result checker": setup: var - player1 = Player(name: "Max") - player2 = Player(name: "Adam") - state: GameState = newGame(player1, player2) + player1 = Player(name: "Adam") + player2 = Player(name: "Eve") + game: GameState = newGame(player1, player2) test "winning row": - state.board[0][0] = [ + game.board[0][0] = [ [mFree, mFree, mFree], [mPlayer1, mPlayer1, mPlayer1], [mFree, mFree, mFree] ] - check checkBoard(state.board[0][0]) == mPlayer1 + check checkBoard(game.board[0][0]) == mPlayer1 test "winning column": - state.board[0][0] = [ + game.board[0][0] = [ [mPlayer2, mFree, mPlayer1], [mPlayer2, mPlayer1, mFree], [mPlayer2, mPlayer1, mFree] ] - check checkBoard(state.board[0][0]) == mPlayer2 + check checkBoard(game.board[0][0]) == mPlayer2 test "winning diagonals": - state.board[0][0] = [ + game.board[0][0] = [ [mFree, mFree, mPlayer2], [mFree, mPlayer2, 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], [mPlayer2, mPlayer1, mPlayer1], [mPlayer2, mFree, mPlayer1] ] - check(checkBoard(state.board[0][0]) == mPlayer1) + check(checkBoard(game.board[0][0]) == mPlayer1) test "board is a draw": - state.board[0][0] = [ + game.board[0][0] = [ [mPlayer1, mPlayer2, mPlayer1], [mPlayer2, mPlayer1, mPlayer1], [mPlayer2, mPlayer1, mPlayer2] ] - check checkBoard(state.board[0][0]) == mDraw + check checkBoard(game.board[0][0]) == mDraw test "board is open": - state.board[0][0] = [ + game.board[0][0] = [ [mPlayer1, mPlayer2, mFree], [mPlayer1, mFree, mPlayer1], [mFree, mPlayer1, mPlayer2] ] - check checkBoard(state.board[0][0]) == mFree + check checkBoard(game.board[0][0]) == mFree test "free inital metaboard": - check checkBoard(state.board) == mFree + check checkBoard(game.board) == mFree test "winning metaboard row": - state.board[1][0] = [ + game.board[1][0] = [ [mPlayer1, mPlayer2, mFree], [mPlayer1, mPlayer1, mPlayer1], [mFree, mPlayer1, mPlayer2] ] - state.board[1][1] = [ + game.board[1][1] = [ [mFree, mFree, mPlayer1], [mFree, mPlayer1, mFree], [mPlayer1, mFree, mFree] ] - state.board[1][2] = [ + game.board[1][2] = [ [mFree, mFree, mPlayer1], [mFree, mFree, mPlayer1], [mFree, mFree, mPlayer1] ] - check checkBoard(state.board) == mPlayer1 + check checkBoard(game.board) == mPlayer1 test "winning metaboard column": - state.board[0][1] = [ + game.board[0][1] = [ [mPlayer1, mPlayer2, mFree], [mPlayer1, mPlayer1, mPlayer1], [mFree, mPlayer1, mPlayer2] ] - state.board[1][1] = [ + game.board[1][1] = [ [mFree, mFree, mFree], [mPlayer1, mPlayer1, mPlayer1], [mFree, mFree, mFree] ] - state.board[2][1] = [ + game.board[2][1] = [ [mPlayer1, mFree, mFree], [mPlayer1, mFree, mFree], [mPlayer1, mFree, mFree] ] - check checkBoard(state.board) == mPlayer1 + check checkBoard(game.board) == mPlayer1 test "winning metaboard diagonal": - state.board[0][0] = [ + game.board[0][0] = [ [mPlayer2, mPlayer2, mFree], [mPlayer2, mPlayer2, mPlayer2], [mFree, mPlayer2, mPlayer2] ] - state.board[1][1] = [ + game.board[1][1] = [ [mFree, mFree, mFree], [mPlayer2, mPlayer2, mPlayer2], [mFree, mFree, mFree] ] - state.board[2][2] = [ + game.board[2][2] = [ [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": let winner = [ @@ -131,12 +131,12 @@ suite "Test the board result checker": [mPlayer1, mPlayer1, mPlayer2], [mPlayer2, mPlayer1, mPlayer2] ] - state.board[0][0] = winner - state.board[1][1] = winner - state.board[2][2] = winner - state.board[1][0] = drawn - state.board[2][0] = drawn - check checkBoard(state.board) == mPlayer2 + game.board[0][0] = winner + game.board[1][1] = winner + game.board[2][2] = winner + game.board[1][0] = drawn + game.board[2][0] = drawn + check checkBoard(game.board) == mPlayer2 test "metaboard is drawn": let drawn = [ @@ -146,14 +146,14 @@ suite "Test the board result checker": ] for x in 0 .. 2: for y in 0 .. 2: - state.board[x][y] = drawn - check checkBoard(state.board) == mDraw + game.board[x][y] = drawn + check checkBoard(game.board) == mDraw test "illegal situation: both players win board": - state.board[1][1] = [ + game.board[1][1] = [ [mPlayer1, mPlayer1, mPlayer1], [mPlayer2, mPlayer2, mPlayer2], [mFree, mFree, mFree] ] expect(Exception): - discard checkBoard(state.board) \ No newline at end of file + discard checkBoard(game.board) \ No newline at end of file diff --git a/tests/testMoves.nim b/tests/testMoves.nim index e69de29..2e05403 100644 --- a/tests/testMoves.nim +++ b/tests/testMoves.nim @@ -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 \ No newline at end of file