move to fixed-size arrays instead of sequences

This commit is contained in:
Joseph Montanaro 2021-03-21 20:59:45 -07:00
parent e0f83cdca1
commit e5e2f30045
3 changed files with 225 additions and 63 deletions

139
colors.nim Normal file
View File

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

View File

@ -12,23 +12,24 @@ iterator allPermutations*[T](x: seq[T]): seq[T] =
yield workingCopy yield workingCopy
iterator allDigits*(lo, hi, size: int): seq[int] = iterator allDigits*(lo, hi, size: Natural): seq[int] =
# we use uninitialized since we will initialize it below, but not necessarily with 0s if size > 0: # otherwise we get an infinite loop
var digits = newSeqUninitialized[int](size) # we use uninitialized since we will initialize it below, but not necessarily with 0s
for i in 0 .. digits.high: var digits = newSeqUninitialized[int](size)
digits[i] = lo for i in 0 .. digits.high:
digits[i] = lo
var complete = false var complete = false
while not complete: while not complete:
yield digits yield digits
for i in countdown(digits.high, 0): for i in countdown(digits.high, 0):
if digits[i] < hi: if digits[i] < hi:
inc digits[i] inc digits[i]
break break
elif i == 0: # since this is the last digit to be incremented, we must be done elif i == 0: # since this is the last digit to be incremented, we must be done
complete = true complete = true
else: else:
digits[i] = lo digits[i] = lo
iterator possibleFutures*[C](dice: seq[C]): seq[tuple[color: C, value: int]] = iterator possibleFutures*[C](dice: seq[C]): seq[tuple[color: C, value: int]] =

114
main.nim
View File

@ -1,46 +1,44 @@
import math, hashes, options, tables, sequtils, sets, sugar import math, hashes, options, tables, sequtils, sets, sugar
import combinators import combinators, colors
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
const allDice = @[cRed, cGreen, cBlue, cYellow, cPurple] 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: for i, s in toAdd:
scores[i] += s scores[i] += s
proc `[]`[T](b: var Board, idx: T): var Square = proc `[]`*[T](b: var Board, idx: T): var Square =
b.squares[idx] b.squares[idx]
proc hash(b: Board): Hash = proc hash*(b: Board): Hash =
var h: Hash = 0 var h: Hash = 0
# there could be a tile anywhere so we have to check all squares # there could be a tile anywhere so we have to check all squares
for i, sq in b.squares: for i, sq in b.squares:
@ -49,11 +47,17 @@ proc hash(b: Board): Hash =
if sq.tile.isSome: if sq.tile.isSome:
h = h !& int(sq.tile.get) * 10 # so it isn't confused with a camel h = h !& int(sq.tile.get) * 10 # so it isn't confused with a camel
else: else:
for c in sq.camels: for c in sq.camels.asInt:
h = h !& int(c) h = h !& c
result = !$h 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) = proc display(b: Board, start, stop: int) =
for i in start..stop: for i in start..stop:
let sq = b.squares[i] let sq = b.squares[i]
@ -79,6 +83,11 @@ proc setState(b: var Board;
b.leader = some(leadCamel) b.leader = some(leadCamel)
proc resetDice(b: var Board) =
var d: array[Color, bool]
b.diceRolled = d
proc advance(b: var Board, die: Die) = proc advance(b: var Board, die: Die) =
let let
(color, roll) = die (color, roll) = die
@ -96,21 +105,24 @@ proc advance(b: var Board, die: Die) =
endPos += int(t) endPos += int(t)
if t == tBackward: prepend = true if t == tBackward: prepend = true
for i, c in b[startPos].camels: let stackStart = b[startPos].camels.find(color)
if c == color: if prepend:
let subStack = b[startPos].camels[i .. ^1] b[startPos].camels.moveSubstackPre(b[endPos].camels, stackStart)
if prepend: let stackLen = b[startPos].camels.len - stackStart
b[endPos].camels.insert(subStack) for i in 0 ..< stackLen:
else: # we know how many camels we added to the bottom, so set the position for each of those
b[endPos].camels.add(subStack) 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[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!
b.diceRolled[color] = true b.diceRolled[color] = true
@ -129,14 +141,20 @@ proc projectLeg(b: Board): LegResults =
inc scores[prediction.leader.get] inc scores[prediction.leader.get]
# deduplicate results # deduplicate results
endStates.incl(prediction) endStates.incl(prediction)
result = (scores, endStates) result = (scores, endStates)
proc projectOutcomes(b: Board, maxDepth = 1): ScoreSet = proc projectOutcomes(b: Board, maxDepth = 1): ScoreSet =
var outcomeStack = @[ [b].toHashSet ] var outcomeStack = @[ [b].toHashSet ]
for depth in 1..maxDepth: for depth in 1..maxDepth:
echo "simulating ", outcomeStack[^1].len, " possible legs."
var endStates: HashSet[Board] var endStates: HashSet[Board]
for o in outcomeStack[^1]: 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 let projection = o.projectLeg
result.update(projection[0]) result.update(projection[0])
endStates.incl(projection[1]) endStates.incl(projection[1])
@ -147,11 +165,15 @@ proc projectOutcomes(b: Board, maxDepth = 1): ScoreSet =
var b: Board var b: Board
b.init
b.display(1, 5) 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: 4}, @[])
b.display(1, 5) b.display(1, 5)
# b.advance((cRed, 1))
# b.display(1, 5)
let r = b.projectOutcomes(2) let r = b.projectOutcomes(2)
let total = r.sum let total = r.sum
for i, c in r: for i, c in r: