163 lines
3.9 KiB
Nim
163 lines
3.9 KiB
Nim
import hashes, options
|
|
import fixedseq
|
|
|
|
|
|
type
|
|
Color* = enum
|
|
cRed, cGreen, cBlue, cYellow, cPurple
|
|
|
|
ColorStack* = FixedSeq[5, Color, int8]
|
|
|
|
|
|
proc initColorStack*: ColorStack =
|
|
result.initFixedSeq
|
|
|
|
|
|
proc getAllColors: ColorStack =
|
|
var i = 0
|
|
for c in Color.low .. Color.high:
|
|
result[i] = c
|
|
|
|
const
|
|
allColors* = getAllColors()
|
|
colorNames: array[Color, string] =
|
|
["Red", "Green", "Blue", "Yellow", "Purple"]
|
|
colorAbbrevs: array[Color, char] = ['R', 'G', 'B', 'Y', 'P']
|
|
|
|
|
|
proc `$`*(c: Color): string =
|
|
result = colorNames[c]
|
|
|
|
|
|
proc abbrev*(c: Color): char =
|
|
result = colorAbbrevs[c]
|
|
|
|
|
|
proc `$`*(s: ColorStack): string =
|
|
result.add("St@[")
|
|
for i, color in s:
|
|
result.add($color)
|
|
if i < s.high:
|
|
result.add(", ")
|
|
result.add("]")
|
|
|
|
|
|
type
|
|
Die* = tuple[color: Color, value: int]
|
|
|
|
Tile* = enum
|
|
tBackward = -1,
|
|
tForward = 1
|
|
|
|
Square* = object
|
|
camels*: ColorStack
|
|
tile*: Option[Tile]
|
|
|
|
Board* = object
|
|
squares*: array[1..16, Square]
|
|
camels*: array[Color, range[1..16]]
|
|
diceRolled*: array[Color, bool]
|
|
winner*: Option[Color]
|
|
gameOver*: bool
|
|
initialized: bool
|
|
|
|
|
|
# use a template here for better inlining
|
|
template `[]`*[T](b: var Board, idx: T): var Square =
|
|
b.squares[idx]
|
|
|
|
# apparently we need separate ones for mutable and non-mutable
|
|
template `[]`*[T](b: Board, idx: T): Square =
|
|
b.squares[idx]
|
|
|
|
|
|
proc hash*(b: Board): Hash =
|
|
var h: Hash = 0
|
|
# there could be a tile anywhere so we have to check all squares
|
|
for i, sq in b.squares:
|
|
if sq.camels.len > 0 or sq.tile.isSome:
|
|
h = h !& i
|
|
if sq.tile.isSome:
|
|
h = h !& int(sq.tile.get) * 10 # so it isn't confused with a camel
|
|
else:
|
|
for c in sq.camels.asInt:
|
|
h = h !& c
|
|
result = !$h
|
|
|
|
|
|
proc init*(b: var Board) =
|
|
for sq in b.squares.mitems:
|
|
sq.camels.initFixedSeq
|
|
b.initialized = true
|
|
|
|
|
|
proc leader*(b: Board): Color =
|
|
let leadSquare = max(b.camels)
|
|
result = b[leadSquare].camels[^1]
|
|
|
|
|
|
proc display*(b: Board, start, stop: int) =
|
|
for i in start..stop:
|
|
let sq = b.squares[i]
|
|
let lead = $i & ": "
|
|
if sq.tile.isSome:
|
|
stdout.writeLine($lead & $sq.tile.get)
|
|
else:
|
|
stdout.writeLine($lead & $sq.camels)
|
|
echo ""
|
|
|
|
|
|
proc setState*(b: var Board;
|
|
camels: openArray[tuple[c: Color, p: int]];
|
|
tiles: openArray[tuple[t: Tile, p: int]]) =
|
|
for (color, dest) in camels: # note that `camels` is ordered, as this determines stacking
|
|
b[dest].camels.add(color)
|
|
b.camels[color] = dest
|
|
|
|
for (tile, dest) in tiles:
|
|
b[dest].tile = some(tile)
|
|
|
|
|
|
proc diceRemaining*(b: Board): ColorStack =
|
|
result.initFixedSeq
|
|
for color, isRolled in b.diceRolled:
|
|
if not isRolled: result.add(color)
|
|
|
|
|
|
proc resetDice*(b: var Board) =
|
|
for c in Color:
|
|
b.diceRolled[c] = false
|
|
|
|
|
|
proc advance*(b: var Board, die: Die) =
|
|
let
|
|
(color, roll) = die
|
|
startPos = b.camels[color]
|
|
var endPos = startPos + roll
|
|
|
|
if endPos > 16: # camel has passed the finish line
|
|
b.winner = some(b[startPos].camels[^1])
|
|
b.gameOver = true
|
|
return
|
|
|
|
var prepend = false
|
|
if b[endPos].tile.isSome: # adjust position (and possibly stacking) to account for tile
|
|
let t = b[endPos].tile.get
|
|
endPos += int(t)
|
|
if t == tBackward: prepend = true
|
|
|
|
let stackStart = b[startPos].camels.find(color)
|
|
if prepend:
|
|
b[startPos].camels.moveSubstackPre(b[endPos].camels, stackStart)
|
|
let stackLen = b[startPos].camels.len - stackStart
|
|
for i in 0 ..< stackLen:
|
|
# we know how many camels we added to the bottom, so set the position for each of those
|
|
b.camels[b[endPos].camels[i]] = endPos
|
|
else:
|
|
let dstPrevHigh = b[endPos].camels.high
|
|
b[startPos].camels.moveSubstack(b[endPos].camels, stackStart)
|
|
# the camels that have moved start immediately after the previous high camel
|
|
for i in (dstPrevHigh + 1) .. b[endPos].camels.high:
|
|
b.camels[b[endPos].camels[i]] = endPos
|
|
|
|
b.diceRolled[color] = true |