// Command holdem-train trains the casino's poker bots and writes the policy the // table embeds. // // go run ./cmd/holdem-train -iterations 5000000 -out internal/games/holdem/policy.gob // // It is not part of Pete. It runs when the engine's rules change, takes half an // hour, and produces a file. Nothing at runtime imports it. // // The bots are trained heads-up, at every stack depth the table deals — that // range is the point, because poker at twenty big blinds and poker at a hundred // are different games and a bot that only knows one of them folds into the other. // A six-handed table then reuses the same policy, which is an approximation, and // a documented one. package main import ( "flag" "fmt" "log" "os" "runtime" "time" "pete/internal/games/holdem" ) func main() { iterations := flag.Int("iterations", 5_000_000, "hands to train on") workers := flag.Int("workers", runtime.NumCPU(), "parallel workers") out := flag.String("out", "internal/games/holdem/policy.gob", "where to write the policy") stakes := flag.String("stakes", "low", "which table's blinds to train at") minBB := flag.Int64("min-bb", 20, "shallowest stack, in big blinds") maxBB := flag.Int64("max-bb", 100, "deepest stack, in big blinds") seed := flag.Uint64("seed", 20260714, "seed, so a run can be repeated") flag.Parse() tier, err := holdem.TierBySlug(*stakes) if err != nil { log.Fatalf("no such table %q", *stakes) } fmt.Printf("training %s hands on %d workers — %s blinds, %d–%d BB deep\n", commas(*iterations), *workers, tier.Name, *minBB, *maxBB) started := time.Now() last := started policy := holdem.Train(*iterations, *workers, tier, *minBB, *maxBB, *seed, func(done int) { if time.Since(last) < 20*time.Second { return } last = time.Now() frac := float64(done) / float64(*iterations) if frac <= 0 { return } left := time.Duration(float64(time.Since(started)) * (1 - frac) / frac) fmt.Printf(" %s / %s hands (%.0f%%), about %s to go\n", commas(done), commas(*iterations), frac*100, left.Round(time.Second)) }) f, err := os.Create(*out) if err != nil { log.Fatal(err) } defer f.Close() if err := holdem.Save(f, policy); err != nil { log.Fatal(err) } info, _ := f.Stat() fmt.Printf("\ndone in %s: %s nodes, %.1f MB → %s\n", time.Since(started).Round(time.Second), commas(policy.Meta.Nodes), float64(info.Size())/(1<<20), *out) } func commas(n int) string { s := fmt.Sprint(n) if len(s) <= 3 { return s } var out []byte for i, c := range []byte(s) { if i > 0 && (len(s)-i)%3 == 0 { out = append(out, ',') } out = append(out, c) } return string(out) }