From 2256b6d9bf3922195fbed854d728bc764045a404 Mon Sep 17 00:00:00 2001 From: Joseph Montanaro Date: Wed, 24 Mar 2021 00:18:53 -0700 Subject: [PATCH] refactor and start shifting to FixedSeq (currently broken) --- colors.nim | 139 ------------------------------------------------ combinators.nim | 24 +++++---- fixedseq.nim | 135 ++++++++++++++++++++++++++++++++++++++++++++++ game.nim | 133 +++++++++++++++++++++++++++++++++++++++++++++ main.nim | 132 +++++---------------------------------------- 5 files changed, 296 insertions(+), 267 deletions(-) delete mode 100644 colors.nim create mode 100644 fixedseq.nim create mode 100644 game.nim diff --git a/colors.nim b/colors.nim deleted file mode 100644 index 681142b..0000000 --- a/colors.nim +++ /dev/null @@ -1,139 +0,0 @@ -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.pieces[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 0b7b79f..74b3814 100644 --- a/combinators.nim +++ b/combinators.nim @@ -1,7 +1,8 @@ -import algorithm, sequtils +import sequtils +import fixedseq, game -iterator allPermutations*[T](x: seq[T]): seq[T] = +iterator allPermutations*(x: FixedSeq): FixedSeq = # returns all permutations of a given seq. Order is wonky but we don't care. var workingCopy = x yield workingCopy @@ -12,12 +13,12 @@ iterator allPermutations*[T](x: seq[T]): seq[T] = yield workingCopy -iterator allDigits*(lo, hi, size: Natural): seq[int] = +iterator allDigits*(lo, hi, size: Natural): auto = 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 digits: FixedSeq[5, int8, int8] + digits.initFixedSeq + for i in 0 ..< size: + digits.add(lo) var complete = false while not complete: @@ -32,9 +33,12 @@ iterator allDigits*(lo, hi, size: Natural): seq[int] = digits[i] = lo -iterator possibleFutures*[C](dice: seq[C]): seq[tuple[color: C, value: int]] = +iterator possibleFutures*(dice: FixedSeq): auto = # iterate over all possible sequences of die rolls. Each outcome # is returned as a 5-sequence of (color, number) tuples. for perm in dice.allPermutations: - for d in allDigits(1, 3, dice.len): - yield zip(perm, d) \ No newline at end of file + for digits in allDigits(1, 3, dice.len): + 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 diff --git a/fixedseq.nim b/fixedseq.nim new file mode 100644 index 0000000..468f0c5 --- /dev/null +++ b/fixedseq.nim @@ -0,0 +1,135 @@ +import algorithm + + +type + FixedSeq*[Idx: static int; Contents; Pointer: SomeSignedInt] = object + data: array[Idx, Contents] + last: Pointer + + +proc initFixedSeq*(size: static Natural; cType: typedesc; pType: typedesc[SomeSignedInt]): auto = + var s: FixedSeq[size, cType, pType] + s.last = -1 + result = s + +proc initFixedSeq*(s: var FixedSeq) = + s.last = -1 + + +proc `[]`*(s: var FixedSeq, i: Natural): var FixedSeq.Contents = + if i > s.last: + raise newException(IndexDefect, "index " & $i & " is out of bounds.") + s.data[i] + + +proc `[]`*(s: FixedSeq, i: BackwardsIndex): auto = + if s.last == -1: + raise newException(IndexDefect, "index out of bounds, the container is empty.") # matching stdlib again + s.data[s.last - typeof(s.last)(i) + 1] + + +proc `[]=`*(s: var FixedSeq, i: Natural, v: FixedSeq.Contents) = + if i > s.last: + raise newException(IndexDefect, "index " & $i & " is out of bounds.") + s.data[i] = v + + +proc high*(s: FixedSeq): auto = + result = s.last + + +proc low*(s: FixedSeq): auto = + result = case s.last + of -1: 0 # a bit weird but it's how the stdlib seq works + else: s.last + + +proc len*(s: FixedSeq): auto = + result = s.last + 1 + + +iterator items*(s: FixedSeq): auto = + for i in 0 .. s.last: + yield s.data[i] + + +iterator asInt*(s: FixedSeq): int8 = + for i in 0 .. s.last: + yield int8(s.data[i]) # now we do have to convert + + +iterator pairs*(s: FixedSeq): auto = + var count = 0 + for c in s: + yield (count, c) + inc count + + +proc add*(s: var FixedSeq, v: FixedSeq.Contents) = + let i = s.last + 1 + s.data[i] = v # will raise exception if out of bounds + s.last = i + + +proc insert*(s: var FixedSeq, v: FixedSeq.Contents, idx: Natural = 0) = + for i in countdown(s.last, typeof(s.last)(idx)): + swap(s.data[i], s.data[i + 1]) # will also raise exception if out of bounds + s.data[idx] = v + inc s.last + + +proc delete*(s: var FixedSeq, idx: Natural) = + if idx > s.last: + raise newException(IndexDefect, "index " & $idx & " is out of bounds.") + s.data[idx] = -1 + dec s.last + for i in typeof(s.last)(idx) .. s.last: + swap(s.data[i], s.data[i + 1]) + + +proc find*(s: FixedSeq, needle: FixedSeq.Contents): FixedSeq.Pointer = + for i, v in s.data: + if v == needle: + return i + return -1 + + +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 moveSubstack*(src, dst: var FixedSeq; start: Natural) = + var count: typeof(src.last) = 0 # have to track this separately apparently + for idx in start .. src.last: + swap(src.data[idx], dst.data[dst.last + 1 + count]) + inc count + dst.last += count + src.last -= count + + +proc moveSubstackPre*(src, dst: var FixedSeq; start: Natural) = + let ssLen = typeof(src.last)(src.last - start + 1) # length of substack + for i in countdown(dst.last, 0): + swap(dst.data[i], dst.data[i + ssLen]) + + var count = 0 + for i in start .. src.last: + swap(src.data[i], dst.data[count]) + inc count + + 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 new file mode 100644 index 0000000..3efa469 --- /dev/null +++ b/game.nim @@ -0,0 +1,133 @@ +import hashes, options +import fixedseq + + +type + Color* = enum + cRed, cGreen, cBlue, cYellow, cPurple + + ColorStack* = FixedSeq[5, Color, int8] + + +proc `$`*(s: ColorStack): string = + result.add("St@[") + for i, color in s: + result.add($color) + if i < s.high: + result.add(", ") + result.add("]") + + +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 + + +const allDice = @[cRed, cGreen, cBlue, cYellow, cPurple] + + +proc `[]`*[T](b: var Board, idx: T): var Square = + b.squares[idx] + + +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: + if sq.camels.len > 0 or sq.tile.isSome: + h = h !& i + 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.asInt: + h = h !& c + result = !$h + + +proc init*(b: var Board) = + for sq in b.squares.mitems: + sq.camels.initFixedSeq + b.initialized = true + + +proc display*(b: Board, start, stop: int) = + for i in start..stop: + let sq = b.squares[i] + let lead = $i & ": " + if sq.tile.isSome: + echo lead, sq.tile.get + else: + echo lead, sq.camels + 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 + b[dest].camels.add(color) + b.camels[color] = dest + + for (tile, dest) in tiles: + b[dest].tile = some(tile) + + let leadCamel = b[max(b.camels)].camels[^1] # top camel in the last currently-occupied space + 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 + startPos = b.camels[color] + var endPos = startPos + roll + + if endPos > 16: # camel has passed the finish line + b.leader = some(b[startPos].camels[^1]) + b.gameOver = true + return + + var prepend = false + if b[endPos].tile.isSome: # adjust position (and possibly stacking) to account for tile + let t = b[endPos].tile.get + endPos += int(t) + if t == tBackward: prepend = true + + 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 \ No newline at end of file diff --git a/main.nim b/main.nim index d7e271b..c727d16 100644 --- a/main.nim +++ b/main.nim @@ -1,29 +1,8 @@ -import math, hashes, options, tables, sequtils, sets, sugar -import combinators, colors - - -const allDice = @[cRed, cGreen, cBlue, cYellow, cPurple] +import math, options, sequtils, sets, sugar +import combinators, game, fixedseq 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]] @@ -34,105 +13,14 @@ proc update*(scores: var ScoreSet, toAdd: ScoreSet) = scores[i] += s -proc `[]`*[T](b: var Board, idx: T): var Square = - b.squares[idx] - - -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: - if sq.camels.len > 0 or sq.tile.isSome: - h = h !& i - 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.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] - let lead = $i & ": " - if sq.tile.isSome: - echo lead, sq.tile.get - else: - echo lead, sq.camels - 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 - b[dest].camels.add(color) - b.camels[color] = dest - - for (tile, dest) in tiles: - b[dest].tile = some(tile) - - let leadCamel = b[max(b.camels)].camels[^1] # top camel in the last currently-occupied space - 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 - startPos = b.camels[color] - var endPos = startPos + roll - - if endPos > 16: # camel has passed the finish line - b.leader = some(b[startPos].camels[^1]) - b.gameOver = true - return - - var prepend = false - if b[endPos].tile.isSome: # adjust position (and possibly stacking) to account for tile - let t = b[endPos].tile.get - endPos += int(t) - if t == tBackward: prepend = true - - 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 - - proc projectLeg(b: Board): LegResults = var scores: ScoreSet var endStates: HashSet[Board] - let diceRemaining = collect(newSeq): - for i, c in b.diceRolled: - if not c: i + var diceRemaining: ColorStack + diceRemaining.initFixedSeq + for i, c in b.diceRolled: + if not c: diceRemaining.add(i) for future in possibleFutures(diceRemaining): var prediction = b # make a copy @@ -174,6 +62,14 @@ b.display(1, 5) # b.advance((cRed, 1)) # b.display(1, 5) +# var s: ColorStack +# s.initFixedSeq +# for i in 0..4: +# s.add(Color(i)) + +# echo s +# echo s[2] + let r = b.projectOutcomes(2) let total = r.sum for i, c in r: