Skip to content

Instantly share code, notes, and snippets.

@StephanieSunshine
Created May 6, 2023 19:23
Show Gist options
  • Save StephanieSunshine/d7f7ffb13c486de27745743d6af09e7a to your computer and use it in GitHub Desktop.
Save StephanieSunshine/d7f7ffb13c486de27745743d6af09e7a to your computer and use it in GitHub Desktop.
A day at the race conditions -- Using race conditions for good -- multithreaded horse racing
/*
* A day at the race (conditions) -- Using race conditions to our benefit
*
* This is a proof of concept I intend to later turn into a BBS door game.
* This is for a bigger project where I plan to make a 24-bit color 90's BBS like shell,
* complete with ANSI animted background screens. The goal is to recreate the 90s BBS experience
* and reboot some of the old popular games from the Renegade/Wildcat/PCBoard/MBBS days.
* Other things it should have as well:
* - WebDAV file upload/download so you can share files
* - Games
* - Message board
* - Mail
* - Chat
* - SSH Keys for user auth
*
* MIT License (c) 2023 Stephanie Sunshine
*/
package main
import (
"fmt"
"math/rand"
"strings"
"time"
"github.com/cip8/autoname"
"github.com/StephanieSunshine/columnize"
"github.com/TwiN/go-color"
roll "github.com/StephanieSunshine/godie"
)
const (
// Overall horses to select from
FIELD = 100
// How many can run in a race
RUNNERS = 10
// No horse should have better stats than this
TOPHORSE = 21
// Baseline for odds to be calculated. Turn up for more excitement
TOPODD = 24.0
)
// Horses communicate their movements via a movement channel in a first come, first serve basis ( this is where the race (condition) happens )
type Movement struct {
horse *Horse
distance uint
}
// A horse
type Horse struct {
// Pre Generated Name
Name string
// Speed Bonus
// 1d6 to init
Speed uint
// How do they fatigue during the race
// 1d10 to init
Stamina uint
// How alert is the horse
// 1d6 to init
Awareness uint
// Odds ( these are gamified odds, not real odds )
Odds float32
// I realized after the fact that the scoring in real horse racing is different. This scoring comes from a hazy childhood memory of a BBS game I once played,
// and I'm not even sure I'm remembering it right.
// Metrics
Win uint
// 2nd place
Place uint
// 3rd place
Show uint
// loss
Loss uint
}
func createHorse() *Horse {
// Mr. or Ms. Ed
ed := new(Horse)
ed.Name = strings.Title(autoname.Generate(" "))
// This is to prime the for loop. We don't want perfect horses
ed.Speed = 6
ed.Stamina = 10
ed.Awareness = 6
// for loop acting as a while loop
for (ed.Speed+ed.Stamina+ed.Awareness) > TOPHORSE {
ed.Speed = roll.D6()
ed.Stamina = roll.D10()
ed.Awareness = roll.D6()
}
// I have no idea if this is even close to how they really do it
ed.Odds = TOPODD / float32(ed.Speed+ed.Stamina+ed.Awareness)
// off into the world with ye
return ed
}
func recalcOdds(ed *Horse) {
base := ed.Speed + ed.Stamina + ed.Awareness
// Wins count for 2, Place count for 1, Loss -1
bonus := (ed.Win*2)+ed.Place-ed.Loss
res := base + bonus
// if result will give us 1:1 or less odds, cap it
if res > TOPODD-1 { res = TOPODD-1 }
// we really don't want to divide by zero
if res < 1 { res = 1 }
// Same formula, just modified a bit
ed.Odds = TOPODD / float32(res)
}
// "Nothing" to see here, just a simple horse engine
func trojan(ready, woah *bool, feed chan Movement, ed *Horse) {
distance := uint(0) // max distance for now is 100
// In the gate and waiting for the signal
del := fmt.Sprintf("%dms", (7-ed.Awareness)*10)
d, _ := time.ParseDuration(del)
// fmt.Printf("%s Ready Delay: %s\n", ed.Name, del)
for !*ready { time.Sleep(d) }
// fmt.Println(ed.Name, "is off!")
// Running the track -- if we haven't been told to stop and we haven't crossed a finish line,
// go baby go
for !*woah && distance<100 {
// convert the calculated delay into a usable duration in milliseconds (1d20-(Awareness*20))
t := fmt.Sprintf("%dms", (((roll.D20()*100)+100) - ((6-ed.Awareness)*20))/5)
d, _ := time.ParseDuration(t)
fmt.Println(ed.Name, "sleeping: ", t)
// wait our turn (roll.D20 - (ed.Awareness*20))*time.Millisecond
time.Sleep(d)
// what is our fatigue?
sp := (distance / (ed.Stamina * 10))/2
// how much did we make it forward this turn?
roll := int(roll.D4()) + int(ed.Speed) - int(sp)
if roll > 0 {
// send via channel
feed <- Movement{ed, uint(roll)}
distance += uint(roll)
}
}
// fmt.Println(ed.Name, "Back to stables")
// nothing to do so it's back to the stables
}
func main() {
config := columnize.DefaultConfig()
config.HeaderColors = &columnize.ColorList{color.InUnderline, color.InUnderline, color.InUnderline, color.InUnderline, color.InUnderline}
config.BodyColors = &columnize.ColorList{color.InBlue, color.InYellow, color.InGreen, color.InCyan, color.InRed}
// field of all horses
var contender [100]*Horse
for i := range contender { contender[i] = createHorse() }
for e:=1; e<=20; e++ {
// horses that are going to be in the next race
var runners []*Horse
distance := make(map[*Horse]uint)
// how to make sure we are unique in our selections
var can [100]bool
// pick some contestants
for i:=0; i<=RUNNERS; i++ {
p := rand.Int()%100
for can[p] == true {
p = rand.Int()%100
}
can[p] = true
runners = append(runners, contender[p])
}
buf := []string{"Lane | Contenders | | | | "}
for i, v := range runners {
buf = append(buf, fmt.Sprintf("%d | %s | Odds: %0.3f:1 | Wins: %d | Losses: %d\n", i, v.Name, v.Odds, v.Win, v.Loss))
}
fmt.Print(columnize.Format(buf, config))
// func trojan(ready, woah *bool, feed chan Movement, ed *Horse) { }
var ready, woah bool = false, false
// we don't want to block the horses trying to get databack
var feed chan Movement = make(chan Movement, 2000)
for _, v := range runners {
go trojan(&ready, &woah, feed, v)
}
ready = true
winners := []*Horse{}
for data := range feed {
distance[data.horse] += data.distance
if distance[data.horse] >= 100 {
winners = append(winners, data.horse)
if len(winners) == 3 { break }
}
}
for _, v := range runners {
v.Loss++
}
// Cleanup
winners[0].Win++
winners[0].Loss--
winners[1].Place++
winners[1].Loss--
winners[2].Show++
winners[2].Loss--
for i, v := range winners {
fmt.Println(i, v.Name)
}
}
buf := []string{}
for i, v := range contender {
recalcOdds(v)
buf = append(buf, fmt.Sprintf("%d | %s | Odds: %0.3f:1 | Wins: %d | Losses: %d\n", i, v.Name, v.Odds, v.Win, v.Loss))
}
fmt.Print(columnize.Format(buf, config))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment