Created
May 6, 2023 19:23
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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 | |
* - 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