Compare commits
No commits in common. "bd413da9a3b9b2bef81347b578501f0c57126643" and "6f694f99edb76ffd2174d2886aec9ef92d532ea2" have entirely different histories.
bd413da9a3
...
6f694f99ed
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:
|
||||||
stdout.writeLine($lead & $sq.tile.get)
|
echo lead, sq.tile.get
|
||||||
else:
|
else:
|
||||||
stdout.writeLine($lead & $sq.camels)
|
echo lead, sq.camels
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
|
||||||
|
108
main.nim
108
main.nim
@ -1,5 +1,102 @@
|
|||||||
import math, options, sequtils, random, sets
|
import math, options, sequtils, random, sets
|
||||||
import combinators, game, fixedseq, simulation, ui
|
import combinators, game, fixedseq, 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:
|
||||||
@ -9,10 +106,5 @@ 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 legScores = b.getLegScores
|
let scores = b.projectLeg()[0]
|
||||||
echo "Current leg probabilities:"
|
scores.display
|
||||||
legScores.display
|
|
||||||
|
|
||||||
let gameScores = b.randomGames(1_000_000)
|
|
||||||
echo "\nFull game probabilities (1M simulations):"
|
|
||||||
gameScores.display
|
|
||||||
|
131
simulation.nim
131
simulation.nim
@ -1,131 +0,0 @@
|
|||||||
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 this at the module level so it 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
|
|
79
test.nim
79
test.nim
@ -1,79 +0,0 @@
|
|||||||
import math, random, strformat, 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
|
|
||||||
|
|
||||||
|
|
||||||
proc testSpread(nTests, nSamples: Natural) =
|
|
||||||
var b: Board
|
|
||||||
b.init
|
|
||||||
var r = initRand(rand(int64))
|
|
||||||
let dice = randomDice(r)
|
|
||||||
b.setState(dice, [])
|
|
||||||
b.display(1, 5)
|
|
||||||
let spread = randomSpread(b, nTests, nSamples)
|
|
||||||
|
|
||||||
stdout.writeLine("Variance:")
|
|
||||||
for c in Color:
|
|
||||||
let variance = 100 * (spread.hi[c] - spread.lo[c])
|
|
||||||
stdout.writeLine(fmt"{c}: {round(variance, 2):.2f}%")
|
|
||||||
|
|
||||||
let diff = 100 * (max(spread.hi) - min(spread.lo))
|
|
||||||
stdout.writeLine(fmt"Win percentage differential: {round(diff, 2):.2f}%")
|
|
||||||
|
|
||||||
stdout.flushFile()
|
|
||||||
|
|
||||||
|
|
||||||
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 ""
|
|
||||||
|
|
||||||
testSpread(100, 1_000_000)
|
|
Loading…
x
Reference in New Issue
Block a user