Check procs for sub boards.
This commit is contained in:
@@ -1,21 +1,10 @@
|
|||||||
import strformat
|
import sets
|
||||||
|
|
||||||
type
|
include libmttt/types
|
||||||
Board*[T] =
|
include libmttt/checks
|
||||||
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
|
|
||||||
|
|
||||||
proc newBoard[T](initial: T): Board[T] =
|
proc newBoard[T](initial: T): Board[T] =
|
||||||
|
## Create a new game board filled with the initial value
|
||||||
result = [
|
result = [
|
||||||
[initial, initial, initial],
|
[initial, initial, initial],
|
||||||
[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]] =
|
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)],
|
[newBoard(val), newBoard(val), newBoard(val)],
|
||||||
[newBoard(val), newBoard(val), newBoard(val)]
|
[newBoard(val), newBoard(val), newBoard(val)]
|
||||||
]
|
]
|
||||||
|
|
||||||
proc createBoard*(): Board[Board[Mark]] =
|
proc newGame*(player1, player2: Player): GameState =
|
||||||
newMetaBoard(mFree)
|
## Create a new game board
|
||||||
|
GameState(
|
||||||
|
players: [player1, player2],
|
||||||
|
currentPlayer: player1,
|
||||||
|
result: rOpen,
|
||||||
|
board: newMetaBoard(mFree))
|
||||||
|
|
||||||
proc checkBoard*(board: Board[Mark]): BoardResult =
|
proc checkBoard*(board: Board[Mark]): BoardResult =
|
||||||
for x in 0 ..< board.len:
|
## Perform a check on a single sub board to see its result
|
||||||
for y in 0 ..< board.len:
|
|
||||||
echo fmt"Cell (x: {x}, y: {y}) = {board[x][y]}"
|
# 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 =
|
proc checkBoard*(board: Board[Board[Mark]]): BoardResult =
|
||||||
|
## Perform a check on a metaboard to see the overall game result
|
||||||
rOpen
|
rOpen
|
||||||
|
|
||||||
|
|||||||
42
src/libmttt/checks.nim
Normal file
42
src/libmttt/checks.nim
Normal file
@@ -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()
|
||||||
|
|
||||||
39
src/libmttt/types.nim
Normal file
39
src/libmttt/types.nim
Normal file
@@ -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}
|
||||||
|
"""
|
||||||
@@ -9,7 +9,41 @@ import unittest
|
|||||||
|
|
||||||
import libmttt
|
import libmttt
|
||||||
|
|
||||||
test "debugging":
|
suite "Test the board result checker":
|
||||||
var metaBoard = createBoard()
|
setup:
|
||||||
metaBoard[0][0][1][1] = mPlayer1
|
var
|
||||||
echo metaBoard[0][0].checkBoard()
|
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
|
||||||
Reference in New Issue
Block a user