Compare commits
2 Commits
6f694f99ed
...
bd413da9a3
Author | SHA1 | Date | |
---|---|---|---|
|
bd413da9a3 | ||
|
bcf87a10fd |
4
game.nim
4
game.nim
@ -86,9 +86,9 @@ proc display*(b: Board, start, stop: int) =
|
||||
let sq = b.squares[i]
|
||||
let lead = $i & ": "
|
||||
if sq.tile.isSome:
|
||||
echo lead, sq.tile.get
|
||||
stdout.writeLine($lead & $sq.tile.get)
|
||||
else:
|
||||
echo lead, sq.camels
|
||||
stdout.writeLine($lead & $sq.camels)
|
||||
echo ""
|
||||
|
||||
|
||||
|
108
main.nim
108
main.nim
@ -1,102 +1,5 @@
|
||||
import math, options, sequtils, random, sets
|
||||
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
|
||||
import combinators, game, fixedseq, simulation, ui
|
||||
|
||||
|
||||
when isMainModule:
|
||||
@ -106,5 +9,10 @@ when isMainModule:
|
||||
b.setState(config.state, [])
|
||||
b.diceRolled = config.diceRolled
|
||||
b.display(1, 5)
|
||||
let scores = b.projectLeg()[0]
|
||||
scores.display
|
||||
let legScores = b.getLegScores
|
||||
echo "Current leg probabilities:"
|
||||
legScores.display
|
||||
|
||||
let gameScores = b.randomGames(1_000_000)
|
||||
echo "\nFull game probabilities (1M simulations):"
|
||||
gameScores.display
|
||||
|
131
simulation.nim
Normal file
131
simulation.nim
Normal file
@ -0,0 +1,131 @@
|
||||
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
Normal file
79
test.nim
Normal file
@ -0,0 +1,79 @@
|
||||
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