From b0bdb16b4a82f03218a5bdd6507338b42d457d2a Mon Sep 17 00:00:00 2001 From: luxick Date: Sat, 26 Oct 2019 00:15:30 +0200 Subject: [PATCH] Check procs for sub boards. --- src/libmttt.nim | 49 ++++++++++++++++++++++++------------------ src/libmttt/checks.nim | 42 ++++++++++++++++++++++++++++++++++++ src/libmttt/types.nim | 39 +++++++++++++++++++++++++++++++++ tests/test1.nim | 42 ++++++++++++++++++++++++++++++++---- 4 files changed, 147 insertions(+), 25 deletions(-) create mode 100644 src/libmttt/checks.nim create mode 100644 src/libmttt/types.nim diff --git a/src/libmttt.nim b/src/libmttt.nim index f4ef247..c04b3fe 100644 --- a/src/libmttt.nim +++ b/src/libmttt.nim @@ -1,21 +1,10 @@ -import strformat +import sets -type - Board*[T] = - array[3, array[3, T]] - - Mark* = enum - mPlayer1, # The mark of the fist player - mPlayer2, # The mark of the second player - mFree # This mark signals that a cell is empty - - BoardResult* = enum - rPlayer1, # The first player has won the board - rPlayer2, # The second player has won the board - rDraw, # There is no winner. The board has ended in a draw - rOpen # The game on this board is still ongoing +include libmttt/types +include libmttt/checks proc newBoard[T](initial: T): Board[T] = + ## Create a new game board filled with the initial value result = [ [initial, initial, initial], [initial, initial, initial], @@ -23,20 +12,38 @@ proc newBoard[T](initial: T): Board[T] = ] proc newMetaBoard[T](val: T): Board[Board[T]] = + ## Create the meta board, composed out of 9 normal boards. [ [newBoard(val), newBoard(val), newBoard(val)], [newBoard(val), newBoard(val), newBoard(val)], [newBoard(val), newBoard(val), newBoard(val)] ] -proc createBoard*(): Board[Board[Mark]] = - newMetaBoard(mFree) +proc newGame*(player1, player2: Player): GameState = + ## Create a new game board + GameState( + players: [player1, player2], + currentPlayer: player1, + result: rOpen, + board: newMetaBoard(mFree)) proc checkBoard*(board: Board[Mark]): BoardResult = - for x in 0 ..< board.len: - for y in 0 ..< board.len: - echo fmt"Cell (x: {x}, y: {y}) = {board[x][y]}" + ## Perform a check on a single sub board to see its result + + # Create a seconded, transposed board. + # This way 'checkRows' can be used to check the columns + var transposed = newBoard(mFree); + for x in 0 .. 2: + for y in 0 .. 2: + transposed[x][y] = board[y][x] + + var states: HashSet[BoardResult] + states.init() + for b in [board, transposed]: + states.incl(checkRows(b)) + return selectResult(states) + proc checkBoard*(board: Board[Board[Mark]]): BoardResult = + ## Perform a check on a metaboard to see the overall game result rOpen - diff --git a/src/libmttt/checks.nim b/src/libmttt/checks.nim new file mode 100644 index 0000000..ea74fcd --- /dev/null +++ b/src/libmttt/checks.nim @@ -0,0 +1,42 @@ +import sets + +import types + +proc selectResult(states: HashSet[BoardResult]): BoardResult = + ## Analyse a set of results and select the correct one + if states.contains(rPlayer1) and + states.contains(rPlayer2): + raise newException(Exception, "Both players cannot win at the same time") + + if states.contains(rPlayer1): + return rPlayer1 + if states.contains(rPlayer2): + return rPlayer2 + if states.contains(rOpen): + return rOpen + return rDraw + +proc checkRow(row: array[3, Mark]): BoardResult = + var tokens = toHashSet(row) + # A player has won + if tokens.len == 1 and not tokens.contains(mFree): + let rowResult = tokens.pop() + if rowResult == mPlayer1: + return rPlayer1 + else: + return rPlayer2 + # The row is full + if tokens.len == 2 and not tokens.contains(mFree): + return rDraw + # There are still cells free in this row + else: + return rOpen + +proc checkRows(board: Board[Mark]): BoardResult = + ## Iterate over all rows of this board and return the result. + var states: HashSet[BoardResult] + states.init() + for row in board: + states.incl(checkRow(row)) + return states.selectResult() + \ No newline at end of file diff --git a/src/libmttt/types.nim b/src/libmttt/types.nim new file mode 100644 index 0000000..88e4ef8 --- /dev/null +++ b/src/libmttt/types.nim @@ -0,0 +1,39 @@ +import strformat +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]] + + Player* = ref object + mark*: Mark + name*: string + + Mark* = enum + mPlayer1, ## The mark of the fist player + mPlayer2, ## The mark of the second player + mFree ## This mark signals that a cell is empty + + BoardResult* = enum + rPlayer1, ## The first player has won the board + rPlayer2, ## The second player has won the board + rDraw, ## There is no winner. The board has ended in a draw + rOpen ## The game on this board is still ongoing + + GameState* = ref object + ## Contains all state for a pint in the game + board*: Board[Board[Mark]] + players*: array[2, Player] + currentPlayer*: Player + result*: BoardResult + turn*: int + +proc `$`*(player: Player): string = + $player.name + +proc `$`*(game: GameState): string = + &""" + Game is in turn {$game.turn} + Players are: '{$game.players[0]}' and '{$game.players[1]}' + Current player: '{$game.currentPlayer}' + Game state is: {$game.result} + """ \ No newline at end of file diff --git a/tests/test1.nim b/tests/test1.nim index 06e536a..84e3900 100644 --- a/tests/test1.nim +++ b/tests/test1.nim @@ -9,7 +9,41 @@ import unittest import libmttt -test "debugging": - var metaBoard = createBoard() - metaBoard[0][0][1][1] = mPlayer1 - echo metaBoard[0][0].checkBoard() +suite "Test the board result checker": + setup: + var + player1 = Player(name: "Max") + player2 = Player(name: "Adam") + state: GameState = newGame(player1, player2) + + test "row checking": + state.board[0][0] = [ + [mFree, mFree, mFree], + [mPlayer1, mPlayer1, mPlayer1], + [mFree, mFree, mFree] + ] + check checkBoard(state.board[0][0]) == rPlayer1 + + test "column checking": + state.board[0][0] = [ + [mPlayer2, mFree, mPlayer1], + [mPlayer2, mPlayer1, mFree], + [mPlayer2, mPlayer1, mFree] + ] + check checkBoard(state.board[0][0]) == rPlayer2 + + test "check for draw": + state.board[0][0] = [ + [mPlayer1, mPlayer2, mPlayer1], + [mPlayer2, mPlayer1, mPlayer1], + [mPlayer2, mPlayer1, mPlayer2] + ] + check checkBoard(state.board[0][0]) == rDraw + + test "check for open board": + state.board[0][0] = [ + [mPlayer1, mPlayer2, mFree], + [mPlayer1, mFree, mPlayer1], + [mFree, mPlayer1, mPlayer2] + ] + check checkBoard(state.board[0][0]) == rOpen \ No newline at end of file