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

42
.gitignore vendored Normal file
View File

@@ -0,0 +1,42 @@
# Created by https://www.gitignore.io/api/nim,visualstudiocode
# Edit at https://www.gitignore.io/?templates=nim,visualstudiocode
### Nim ###
nimcache/
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
# End of https://www.gitignore.io/api/nim,visualstudiocode
# Created by https://www.gitignore.io/api/nim,visualstudiocode
# Edit at https://www.gitignore.io/?templates=nim,visualstudiocode
### Nim ###
nimcache/
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# Ignore all
*
# Unignore all with extensions
!*.*
# Unignore all dirs
!*/

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}
"""

View File

@@ -1,49 +0,0 @@
# This is just an example to get you started. You may wish to put all of your
# tests into a single file, or separate them into multiple `test1`, `test2`
# etc. files (better names are recommended, just make sure the name starts with
# the letter 't').
#
# To run these tests, simply execute `nimble test`.
import unittest
import libmttt
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

159
tests/testChecks.nim Normal file
View File

@@ -0,0 +1,159 @@
# This is just an example to get you started. You may wish to put all of your
# tests into a single file, or separate them into multiple `test1`, `test2`
# etc. files (better names are recommended, just make sure the name starts with
# the letter 't').
#
# To run these tests, simply execute `nimble test`.
import unittest
import libmttt
suite "Test the board result checker":
setup:
var
player1 = Player(name: "Max")
player2 = Player(name: "Adam")
state: GameState = newGame(player1, player2)
test "winning row":
state.board[0][0] = [
[mFree, mFree, mFree],
[mPlayer1, mPlayer1, mPlayer1],
[mFree, mFree, mFree]
]
check checkBoard(state.board[0][0]) == mPlayer1
test "winning column":
state.board[0][0] = [
[mPlayer2, mFree, mPlayer1],
[mPlayer2, mPlayer1, mFree],
[mPlayer2, mPlayer1, mFree]
]
check checkBoard(state.board[0][0]) == mPlayer2
test "winning diagonals":
state.board[0][0] = [
[mFree, mFree, mPlayer2],
[mFree, mPlayer2, mFree],
[mPlayer2, mFree, mFree]
]
check(checkBoard(state.board[0][0]) == mPlayer2)
state.board[0][0] = [
[mPlayer1, mPlayer2, mPlayer2],
[mPlayer2, mPlayer1, mPlayer1],
[mPlayer2, mFree, mPlayer1]
]
check(checkBoard(state.board[0][0]) == mPlayer1)
test "board is a draw":
state.board[0][0] = [
[mPlayer1, mPlayer2, mPlayer1],
[mPlayer2, mPlayer1, mPlayer1],
[mPlayer2, mPlayer1, mPlayer2]
]
check checkBoard(state.board[0][0]) == mDraw
test "board is open":
state.board[0][0] = [
[mPlayer1, mPlayer2, mFree],
[mPlayer1, mFree, mPlayer1],
[mFree, mPlayer1, mPlayer2]
]
check checkBoard(state.board[0][0]) == mFree
test "free inital metaboard":
check checkBoard(state.board) == mFree
test "winning metaboard row":
state.board[1][0] = [
[mPlayer1, mPlayer2, mFree],
[mPlayer1, mPlayer1, mPlayer1],
[mFree, mPlayer1, mPlayer2]
]
state.board[1][1] = [
[mFree, mFree, mPlayer1],
[mFree, mPlayer1, mFree],
[mPlayer1, mFree, mFree]
]
state.board[1][2] = [
[mFree, mFree, mPlayer1],
[mFree, mFree, mPlayer1],
[mFree, mFree, mPlayer1]
]
check checkBoard(state.board) == mPlayer1
test "winning metaboard column":
state.board[0][1] = [
[mPlayer1, mPlayer2, mFree],
[mPlayer1, mPlayer1, mPlayer1],
[mFree, mPlayer1, mPlayer2]
]
state.board[1][1] = [
[mFree, mFree, mFree],
[mPlayer1, mPlayer1, mPlayer1],
[mFree, mFree, mFree]
]
state.board[2][1] = [
[mPlayer1, mFree, mFree],
[mPlayer1, mFree, mFree],
[mPlayer1, mFree, mFree]
]
check checkBoard(state.board) == mPlayer1
test "winning metaboard diagonal":
state.board[0][0] = [
[mPlayer2, mPlayer2, mFree],
[mPlayer2, mPlayer2, mPlayer2],
[mFree, mPlayer2, mPlayer2]
]
state.board[1][1] = [
[mFree, mFree, mFree],
[mPlayer2, mPlayer2, mPlayer2],
[mFree, mFree, mFree]
]
state.board[2][2] = [
[mPlayer2, mFree, mFree],
[mPlayer2, mFree, mFree],
[mPlayer2, mFree, mFree]
]
check checkBoard(state.board) == mPlayer2
test "winning metaboard with some boards in draw":
let winner = [
[mFree, mFree, mFree],
[mPlayer2, mPlayer2, mPlayer2],
[mFree, mFree, mFree]
]
let drawn = [
[mPlayer2, mPlayer2, mPlayer1],
[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
test "metaboard is drawn":
let drawn = [
[mPlayer2, mPlayer2, mPlayer1],
[mPlayer1, mPlayer1, mPlayer2],
[mPlayer2, mPlayer1, mPlayer2]
]
for x in 0 .. 2:
for y in 0 .. 2:
state.board[x][y] = drawn
check checkBoard(state.board) == mDraw
test "illegal situation: both players win board":
state.board[1][1] = [
[mPlayer1, mPlayer1, mPlayer1],
[mPlayer2, mPlayer2, mPlayer2],
[mFree, mFree, mFree]
]
expect(Exception):
discard checkBoard(state.board)

0
tests/testMoves.nim Normal file
View File