refactor and start shifting to FixedSeq (currently broken)

This commit is contained in:
Joseph Montanaro 2021-03-24 00:18:53 -07:00
parent 59fc6b961b
commit 2256b6d9bf
5 changed files with 296 additions and 267 deletions

View File

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

View File

@ -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. # returns all permutations of a given seq. Order is wonky but we don't care.
var workingCopy = x var workingCopy = x
yield workingCopy yield workingCopy
@ -12,12 +13,12 @@ iterator allPermutations*[T](x: seq[T]): seq[T] =
yield workingCopy 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 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: FixedSeq[5, int8, int8]
var digits = newSeqUninitialized[int](size) digits.initFixedSeq
for i in 0 .. digits.high: for i in 0 ..< size:
digits[i] = lo digits.add(lo)
var complete = false var complete = false
while not complete: while not complete:
@ -32,9 +33,12 @@ iterator allDigits*(lo, hi, size: Natural): seq[int] =
digits[i] = lo 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 # iterate over all possible sequences of die rolls. Each outcome
# is returned as a 5-sequence of (color, number) tuples. # is returned as a 5-sequence of (color, number) tuples.
for perm in dice.allPermutations: for perm in dice.allPermutations:
for d in allDigits(1, 3, dice.len): for digits in allDigits(1, 3, dice.len):
yield zip(perm, d) var f = initFixedSeq(5, Die, int8)
for i in 0 .. dice.high:
f.add((perm[i], digits[i]))
yield f

135
fixedseq.nim Normal file
View File

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

133
game.nim Normal file
View File

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

132
main.nim
View File

@ -1,29 +1,8 @@
import math, hashes, options, tables, sequtils, sets, sugar import math, options, sequtils, sets, sugar
import combinators, colors import combinators, game, fixedseq
const allDice = @[cRed, cGreen, cBlue, cYellow, cPurple]
type 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] ScoreSet* = array[Color, int]
LegResults* = tuple[scores: ScoreSet, endStates: HashSet[Board]] LegResults* = tuple[scores: ScoreSet, endStates: HashSet[Board]]
@ -34,105 +13,14 @@ proc update*(scores: var ScoreSet, toAdd: ScoreSet) =
scores[i] += s 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 = proc projectLeg(b: Board): LegResults =
var scores: ScoreSet var scores: ScoreSet
var endStates: HashSet[Board] var endStates: HashSet[Board]
let diceRemaining = collect(newSeq): var diceRemaining: ColorStack
for i, c in b.diceRolled: diceRemaining.initFixedSeq
if not c: i for i, c in b.diceRolled:
if not c: diceRemaining.add(i)
for future in possibleFutures(diceRemaining): for future in possibleFutures(diceRemaining):
var prediction = b # make a copy var prediction = b # make a copy
@ -174,6 +62,14 @@ b.display(1, 5)
# b.advance((cRed, 1)) # b.advance((cRed, 1))
# b.display(1, 5) # 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 r = b.projectOutcomes(2)
let total = r.sum let total = r.sum
for i, c in r: for i, c in r: