diff --git a/combinators.nim b/combinators.nim index 3fc5a6e..903d1a7 100644 --- a/combinators.nim +++ b/combinators.nim @@ -1,7 +1,52 @@ -import random, sugar +import algorithm, random, sugar import fixedseq, game +proc nextPermutation(x: var FixedSeq): bool = + # copied shamelessly from std/algorithm.nim + if x.len < 2: + return false + + var i = x.high + while i > 0 and x[i - 1] >= x[i]: + dec i + + if i == 0: + return false + + var j = x.high + while j >= i and x[j] <= x[i - 1]: + dec j + + swap x[j], x[i - 1] + x.reverse(i, x.high) + + result = true + + +proc prevPermutation(x: var FixedSeq): bool = + # copied shamelessly from std/algorithm.nim + if x.len < 2: + return false + + var i = x.high + while i > 0 and x[i - 1] <= x[i]: + dec i + + if i == 0: + return false + + x.reverse(i, x.high) + + var j = x.high + while j >= i and x[j - 1] < x[i - 1]: + dec j + + swap x[i - 1], x[j] + + result = true + + iterator allPermutations*(x: FixedSeq): FixedSeq = # returns all permutations of a given seq. Order is wonky but we don't care. var workingCopy = x diff --git a/fixedseq.nim b/fixedseq.nim index d99bf01..4bdc88c 100644 --- a/fixedseq.nim +++ b/fixedseq.nim @@ -1,4 +1,4 @@ -import algorithm, random +import random type @@ -16,11 +16,21 @@ proc initFixedSeq*(s: var FixedSeq) = s.last = -1 +proc `$`*(s: FixedSeq): string = + result.add("FixedSeq[") + for i, item in s: + if i != 0: + result.add(", ") + result.add($item) + result.add("]") + + proc `[]`*(s: FixedSeq, i: Natural): FixedSeq.Contents = if i > s.last: raise newException(IndexDefect, "index " & $i & " is out of bounds.") s.data[i] + proc `[]`*(s: var FixedSeq, i: Natural): var FixedSeq.Contents = if i > s.last: raise newException(IndexDefect, "index " & $i & " is out of bounds.") @@ -99,9 +109,15 @@ proc find*(s: FixedSeq, needle: FixedSeq.Contents): FixedSeq.Pointer = return -1 -proc nextPermutation*(s: var FixedSeq): bool = s.data.nextPermutation +proc reverse*(s: var FixedSeq; first, last: Natural) = + # copied shamelessly from std/algorithm.nim + var x = first + var y = last + while x < y: + swap(s[x], s[y]) + inc x + dec y -proc prevPermutation*(s: var FixedSeq): bool = s.data.prevPermutation proc shuffle*(s: var FixedSeq, r: var Rand) = r.shuffle(s.data) @@ -128,14 +144,3 @@ proc moveSubstackPre*(src, dst: var FixedSeq; start: Natural) = dst.last += ssLen src.last -= ssLen - - -# proc display(s: ColorStack) = -# var p: seq[string] -# for i in s.pieces: -# if i == -1: -# p.add("none") -# else: -# p.add($Color(i)) -# echo "pieces: @[", p.join(", "), "], last: ", s.last -# echo "len: ", s.len, "\n" diff --git a/game.nim b/game.nim index a295a40..4b4776b 100644 --- a/game.nim +++ b/game.nim @@ -18,7 +18,13 @@ proc getAllColors: ColorStack = for c in Color.low .. Color.high: result[i] = c -const allColors* = getAllColors() # compile-time evaluation +const allColors* = getAllColors() +const colorNames: array[Color, string] = + ["Red", "Green", "Blue", "Yellow", "Purple"] + + +proc `$`*(c: Color): string = + result = colorNames[c] proc `$`*(s: ColorStack): string = @@ -87,8 +93,8 @@ proc display*(b: Board, start, stop: int) = proc setState*(b: var Board; - camels: openArray[tuple[c: Color, p: int]]; - tiles: openArray[tuple[t: Tile, p: int]]) = + 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 @@ -107,8 +113,8 @@ proc diceRemaining*(b: Board): ColorStack = proc resetDice*(b: var Board) = - var d: array[Color, bool] - b.diceRolled = d + for c, rolled in b.diceRolled: + b.diceRolled[c] = false proc advance*(b: var Board, die: Die) = diff --git a/main.nim b/main.nim index a301253..24ac901 100644 --- a/main.nim +++ b/main.nim @@ -1,5 +1,5 @@ import math, options, sequtils, random, sets -import combinators, game, fixedseq +import combinators, game, fixedseq, ui type @@ -17,7 +17,13 @@ proc update*(scores: var ScoreSet, toAdd: ScoreSet) = scores[i] += s -proc projectLeg(b: Board): LegResults = +proc display*(scores: ScoreSet) = + let total = scores.sum + for color, score in scores: + echo color, ": ", round(100 * scores[color] / total, 2), '%' + + +proc projectLeg*(b: Board): LegResults = var scores: ScoreSet var endStates: HashSet[Board] @@ -93,42 +99,12 @@ proc randomSpread(b: Board, nTests: SomeInteger, nSamples: SomeInteger): ScoreSp result.hi[color] = pct -var b: Board -b.init - -randomize() -var r = initRand(rand(int64)) - -var rolls: array[5, tuple[c: Color, p: int]] -for i, roll in randomFuture(b.diceRemaining, r): - rolls[i] = (roll[0], roll[1] + 1) - -b.setState(rolls, @[]) -echo "Starting state:" -b.display(1, 5) - -# block outer: -# while true: -# for roll in randomFuture(b.diceRemaining, r): -# b.advance(roll) -# if b.gameOver: -# echo "last roll: ", roll -# break outer -# b.resetDice - -# echo "winner: ", b.leader.get -# b.display(min(b.camels) - 1, 16) - -# let scores = b.randomGames(10_000_000) -# let total = scores.sum -# for i, c in scores: -# echo Color(i), ": ", (100 * c / total).round(2), "% (", c, " / ", total, ")" - -for i in 0..3: - let samples = 10 ^ (i + 3) - echo "Simulating ", samples, " games 100 times" - let spread = b.randomSpread(100, samples) - for color, lo in spread.lo: - stdout.write($color & ": " & $lo.round(4) & "-" & $spread.hi[color].round(4) & ", ") - stdout.flushFile - echo "\n" +when isMainModule: + let config = parseArgs() + var b: Board + b.init + b.setState(config.state, []) + b.diceRolled = config.diceRolled + b.display(1, 5) + let scores = b.projectLeg()[0] + scores.display diff --git a/ui.nim b/ui.nim new file mode 100644 index 0000000..3ca9636 --- /dev/null +++ b/ui.nim @@ -0,0 +1,65 @@ +import os, strutils +import game + + +const help = + """cup - Probability calculator for the board game CamelUp + +Usage: + cup [-i] SPACE:STACK [...SPACE:STACK] [DICE] + +SPACE refers to a numbered board space (1-16). +STACK refers to a stack of camel colors from bottom to top, e.g. + YBR (Yellow, Blue, Red, with Red on top). +DICE refers to the set of dice that have already been rolled, + e.g. GPR (Green, Purple, Red) + +Options: + -i Interactive mode (currently unimplemented) + -h Show this message and exit +""" + +type + CmdConfig* = object + state*: seq[tuple[c: Color, p: int]] + interactive*: bool + diceRolled*: array[Color, bool] + + +proc parseColor(c: char): Color = + case c: + of 'R', 'r': + return cRed + of 'G', 'g': + return cGreen + of 'B', 'b': + return cBlue + of 'Y', 'y': + return cYellow + of 'P', 'p': + return cPurple + else: + raise newException(ValueError, "Invalid camel color specified.") + + +proc parseArgs*(): CmdConfig = + for p in os.commandLineParams(): + if p == "-h": + echo help + quit 0 + elif p == "-i": + result.interactive = true + elif result.state.len < 5: + let splat = p.split(':') + + let sq = splat[0] + let square = sq.parseInt + + let colors = splat[1] + for c in colors: + let color = parseColor(c) + result.state.add((color, square)) + else: + for c in p: + let color = parseColor(c) + result.diceRolled[color] = true