multithreaded simulation for full game
This commit is contained in:
parent
6f694f99ed
commit
bcf87a10fd
4
game.nim
4
game.nim
@ -86,9 +86,9 @@ proc display*(b: Board, start, stop: int) =
|
|||||||
let sq = b.squares[i]
|
let sq = b.squares[i]
|
||||||
let lead = $i & ": "
|
let lead = $i & ": "
|
||||||
if sq.tile.isSome:
|
if sq.tile.isSome:
|
||||||
echo lead, sq.tile.get
|
stdout.writeLine($lead & $sq.tile.get)
|
||||||
else:
|
else:
|
||||||
echo lead, sq.camels
|
stdout.writeLine($lead & $sq.camels)
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
|
||||||
|
108
main.nim
108
main.nim
@ -1,102 +1,5 @@
|
|||||||
import math, options, sequtils, random, sets
|
import math, options, sequtils, random, sets
|
||||||
import combinators, game, fixedseq, ui
|
import combinators, game, fixedseq, simulation, ui
|
||||||
|
|
||||||
|
|
||||||
type
|
|
||||||
ScoreSet* = array[Color, int]
|
|
||||||
|
|
||||||
ScoreSpread = object
|
|
||||||
lo: array[Color, float]
|
|
||||||
hi: array[Color, float]
|
|
||||||
|
|
||||||
LegResults* = tuple[scores: ScoreSet, endStates: HashSet[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:
|
|
||||||
echo color, ": ", round(100 * scores[color] / total, 2), '%'
|
|
||||||
|
|
||||||
|
|
||||||
proc projectLeg*(b: Board): LegResults =
|
|
||||||
var scores: ScoreSet
|
|
||||||
var endStates: HashSet[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
|
|
||||||
for dieRoll in future:
|
|
||||||
prediction.advance(dieRoll)
|
|
||||||
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
|
|
||||||
if outcomeStack.len > 1:
|
|
||||||
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])
|
|
||||||
stdout.write("simulated: " & $result.sum & "\r")
|
|
||||||
|
|
||||||
outcomeStack.add(endStates)
|
|
||||||
echo "\nDistinct end states: ", outcomeStack.mapIt(it.len).sum
|
|
||||||
|
|
||||||
|
|
||||||
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:
|
|
||||||
return projection.leader.get
|
|
||||||
projection.resetDice
|
|
||||||
|
|
||||||
|
|
||||||
proc randomGames(b: Board, count: SomeInteger): ScoreSet =
|
|
||||||
randomize()
|
|
||||||
var r = initRand(rand(int64))
|
|
||||||
for i in 1 .. count:
|
|
||||||
let winner = b.randomGame(r)
|
|
||||||
inc result[winner]
|
|
||||||
# if i mod 100_000 == 0 or i == count - 1:
|
|
||||||
# stdout.write("simulating " & count & "random games: " & $i & "\r")
|
|
||||||
# echo ""
|
|
||||||
|
|
||||||
|
|
||||||
proc randomSpread(b: Board, nTests: SomeInteger, nSamples: SomeInteger): 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
|
|
||||||
|
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
@ -106,5 +9,10 @@ when isMainModule:
|
|||||||
b.setState(config.state, [])
|
b.setState(config.state, [])
|
||||||
b.diceRolled = config.diceRolled
|
b.diceRolled = config.diceRolled
|
||||||
b.display(1, 5)
|
b.display(1, 5)
|
||||||
let scores = b.projectLeg()[0]
|
let legScores = b.getLegScores
|
||||||
scores.display
|
echo "Current leg probabilities:"
|
||||||
|
legScores.display
|
||||||
|
|
||||||
|
let gameScores = b.randomGames(1_000_000)
|
||||||
|
echo "\nFull game probabilities (1M simulations):"
|
||||||
|
gameScores.display
|
||||||
|
129
simulation.nim
Normal file
129
simulation.nim
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import cpuinfo, math, options, random, tables
|
||||||
|
import combinators, game, fixedseq
|
||||||
|
|
||||||
|
|
||||||
|
type
|
||||||
|
ScoreSet* = array[Color, int]
|
||||||
|
|
||||||
|
ScoreSpread = object
|
||||||
|
lo: array[Color, float]
|
||||||
|
hi: array[Color, float]
|
||||||
|
|
||||||
|
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), '%'
|
||||||
|
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# 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:
|
||||||
|
inc result[prediction.leader.get]
|
||||||
|
|
||||||
|
|
||||||
|
# =====================
|
||||||
|
# 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:
|
||||||
|
return projection.leader.get
|
||||||
|
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
|
||||||
|
# have to do these at the module level so they can be shared
|
||||||
|
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))
|
||||||
|
if i <= (count mod numThreads):
|
||||||
|
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
|
57
test.nim
Normal file
57
test.nim
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import random, times
|
||||||
|
import fixedseq, game, simulation
|
||||||
|
|
||||||
|
|
||||||
|
proc randomDice(r: var Rand): seq[tuple[c: Color, p: int]] =
|
||||||
|
for c in Color:
|
||||||
|
let v = r.rand(1..3)
|
||||||
|
result.add((c, v))
|
||||||
|
result.shuffle
|
||||||
|
|
||||||
|
|
||||||
|
proc testGames(n: SomeInteger = 100): auto =
|
||||||
|
var r = initRand(rand(int64))
|
||||||
|
let dice = randomDice(r)
|
||||||
|
var b: Board
|
||||||
|
b.init
|
||||||
|
b.setState(dice, [])
|
||||||
|
b.display(1, 5)
|
||||||
|
|
||||||
|
let startTime = cpuTime()
|
||||||
|
let scores = b.randomGames(n)
|
||||||
|
result = cpuTime() - startTime
|
||||||
|
scores.display()
|
||||||
|
|
||||||
|
|
||||||
|
proc testLegs(n: Natural = 100): auto =
|
||||||
|
var boards: seq[Board]
|
||||||
|
var r = initRand(rand(int64))
|
||||||
|
for i in 1 .. n:
|
||||||
|
var b: Board
|
||||||
|
b.init
|
||||||
|
let dice = randomDice(r)
|
||||||
|
b.setState(dice, [])
|
||||||
|
boards.add(b)
|
||||||
|
stdout.write("Constructed: " & $i & "\r")
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Running..."
|
||||||
|
let start = cpuTime()
|
||||||
|
for b in boards:
|
||||||
|
discard b.getLegScores
|
||||||
|
result = cpuTime() - start
|
||||||
|
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
randomize()
|
||||||
|
# let start_states = 2_000
|
||||||
|
# let executionTime = testLegs(start_states)
|
||||||
|
# echo "Execution time: ", executionTime
|
||||||
|
# echo "Leg simulations per second: ", float(start_states * 29_160) / executionTime
|
||||||
|
|
||||||
|
for i in 1 .. 1:
|
||||||
|
let num_games = 100_000_005
|
||||||
|
let executionTime = testGames(num_games)
|
||||||
|
echo "Execution time: ", executionTime
|
||||||
|
echo "Full-game simulations per second: ", float(num_games) / executionTime
|
||||||
|
echo ""
|
Loading…
x
Reference in New Issue
Block a user