From e5e2f30045fb2494abbe03d376ca1f383dfbae8b Mon Sep 17 00:00:00 2001 From: Joseph Montanaro Date: Sun, 21 Mar 2021 20:59:45 -0700 Subject: [PATCH] move to fixed-size arrays instead of sequences --- colors.nim | 139 ++++++++++++++++++++++++++++++++++++++++++++++++ combinators.nim | 33 ++++++------ main.nim | 116 ++++++++++++++++++++++++---------------- 3 files changed, 225 insertions(+), 63 deletions(-) create mode 100644 colors.nim diff --git a/colors.nim b/colors.nim new file mode 100644 index 0000000..e22b286 --- /dev/null +++ b/colors.nim @@ -0,0 +1,139 @@ +import strutils + + +type + Color* = enum + cRed, cGreen, cBlue, cYellow, cPurple + + ColorStack* = object + pieces: array[5, int8] + last: int8 + + +proc init*(s: var ColorStack) = + for i in 0..4: + s.pieces[i] = -1 + s.last = -1 + + +proc `[]`*(s: ColorStack, i: Natural): Color = + if i > s.last: + raise newException(IndexDefect, "index " & $i & " is out of bounds.") + result = Color(s.pieces[i]) + + +proc `[]`*(s: ColorStack, i: BackwardsIndex): Color = + if s.last == -1: + raise newException(IndexDefect, "index out of bounds, the container is empty.") # matching stdlib again + result = Color(s.pieces[s.last - int8(i) + 1]) + + +proc `[]=`*(s: var ColorStack, i: Natural, v: Color) = + if i > s.last: + raise newException(IndexDefect, "index " & $i & " is out of bounds.") + s.pieces[i] = int8(v) + + +proc `$`*(s: ColorStack): string = + result.add("St@[") + for i in 0 .. s.last: + let c = Color(s.pieces[i]) + result.add($c) + if i < s.last: + result.add(", ") + result.add("]") + + +proc high*(s: ColorStack): int8 = + result = s.last + + +proc low*(s: ColorStack): int8 = + result = case s.last + of -1: 0 # a bit weird but it's how the stdlib seq works + else: s.last + + +proc len*(s: ColorStack): int8 = + result = s.last + 1 + + +iterator items*(s: ColorStack): Color = + for i in 0 .. s.last: + yield Color(s[i]) + + +iterator asInt*(s: ColorStack): int8 = + for i in 0 .. s.last: + yield s.pieces[i] # no conversion, should speed up hashing? maybe + + +iterator pairs*(s: ColorStack): auto = + var count = 0 + for c in s: + yield (count, c) + inc count + + +proc add*(s: var ColorStack, c: Color) = + let i = s.last + 1 + s.pieces[i] = int8(c) # will raise exception if out of bounds + s.last = i + + +proc insert*(s: var ColorStack, c: Color, idx: Natural = 0) = + for i in countdown(s.last, int8(idx)): + swap(s.pieces[i], s.pieces[i + 1]) # will also raise exception if out of bounds + s.pieces[idx] = int8(c) + inc s.last + + +proc delete*(s: var ColorStack, idx: Natural) = + if idx > s.last: + raise newException(IndexDefect, "index " & $idx & " is out of bounds.") + s.pieces[idx] = -1 + dec s.last + for i in int8(idx) .. s.last: + swap(s.pieces[i], s.pieces[i + 1]) + + +proc find(s: ColorStack, c: Color): int8 = + let needle = int(c) + for i, v in s.pieces: + if v == needle: + return i + return -1 + + +proc moveSubstack*(src, dst: var ColorStack; start: Natural) = + var count = 0 # have to track this separately apparently + for idx in (int8(start) .. src.last): + swap(src.pieces[idx], dst.pieces[dst.last + 1 + count]) + inc count + dst.last += int8(count) + src.last -= int8(count) + + +proc moveSubstackPre*(src, dst: var ColorStack; start: Natural) = + let ssLen = src.last - start + 1 # length of substack + for i in countdown(dst.last, 0): + swap(dst.pieces[i], dst.pieces[i + ssLen]) + + var count = 0 + for i in start .. src.last: + swap(src.pieces[i], dst.pieces[count]) + inc count + + dst.last += int8(ssLen) + src.last -= int8(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/combinators.nim b/combinators.nim index effbbf7..0b7b79f 100644 --- a/combinators.nim +++ b/combinators.nim @@ -12,23 +12,24 @@ iterator allPermutations*[T](x: seq[T]): seq[T] = yield workingCopy -iterator allDigits*(lo, hi, size: int): seq[int] = - # we use uninitialized since we will initialize it below, but not necessarily with 0s - var digits = newSeqUninitialized[int](size) - for i in 0 .. digits.high: - digits[i] = lo +iterator allDigits*(lo, hi, size: Natural): seq[int] = + if size > 0: # otherwise we get an infinite loop + # we use uninitialized since we will initialize it below, but not necessarily with 0s + var digits = newSeqUninitialized[int](size) + for i in 0 .. digits.high: + digits[i] = lo - var complete = false - while not complete: - yield digits - for i in countdown(digits.high, 0): - if digits[i] < hi: - inc digits[i] - break - elif i == 0: # since this is the last digit to be incremented, we must be done - complete = true - else: - digits[i] = lo + var complete = false + while not complete: + yield digits + for i in countdown(digits.high, 0): + if digits[i] < hi: + inc digits[i] + break + elif i == 0: # since this is the last digit to be incremented, we must be done + complete = true + else: + digits[i] = lo iterator possibleFutures*[C](dice: seq[C]): seq[tuple[color: C, value: int]] = diff --git a/main.nim b/main.nim index 545207d..d7e271b 100644 --- a/main.nim +++ b/main.nim @@ -1,46 +1,44 @@ import math, hashes, options, tables, sequtils, sets, sugar -import combinators - - -type - Color = enum - cRed, cGreen, cBlue, cYellow, cPurple - - Tile = enum - tBackward = -1, - tForward = 1 - - Square = object - camels: seq[Color] - tile: Option[Tile] - - Die = tuple[color: Color, value: int] - - ScoreSet = array[Color, int] - - LegResults = tuple[scores: ScoreSet, endStates: HashSet[Board]] - - Board = object - squares: array[1..16, Square] - camels: array[Color, range[1..16]] - diceRolled: array[Color, bool] - leader: Option[Color] - gameOver: bool +import combinators, colors const allDice = @[cRed, cGreen, cBlue, cYellow, cPurple] -proc update(scores: var ScoreSet, toAdd: ScoreSet) = +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] + leader: Option[Color] + gameOver: bool + initialized: bool + + ScoreSet* = array[Color, int] + + LegResults* = tuple[scores: ScoreSet, endStates: HashSet[Board]] + + +proc update*(scores: var ScoreSet, toAdd: ScoreSet) = for i, s in toAdd: scores[i] += s -proc `[]`[T](b: var Board, idx: T): var Square = +proc `[]`*[T](b: var Board, idx: T): var Square = b.squares[idx] -proc hash(b: Board): Hash = +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: @@ -49,11 +47,17 @@ proc hash(b: Board): Hash = 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: - h = h !& int(c) + for c in sq.camels.asInt: + h = h !& c result = !$h +proc init*(b: var Board) = + for sq in b.squares.mitems: + sq.camels.init + b.initialized = true + + proc display(b: Board, start, stop: int) = for i in start..stop: let sq = b.squares[i] @@ -79,6 +83,11 @@ proc setState(b: var Board; b.leader = some(leadCamel) +proc resetDice(b: var Board) = + var d: array[Color, bool] + b.diceRolled = d + + proc advance(b: var Board, die: Die) = let (color, roll) = die @@ -96,21 +105,24 @@ proc advance(b: var Board, die: Die) = endPos += int(t) if t == tBackward: prepend = true - for i, c in b[startPos].camels: - if c == color: - let subStack = b[startPos].camels[i .. ^1] - if prepend: - b[endPos].camels.insert(subStack) - else: - b[endPos].camels.add(subStack) - - b[startPos].camels[i .. ^1] = @[] - for moved in subStack: - b.camels[moved] = endPos - # if we are stacking on or moving past the previous leader - if endPos >= b.camels[b.leader.get]: - b.leader = some(b[endPos].camels[^1]) - break # breaking the outer loop here, not the inner - but only conditionally! gah! + 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 + + # if we are stacking on or moving past the previous leader + if endPos >= b.camels[b.leader.get]: + b.leader = some(b[endPos].camels[^1]) + b.diceRolled[color] = true @@ -129,14 +141,20 @@ proc projectLeg(b: Board): LegResults = inc scores[prediction.leader.get] # deduplicate results endStates.incl(prediction) + result = (scores, endStates) proc projectOutcomes(b: Board, maxDepth = 1): ScoreSet = var outcomeStack = @[ [b].toHashSet ] for depth in 1..maxDepth: + echo "simulating ", outcomeStack[^1].len, " possible legs." var endStates: HashSet[Board] + 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 + let projection = o.projectLeg result.update(projection[0]) endStates.incl(projection[1]) @@ -147,11 +165,15 @@ proc projectOutcomes(b: Board, maxDepth = 1): ScoreSet = var b: Board +b.init b.display(1, 5) b.setState({cGreen: 4, cYellow: 3, cPurple: 4, cBlue: 3, cRed: 4}, @[]) b.display(1, 5) +# b.advance((cRed, 1)) +# b.display(1, 5) + let r = b.projectOutcomes(2) let total = r.sum for i, c in r: