add (very) basic CLI

This commit is contained in:
Joseph Montanaro 2021-07-12 15:46:06 -07:00
parent b4c31a7a68
commit 6f694f99ed
5 changed files with 158 additions and 61 deletions

View File

@ -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

View File

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

View File

@ -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 =
@ -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) =

View File

@ -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

65
ui.nim Normal file
View File

@ -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