Add diagonal and metaboard checking.
This commit is contained in:
42
.gitignore
vendored
Normal file
42
.gitignore
vendored
Normal 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
|
||||
!*/
|
||||
109
src/libmttt.nim
109
src/libmttt.nim
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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}
|
||||
"""
|
||||
@@ -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
159
tests/testChecks.nim
Normal 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
0
tests/testMoves.nim
Normal file
Reference in New Issue
Block a user