From f05ab2cfa2a3a6978925a5be7039273cc1351621 Mon Sep 17 00:00:00 2001 From: Joseph Montanaro Date: Wed, 24 Mar 2021 10:54:50 -0700 Subject: [PATCH] stochastic modeling for full games --- combinators.nim | 13 ++++++++++--- fixedseq.nim | 10 ++++++++-- game.nim | 21 ++++++++++++++++++--- main.nim | 30 ++++++++++++++++++++++++++---- 4 files changed, 62 insertions(+), 12 deletions(-) diff --git a/combinators.nim b/combinators.nim index 74b3814..3fc5a6e 100644 --- a/combinators.nim +++ b/combinators.nim @@ -1,4 +1,4 @@ -import sequtils +import random, sugar import fixedseq, game @@ -15,7 +15,7 @@ iterator allPermutations*(x: FixedSeq): FixedSeq = iterator allDigits*(lo, hi, size: Natural): auto = if size > 0: # otherwise we get an infinite loop - var digits: FixedSeq[5, int8, int8] + var digits: FixedSeq[5, int, int8] digits.initFixedSeq for i in 0 ..< size: digits.add(lo) @@ -41,4 +41,11 @@ iterator possibleFutures*(dice: FixedSeq): auto = var f = initFixedSeq(5, Die, int8) for i in 0 .. dice.high: f.add((perm[i], digits[i])) - yield f \ No newline at end of file + yield f + + +proc randomFuture*(dice: FixedSeq, r: var Rand): FixedSeq[5, Die, int8] = + result.initFixedSeq + let order = dice.dup(shuffle(r)) + for i, color in order: + result.add((color, r.rand(1..3))) diff --git a/fixedseq.nim b/fixedseq.nim index 468f0c5..d99bf01 100644 --- a/fixedseq.nim +++ b/fixedseq.nim @@ -1,4 +1,4 @@ -import algorithm +import algorithm, random type @@ -16,6 +16,11 @@ proc initFixedSeq*(s: var FixedSeq) = s.last = -1 +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.") @@ -98,7 +103,8 @@ proc nextPermutation*(s: var FixedSeq): bool = s.data.nextPermutation proc prevPermutation*(s: var FixedSeq): bool = s.data.prevPermutation -proc maxSize*(s: FixedSeq): FixedSeq.Pointer = s.data.len +proc shuffle*(s: var FixedSeq, r: var Rand) = + r.shuffle(s.data) proc moveSubstack*(src, dst: var FixedSeq; start: Natural) = diff --git a/game.nim b/game.nim index 3efa469..693d796 100644 --- a/game.nim +++ b/game.nim @@ -9,6 +9,18 @@ type 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() # compile-time evaluation + + proc `$`*(s: ColorStack): string = result.add("St@[") for i, color in s: @@ -38,9 +50,6 @@ type initialized: bool -const allDice = @[cRed, cGreen, cBlue, cYellow, cPurple] - - proc `[]`*[T](b: var Board, idx: T): var Square = b.squares[idx] @@ -90,6 +99,12 @@ proc setState*(b: var Board; b.leader = some(leadCamel) +proc diceRemaining*(b: Board): ColorStack = + result.initFixedSeq + for color, isRolled in b.diceRolled: + if not isRolled: result.add(color) + + proc resetDice*(b: var Board) = var d: array[Color, bool] b.diceRolled = d diff --git a/main.nim b/main.nim index c727d16..4d45357 100644 --- a/main.nim +++ b/main.nim @@ -1,4 +1,4 @@ -import math, options, sequtils, sets, sugar +import math, options, sequtils, random, sets import combinators, game, fixedseq @@ -41,7 +41,8 @@ proc projectOutcomes(b: Board, maxDepth = 1): ScoreSet = for o in outcomeStack[^1]: var o = o # make it mutable - o.resetDice # o was describina an end-of-leg state, so dice were exhausted + if outcomeStack.len > 1: + o.resetDice # o was describina an end-of-leg state, so dice were exhausted let projection = o.projectLeg result.update(projection[0]) @@ -52,11 +53,32 @@ proc projectOutcomes(b: Board, maxDepth = 1): ScoreSet = echo "\nDistinct end states: ", outcomeStack.mapIt(it.len).sum +proc randomGame(b: Board, r: var Rand): Color = + var projection = b + while true: + for roll in randomFuture(projection.diceRemaining, r): + projection.advance(roll) + if projection.gameOver: + return projection.leader.get + projection.resetDice + + +proc randomGames(b: Board, count: SomeInteger): ScoreSet = + randomize() + var r = initRand(rand(int64)) + for i in 0 ..< count: + let winner = b.randomGame(r) + inc result[winner] + if i mod 100_000 == 0: + stdout.write("simulated: " & $i & "\r") + echo "" + + var b: Board b.init b.display(1, 5) -b.setState({cGreen: 4, cYellow: 3, cPurple: 4, cBlue: 3, cRed: 4}, @[]) +b.setState({cGreen: 4, cYellow: 3, cPurple: 4, cBlue: 3, cRed: 5}, @[]) b.display(1, 5) # b.advance((cRed, 1)) @@ -70,7 +92,7 @@ b.display(1, 5) # echo s # echo s[2] -let r = b.projectOutcomes(2) +let r = b.randomGames(10_000_000) let total = r.sum for i, c in r: echo Color(i), ": ", (100 * c / total).round(2), "% (", c, " / ", total, ")"