Skip to content

Instantly share code, notes, and snippets.

@ekevoo
Created May 6, 2021
Embed
What would you like to do?
Simulation of a game of Pigs
/*
Simulation of the game Pigs.
Inspired by a Numberphile video.
https://www.youtube.com/watch?v=ULhRLGzoXQ0
*/
package main
import (
"math/rand"
"os"
"sort"
"strconv"
"time"
"github.com/buger/goterm"
)
type Scores struct {
scores []int
plays int
}
type Podium []struct {
bet int
score int
}
var started = time.Now()
func main() {
numberOfGoroutines, err := strconv.Atoi(os.Args[1])
if err != nil {
numberOfGoroutines = 6
}
scores := make(chan Scores, 10)
for i := 0; i < numberOfGoroutines; i++ {
go keepGenerating(scores, rand.NewSource(rand.Int63()))
}
var tracker Scores
go tracker.accumulate(scores)
tracker.keepPrinting()
}
func keepGenerating(channel chan Scores, source rand.Source) {
const capacity = 32
random := rand.New(source)
play := make([]int, 0, capacity)
for {
// accumulate a number of scores before emitting them
scores := Scores{}
for i := 0; i < 100000; i++ {
roll := random.Intn(6) + 1
if roll == 1 {
scores.add(play2score(play))
play = make([]int, 0, capacity)
} else {
play = append(play, roll)
}
}
channel <- scores
}
}
func play2score(play []int) Scores {
places := 0
for _, val := range play {
places += val
}
result := Scores{make([]int, places), 1}
sum := 0
index := 0
for _, roll := range play {
sum += roll
for ; index < sum; index++ {
result.scores[index] = sum
}
}
return result
}
func (summed *Scores) add(summee Scores) {
if summed.scores == nil || summed.plays == 0 {
*summed = summee
return
}
if len(summee.scores) > len(summed.scores) {
summed.scores, summee.scores = summee.scores, summed.scores
}
for i, v := range summee.scores {
summed.scores[i] += v
}
summed.plays += summee.plays
}
func (tracker *Scores) accumulate(channel chan Scores) {
for score := range channel {
tracker.add(score)
}
}
func (tracker *Scores) keepPrinting() {
goterm.Clear()
for {
time.Sleep(time.Second / 6)
snapshot := tracker.duplicate()
podium := snapshot.framePodium()
podium.print(snapshot.plays)
}
}
func (source Scores) duplicate() Scores {
destination := Scores{make([]int, len(source.scores)), source.plays}
copy(destination.scores, source.scores)
return destination
}
func (tracker Scores) framePodium() Podium {
podium := make(Podium, len(tracker.scores))
for i, v := range tracker.scores {
podium[i].bet = i + 1
podium[i].score = v
}
sort.Sort(podium)
return podium
}
func (podium Podium) Len() int {
return len(podium)
}
func (podium Podium) Less(i, j int) bool {
if podium[i].score != podium[j].score {
return podium[i].score > podium[j].score
} else {
return podium[i].bet < podium[j].bet
}
}
func (podium Podium) Swap(i, j int) {
podium[i], podium[j] = podium[j], podium[i]
}
func (podium Podium) print(plays int) {
elapsed := time.Since(started)
rate := float64(plays) / elapsed.Seconds()
for line := goterm.Height(); line > 0; line-- {
if line <= len(podium) {
goterm.MoveCursor(1, line)
item := podium[line-1]
goterm.Printf("%4d: %d ", item.bet, item.score)
}
}
goterm.MoveCursor(40, 5)
goterm.Printf("%d/s ", int(rate))
goterm.Flush()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment