Add diagonal and metaboard checking.

This commit is contained in:
2019-10-28 19:06:46 +01:00
parent b0bdb16b4a
commit 2e527a79ac
7 changed files with 301 additions and 139 deletions

View File

@@ -1,7 +1,39 @@
import sets
import sets, strformat, options
include libmttt/types
include libmttt/checks
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
mDraw ## Special mark to indicate the game has ended in a draw
GameState* = ref object
## Contains all state for a pint in the game
board*: Board[Board[Mark]]
players*: array[2, Player]
currentPlayer*: Player
currentBoard*: Option[(int, int)]
result*: Mark
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}
"""
proc newBoard[T](initial: T): Board[T] =
## Create a new game board filled with the initial value
@@ -24,10 +56,59 @@ proc newGame*(player1, player2: Player): GameState =
GameState(
players: [player1, player2],
currentPlayer: player1,
result: rOpen,
result: mFree,
board: newMetaBoard(mFree))
proc checkBoard*(board: Board[Mark]): BoardResult =
###################################################
# Board Checking
###################################################
proc selectResult(states: HashSet[Mark]): Mark =
## Analyse a set of results and select the correct one
if states.contains(mPlayer1) and
states.contains(mPlayer2):
raise newException(Exception, "Both players cannot win at the same time")
if states.contains(mPlayer1):
return mPlayer1
if states.contains(mPlayer2):
return mPlayer2
if states.contains(mFree):
return mFree
return mDraw
proc checkRow(row: array[3, Mark]): Mark =
var tokens = toHashSet(row)
# A player has won
if tokens.len == 1 and not tokens.contains(mFree):
return tokens.pop()
# The row is full
if tokens.len == 2 and not tokens.contains(mFree):
return mDraw
# There are still cells free in this row
return mFree
proc checkRows(board: Board[Mark]): Mark =
## Iterate over all rows of this board and return the result.
var states: HashSet[Mark]
states.init()
for row in board:
states.incl(checkRow(row))
return states.selectResult()
proc checkDiagonals(board: Board[Mark]): Mark =
var states: HashSet[Mark]
states.init()
var topToBottom: array[3, Mark]
var bottomToTop: array[3, Mark]
for x in 0 .. 2:
topToBottom[x] = board[x][x]
bottomToTop[x] = board[2-x][x]
states.incl(checkRow(topToBottom))
states.incl(checkRow(bottomToTop))
return states.selectResult()
proc checkBoard*(board: Board[Mark]): Mark =
## Perform a check on a single sub board to see its result
# Create a seconded, transposed board.
@@ -37,13 +118,23 @@ proc checkBoard*(board: Board[Mark]): BoardResult =
for y in 0 .. 2:
transposed[x][y] = board[y][x]
var states: HashSet[BoardResult]
states.init()
var states: HashSet[Mark]
states.init()
states.incl(checkDiagonals(board))
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]]): Mark =
## Perform a check on a metaboard to see the overall game result
rOpen
# This temporary board will hold the intermediate results from each sub board
var subResults = newBoard(mFree)
var states: HashSet[Mark]
states.init()
for x in 0 .. 2:
for y in 0 .. 2:
subResults[x][y] = checkBoard(board[x][y])
return checkBoard(subResults)

View File

@@ -1,42 +0,0 @@
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()

View File

@@ -1,39 +0,0 @@
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}
"""