mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 00:32:40 +00:00
Adventure: Complete v1 daily idle RPG with DM-driven gameplay, equipment shop, treasure system, TwinBee NPC, streak/grudge/party mechanics, flavor text, and scheduled morning/evening/midnight tickers. Holdem CFR: Fix three critical training bugs (fold not forfeiting pot, free calls after raises, training/runtime key mismatch). Add performance optimizations (preflop lookup table, zero-alloc equity, integer keys, raise cap, regret pruning). Enrich abstraction with 12 equity buckets, board texture dimension, and 6-char action history. Replace validation with proper multi-street simulation. Also includes wordle plugin, holdem seed tooling, and schema additions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
115 lines
2.6 KiB
Go
115 lines
2.6 KiB
Go
package plugin
|
|
|
|
import (
|
|
"math/rand/v2"
|
|
|
|
"github.com/chehsunliu/poker"
|
|
)
|
|
|
|
// EquityResult holds Monte Carlo simulation results.
|
|
type EquityResult struct {
|
|
Win float64
|
|
Tie float64
|
|
Loss float64
|
|
}
|
|
|
|
// allCards returns a fresh 52-card slice.
|
|
func allCards() []poker.Card {
|
|
suits := []string{"s", "h", "d", "c"}
|
|
ranks := []string{"2", "3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K", "A"}
|
|
cards := make([]poker.Card, 0, 52)
|
|
for _, r := range ranks {
|
|
for _, s := range suits {
|
|
cards = append(cards, poker.NewCard(r+s))
|
|
}
|
|
}
|
|
return cards
|
|
}
|
|
|
|
// Equity computes win/tie/loss fractions via Monte Carlo simulation.
|
|
func Equity(hole [2]poker.Card, community []poker.Card, numOpponents, iterations int) EquityResult {
|
|
if numOpponents < 1 {
|
|
numOpponents = 1
|
|
}
|
|
|
|
// Build set of known cards to exclude.
|
|
known := make(map[poker.Card]bool, 2+len(community))
|
|
known[hole[0]] = true
|
|
known[hole[1]] = true
|
|
for _, c := range community {
|
|
known[c] = true
|
|
}
|
|
|
|
// Remaining deck.
|
|
remaining := make([]poker.Card, 0, 52-len(known))
|
|
for _, c := range allCards() {
|
|
if !known[c] {
|
|
remaining = append(remaining, c)
|
|
}
|
|
}
|
|
|
|
boardNeeded := 5 - len(community)
|
|
cardsNeeded := numOpponents*2 + boardNeeded
|
|
|
|
var wins, ties, losses int
|
|
|
|
for i := 0; i < iterations; i++ {
|
|
// Fisher-Yates shuffle of first cardsNeeded elements.
|
|
for j := 0; j < cardsNeeded && j < len(remaining); j++ {
|
|
k := j + rand.IntN(len(remaining)-j)
|
|
remaining[j], remaining[k] = remaining[k], remaining[j]
|
|
}
|
|
|
|
// Deal opponent holes.
|
|
idx := 0
|
|
opponentHoles := make([][2]poker.Card, numOpponents)
|
|
for o := 0; o < numOpponents; o++ {
|
|
opponentHoles[o] = [2]poker.Card{remaining[idx], remaining[idx+1]}
|
|
idx += 2
|
|
}
|
|
|
|
// Complete board.
|
|
fullBoard := make([]poker.Card, 5)
|
|
copy(fullBoard, community)
|
|
for b := len(community); b < 5; b++ {
|
|
fullBoard[b] = remaining[idx]
|
|
idx++
|
|
}
|
|
|
|
// Evaluate hero.
|
|
heroCards := make([]poker.Card, 7)
|
|
heroCards[0] = hole[0]
|
|
heroCards[1] = hole[1]
|
|
copy(heroCards[2:], fullBoard)
|
|
heroRank := poker.Evaluate(heroCards)
|
|
|
|
// Evaluate opponents.
|
|
bestOpp := int32(7463) // worst possible rank
|
|
for _, oh := range opponentHoles {
|
|
oppCards := make([]poker.Card, 7)
|
|
oppCards[0] = oh[0]
|
|
oppCards[1] = oh[1]
|
|
copy(oppCards[2:], fullBoard)
|
|
oppRank := poker.Evaluate(oppCards)
|
|
if oppRank < bestOpp {
|
|
bestOpp = oppRank
|
|
}
|
|
}
|
|
|
|
if heroRank < bestOpp {
|
|
wins++
|
|
} else if heroRank == bestOpp {
|
|
ties++
|
|
} else {
|
|
losses++
|
|
}
|
|
}
|
|
|
|
total := float64(iterations)
|
|
return EquityResult{
|
|
Win: float64(wins) / total,
|
|
Tie: float64(ties) / total,
|
|
Loss: float64(losses) / total,
|
|
}
|
|
}
|