2021-07-13 15:54:54 -07:00
|
|
|
import cpuinfo, math, options, random, tables
|
|
|
|
import combinators, game, fixedseq
|
|
|
|
|
|
|
|
|
|
|
|
type
|
|
|
|
ScoreSet* = array[Color, int]
|
2021-07-14 21:49:10 -07:00
|
|
|
WinPercents* = array[Color, float]
|
2021-07-13 15:54:54 -07:00
|
|
|
|
|
|
|
ScoreSpread = object
|
2021-07-13 16:16:47 -07:00
|
|
|
lo*: array[Color, float]
|
|
|
|
hi*: array[Color, float]
|
2021-07-13 15:54:54 -07:00
|
|
|
|
|
|
|
LegResults* = tuple[scores: ScoreSet, endStates: CountTable[Board]]
|
|
|
|
|
|
|
|
|
|
|
|
proc update*(scores: var ScoreSet, toAdd: ScoreSet) =
|
|
|
|
for i, s in toAdd:
|
|
|
|
scores[i] += s
|
|
|
|
|
|
|
|
|
|
|
|
proc display*(scores: ScoreSet) =
|
|
|
|
let total = scores.sum
|
|
|
|
for color, score in scores:
|
|
|
|
let line = $color & ": " & $round(100 * scores[color] / total, 2) & '%'
|
|
|
|
stdout.writeLine(line)
|
|
|
|
stdout.flushFile()
|
|
|
|
# echo color, ": ", round(100 * scores[color] / total, 2), '%'
|
|
|
|
|
|
|
|
|
2021-07-14 21:49:10 -07:00
|
|
|
proc percents*(scores: ScoreSet): WinPercents =
|
|
|
|
let total = scores.sum
|
|
|
|
for c, score in scores:
|
|
|
|
result[c] = score / total
|
|
|
|
|
|
|
|
|
2021-07-13 15:54:54 -07:00
|
|
|
# ======================
|
|
|
|
# Single-leg simulations
|
|
|
|
# ======================
|
|
|
|
|
|
|
|
iterator legEndStates(b: Board): Board =
|
|
|
|
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 so we can mutate
|
|
|
|
for dieRoll in future:
|
|
|
|
prediction.advance(dieRoll)
|
|
|
|
yield prediction
|
|
|
|
|
|
|
|
|
|
|
|
proc getLegScores*(b: Board): ScoreSet =
|
|
|
|
for prediction in b.legEndStates:
|
2021-07-19 21:26:26 -07:00
|
|
|
inc result[prediction.leader]
|
2021-07-13 15:54:54 -07:00
|
|
|
|
|
|
|
|
|
|
|
# =====================
|
|
|
|
# Full-game simulations
|
|
|
|
# =====================
|
|
|
|
|
|
|
|
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:
|
2021-07-19 13:20:45 -07:00
|
|
|
return projection.winner.get
|
2021-07-13 15:54:54 -07:00
|
|
|
projection.resetDice()
|
|
|
|
|
|
|
|
|
|
|
|
proc randomGamesWorker(b: Board, count: Natural, r: var Rand): ScoreSet =
|
|
|
|
for i in 1 .. count:
|
|
|
|
let winner = b.randomGame(r)
|
|
|
|
inc result[winner]
|
|
|
|
|
|
|
|
|
|
|
|
# =======================
|
|
|
|
# Multithreading nonsense
|
|
|
|
# =======================
|
|
|
|
|
|
|
|
type WorkerArgs = object
|
|
|
|
board: Board
|
|
|
|
count: Natural
|
|
|
|
seed: int64
|
2021-07-13 16:16:47 -07:00
|
|
|
|
|
|
|
|
|
|
|
# have to do this at the module level so it can be shared
|
2021-07-13 15:54:54 -07:00
|
|
|
var gamesChannel: Channel[ScoreSet]
|
|
|
|
gamesChannel.open()
|
|
|
|
|
|
|
|
|
|
|
|
proc randomGamesThread(args: WorkerArgs) =
|
|
|
|
var r = initRand(args.seed)
|
|
|
|
let scores = randomGamesWorker(args.board, args.count, r)
|
|
|
|
gamesChannel.send(scores)
|
|
|
|
|
|
|
|
|
|
|
|
proc randomGames*(b: Board, count: Natural, parallel = true, numThreads = 0): ScoreSet =
|
|
|
|
randomize()
|
|
|
|
|
|
|
|
if not parallel:
|
|
|
|
var r = initRand(rand(int64))
|
|
|
|
return randomGamesWorker(b, count, r)
|
|
|
|
|
|
|
|
let numThreads =
|
|
|
|
if numThreads == 0:
|
|
|
|
countProcessors()
|
|
|
|
else:
|
|
|
|
numThreads
|
|
|
|
|
|
|
|
var workers = newSeq[Thread[WorkerArgs]](numThreads)
|
|
|
|
for i, w in workers.mpairs:
|
|
|
|
var numGames = int(floor(count / numThreads))
|
2021-07-14 08:43:12 -07:00
|
|
|
if i < (count mod numThreads):
|
2021-07-13 15:54:54 -07:00
|
|
|
numGames += 1
|
|
|
|
let args = WorkerArgs(board: b, count: numGames, seed: rand(int64))
|
|
|
|
|
|
|
|
createThread(w, randomGamesThread, args)
|
|
|
|
|
|
|
|
for i in 1 .. numThreads:
|
|
|
|
let scores = gamesChannel.recv()
|
|
|
|
result.update(scores)
|
|
|
|
|
|
|
|
|
|
|
|
proc randomSpread*(b: Board; nTests, nSamples: Natural): ScoreSpread =
|
|
|
|
for s in result.lo.mitems:
|
|
|
|
s = 1
|
|
|
|
|
|
|
|
for i in 0 ..< nTests:
|
|
|
|
let scores = b.randomGames(nSamples)
|
|
|
|
let total = scores.sum
|
|
|
|
for color, score in scores:
|
|
|
|
let pct = score / total
|
|
|
|
if pct < result.lo[color]:
|
|
|
|
result.lo[color] = pct
|
|
|
|
if pct > result.hi[color]:
|
|
|
|
result.hi[color] = pct
|