From 4995a9388ba1bcee58be3f9be2a071af711ccd35 Mon Sep 17 00:00:00 2001 From: Joseph Montanaro Date: Mon, 1 Nov 2021 15:44:21 -0700 Subject: [PATCH] fix game state representation --- config.nims | 3 +-- cup.nim | 3 +-- fixedseq.nim | 9 ++++++++- game.nim | 45 ++++++++++++++++++++++++++++++++++++++++----- simulation.nim | 5 ++++- test.nim | 13 ++++++++----- ui.nim | 17 ++++++++++------- 7 files changed, 72 insertions(+), 23 deletions(-) diff --git a/config.nims b/config.nims index cf6a8b0..9934587 100644 --- a/config.nims +++ b/config.nims @@ -1,5 +1,4 @@ --threads: on --d: release +--d: lto --opt: speed ---passC: -flto ---passL: -flto diff --git a/cup.nim b/cup.nim index 85c86c7..3c9c12e 100644 --- a/cup.nim +++ b/cup.nim @@ -5,8 +5,7 @@ when isMainModule: let config = parseArgs() var b: Board b.init - b.setState(config.state, []) - b.diceRolled = config.diceRolled + b.setState(config.state) let legScores = b.getLegScores let gameScores = b.randomGames(1_000_000) diff --git a/fixedseq.nim b/fixedseq.nim index 879c5e3..fa5ed1b 100644 --- a/fixedseq.nim +++ b/fixedseq.nim @@ -101,12 +101,16 @@ proc delete*(s: var FixedSeq, idx: Natural) = when not defined(danger): if idx > s.last: raise newException(IndexDefect, "index " & $idx & " is out of bounds.") - s.data[idx] = -1 + s.data[idx] = -1 # do we even need this? dec s.last for i in typeof(s.last)(idx) .. s.last: swap(s.data[i], s.data[i + 1]) +proc clear*(s: var FixedSeq) = + s.last = -1 + + proc find*(s: FixedSeq, needle: FixedSeq.Contents): FixedSeq.Pointer = for i, v in s.data: if v == needle: @@ -127,6 +131,9 @@ proc reverse*(s: var FixedSeq; first, last: Natural) = proc shuffle*(s: var FixedSeq, r: var Rand) = r.shuffle(s.data) +proc shuffle*(s: var FixedSeq) = + shuffle(s.data) + proc moveSubstack*(src, dst: var FixedSeq; start: Natural) = var count: typeof(src.last) = 0 # have to track this separately apparently diff --git a/game.nim b/game.nim index 0cbfa8c..d9f1f83 100644 --- a/game.nim +++ b/game.nim @@ -53,6 +53,11 @@ type camels*: ColorStack tile*: Option[Tile] + GameState* = object + dice*: array[Color, bool] + camels*: FixedSeq[5, tuple[c: Color, p: range[1..16]], int8] + tiles*: FixedSeq[8, tuple[t: Tile, p: range[1..16]], int8] # max 8 players, so max 8 tiles + Board* = object squares*: array[1..16, Square] camels*: array[Color, range[1..16]] @@ -62,6 +67,14 @@ type initialized: bool +proc init*(state: var GameState) = + state.camels.initFixedSeq + state.tiles.initFixedSeq + +proc newGameState*(): GameState = + result.init + + # use a template here for better inlining template `[]`*[T](b: var Board, idx: T): var Square = b.squares[idx] @@ -107,16 +120,38 @@ proc display*(b: Board, start, stop: int) = 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 +proc setState*(b: var Board; state: GameState) = + for color, pos in b.camels: + if pos > 0: + b[pos].camels.clear() + + for (color, dest) in state.camels: # note that `camels` is ordered, as this determines stacking b[dest].camels.add(color) b.camels[color] = dest - for (tile, dest) in tiles: + for (tile, dest) in state.tiles: b[dest].tile = some(tile) + b.diceRolled = state.dice + + +proc getState*(b: Board): GameState = + result.init + var camelCount = 0 + let start = min(b.camels) + for pos in start .. b.squares.high: + let sq = b[pos] + for color in sq.camels: + result.camels.add((c: color, p: pos)) + camelCount += 1 + + if sq.tile.isSome: + result.tiles.add((t: sq.tile.get, p: pos)) + if camelCount >= 5: + break + + result.dice = b.diceRolled + proc diceRemaining*(b: Board): ColorStack = result.initFixedSeq diff --git a/simulation.nim b/simulation.nim index 2ab8f9a..6179af1 100644 --- a/simulation.nim +++ b/simulation.nim @@ -43,11 +43,14 @@ iterator legEndStates(b: Board): Board = for i, c in b.diceRolled: if not c: diceRemaining.add(i) + let origState = b.getState + var prediction = b for future in possibleFutures(diceRemaining): - var prediction = b # make a copy so we can mutate + # var prediction = b # make a copy so we can mutate for dieRoll in future: prediction.advance(dieRoll) yield prediction + prediction.setState(origState) proc getLegScores*(b: Board): ScoreSet = diff --git a/test.nim b/test.nim index 1c48ef0..8b2b73f 100644 --- a/test.nim +++ b/test.nim @@ -39,12 +39,15 @@ proc summarize(tr: TestResults, opname = "operations") = proc newRandomGame(): Board = randomize() - var dice: array[5, tuple[c: Color, p: int]] - for i in 0 .. 4: - dice[i] = (Color(i), rand(1..3)) - result.init - result.setState(dice, []) + + var state = newGameState() + for i in 0 .. 4: + let pos = rand(1..3) + state.camels.add((c: Color(i), p: pos)) + state.camels.shuffle() + + result.setState(state) template executionTime(body: untyped): Duration = diff --git a/ui.nim b/ui.nim index 2a004b3..81b105f 100644 --- a/ui.nim +++ b/ui.nim @@ -16,7 +16,7 @@ const help = block: type CmdConfig* = object - state*: seq[tuple[c: Color, p: int]] + state*: GameState interactive*: bool diceRolled*: array[Color, bool] @@ -34,17 +34,18 @@ proc parseColor(c: char): Color = of 'P', 'p': return cPurple else: - raise newException(ValueError, "Invalid camel color specified.") + raise newException(ValueError, "Invalid camel color specified: " & c) proc parseArgs*(): CmdConfig = + result.state.init() for p in os.commandLineParams(): if p == "-h": echo help quit 0 elif p == "-i": result.interactive = true - elif result.state.len < 5: + elif result.state.camels.len < 5: let splat = p.split(':') let sq = splat[0] @@ -53,11 +54,14 @@ proc parseArgs*(): CmdConfig = let colors = splat[1] for c in colors: let color = parseColor(c) - result.state.add((color, square)) + result.state.camels.add((c: color, p: square)) else: for c in p: let color = parseColor(c) - result.diceRolled[color] = true + result.state.dice[color] = true + + if result.state.camels.len != 5: + raise newException(ValueError, "Please specify positions for all camels.") # ========================== @@ -98,7 +102,6 @@ proc showSpaces*(b: Board; start, stop: Natural): string = proc showPercents*(scores: ScoreSet): string = var lines: array[5, string] for color, pct in scores.percents: - let label = align($color, 7) # e.g. " Green" var bar = repeat(" ", 20) let percentage = round(pct * 100, 2) # populate the progress bar @@ -106,5 +109,5 @@ proc showPercents*(scores: ScoreSet): string = for i in 0 ..< barFill: bar[i] = '=' - lines[int(color)] = fmt"{label}: [{bar}] {percentage}%" + lines[int(color)] = fmt"{color:>7}: [{bar}] {percentage:5.2f}%" result = lines.join("\n")