mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 08:52:42 +00:00
Add forex plugin, stability fixes, and async HTTP dispatch
- Add forex plugin (Frankfurter v2 API) with rate lookups, analysis, DM-based alerts, and daily cron poll. Backfills 1 year of history on startup for moving averages and buy signal scoring. - Fix bot hang caused by SQLite lock contention in reminder polling: rows cursor was held open while writing to the same DB. Collect results first, close cursor, then process. Same fix in milkcarton. - Add sync retry loop so the bot reconnects after network drops instead of silently exiting. StopSync() for clean Ctrl+C shutdown. - Add panic recovery to all dispatch, syncer, and cron paths. - Make all HTTP-calling plugin commands async (goroutines) so a slow or dead external API cannot block the message dispatch pipeline. Affects: lookup, stocks, forex, anime, movies, concerts, gaming, retro, wotd, urls, howami. - Extract DisplayName to Base, add db.Exec helper, convert silent error discards across the codebase. - Fix UNO mercy-kill bug (eliminated bot continues playing), adventure DM nag spam, stats column mismatch, per-call regex/replacer allocs. - Update README: forex commands, Finance section, 47 plugins. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"strings"
|
||||
|
||||
"github.com/chehsunliu/poker"
|
||||
)
|
||||
@@ -112,3 +114,224 @@ func Equity(hole [2]poker.Card, community []poker.Card, numOpponents, iterations
|
||||
Loss: float64(losses) / total,
|
||||
}
|
||||
}
|
||||
|
||||
// DrawInfo holds computed draw information for tip generation.
|
||||
type DrawInfo struct {
|
||||
IsDraw bool
|
||||
FlushDrawOuts int
|
||||
StraightDrawOuts int
|
||||
TotalOuts int
|
||||
Description string // e.g. "flush draw + gutshot (13 outs)"
|
||||
}
|
||||
|
||||
// computeDraws analyzes hole cards and community for flush and straight draws.
|
||||
// Only meaningful on flop and turn (not preflop, not river).
|
||||
func computeDraws(hole [2]poker.Card, community []poker.Card) DrawInfo {
|
||||
if len(community) < 3 || len(community) > 4 {
|
||||
return DrawInfo{}
|
||||
}
|
||||
|
||||
all := make([]poker.Card, 0, 7)
|
||||
all = append(all, hole[0], hole[1])
|
||||
all = append(all, community...)
|
||||
|
||||
flushOuts := countFlushOuts(hole, community)
|
||||
straightOuts := countStraightOuts(all, hole, community)
|
||||
|
||||
total := flushOuts + straightOuts
|
||||
if total > 15 {
|
||||
total = 15 // cap to avoid double-counting
|
||||
}
|
||||
|
||||
if total == 0 {
|
||||
// Check backdoor draws (only on flop)
|
||||
if len(community) == 3 {
|
||||
return computeBackdoorDraws(hole, community)
|
||||
}
|
||||
return DrawInfo{}
|
||||
}
|
||||
|
||||
var parts []string
|
||||
if flushOuts >= 8 {
|
||||
parts = append(parts, "flush draw")
|
||||
}
|
||||
if straightOuts == 8 {
|
||||
parts = append(parts, "open-ended straight draw")
|
||||
} else if straightOuts == 4 {
|
||||
parts = append(parts, "gutshot straight draw")
|
||||
}
|
||||
|
||||
desc := fmt.Sprintf("%s (%d outs)", strings.Join(parts, " + "), total)
|
||||
|
||||
return DrawInfo{
|
||||
IsDraw: true,
|
||||
FlushDrawOuts: flushOuts,
|
||||
StraightDrawOuts: straightOuts,
|
||||
TotalOuts: total,
|
||||
Description: desc,
|
||||
}
|
||||
}
|
||||
|
||||
// countFlushOuts returns 9 if we have a flush draw (4 to a flush), 0 otherwise.
|
||||
func countFlushOuts(hole [2]poker.Card, community []poker.Card) int {
|
||||
suitCounts := map[int32]int{}
|
||||
holeSuits := map[int32]bool{}
|
||||
|
||||
for _, c := range []poker.Card{hole[0], hole[1]} {
|
||||
s := c.Suit()
|
||||
suitCounts[s]++
|
||||
holeSuits[s] = true
|
||||
}
|
||||
for _, c := range community {
|
||||
suitCounts[c.Suit()]++
|
||||
}
|
||||
|
||||
for s, count := range suitCounts {
|
||||
if count == 4 && holeSuits[s] {
|
||||
return 9 // 13 cards of suit minus 4 seen = 9 outs
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// countStraightOuts returns the number of straight outs (8 for OESD, 4 for gutshot).
|
||||
func countStraightOuts(allCards []poker.Card, hole [2]poker.Card, community []poker.Card) int {
|
||||
// Get unique ranks present (0-12 where 0=2, 12=A)
|
||||
rankSet := uint16(0)
|
||||
for _, c := range allCards {
|
||||
rankSet |= 1 << uint(c.Rank())
|
||||
}
|
||||
|
||||
// Already have a straight? (5+ consecutive bits)
|
||||
if hasStraight(rankSet) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Try adding each rank not already present; if it completes a straight, it's an out.
|
||||
// But only count if at least one hole card is part of the straight.
|
||||
outs := 0
|
||||
for r := int32(0); r < 13; r++ {
|
||||
if rankSet&(1<<uint(r)) != 0 {
|
||||
continue // already have this rank
|
||||
}
|
||||
test := rankSet | (1 << uint(r))
|
||||
if hasStraight(test) {
|
||||
// Verify at least one hole card participates in the completed straight.
|
||||
if holeParticipatesInStraight(test, hole) {
|
||||
// Count available cards of this rank (4 minus those on board)
|
||||
available := 4
|
||||
for _, c := range community {
|
||||
if c.Rank() == r {
|
||||
available--
|
||||
}
|
||||
}
|
||||
outs += available
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize: OESD = 8, gutshot = 4, double gutshot = 8
|
||||
if outs > 8 {
|
||||
outs = 8
|
||||
}
|
||||
return outs
|
||||
}
|
||||
|
||||
// hasStraight checks if a rank bitset contains 5+ consecutive ranks.
|
||||
// Handles A-low straight (A-2-3-4-5) by duplicating ace as rank -1.
|
||||
func hasStraight(ranks uint16) bool {
|
||||
// Check A-low straight: A(12), 2(0), 3(1), 4(2), 5(3)
|
||||
if ranks&0x100F == 0x100F { // bits 0,1,2,3,12
|
||||
return true
|
||||
}
|
||||
|
||||
consecutive := 0
|
||||
for i := uint(0); i < 13; i++ {
|
||||
if ranks&(1<<i) != 0 {
|
||||
consecutive++
|
||||
if consecutive >= 5 {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
consecutive = 0
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// holeParticipatesInStraight checks if at least one hole card rank is part of
|
||||
// any 5-consecutive-rank window in the given rank set.
|
||||
func holeParticipatesInStraight(ranks uint16, hole [2]poker.Card) bool {
|
||||
hr0 := uint(hole[0].Rank())
|
||||
hr1 := uint(hole[1].Rank())
|
||||
|
||||
// Check each possible 5-card window
|
||||
for start := uint(0); start <= 8; start++ {
|
||||
window := uint16(0x1F) << start // 5 consecutive bits
|
||||
if ranks&window == window {
|
||||
if hr0 >= start && hr0 < start+5 {
|
||||
return true
|
||||
}
|
||||
if hr1 >= start && hr1 < start+5 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check A-low straight (A=12, 2=0, 3=1, 4=2, 5=3)
|
||||
if ranks&0x100F == 0x100F && ranks&0x6 == 0x6 { // A,2,3,4,5
|
||||
if hr0 == 12 || hr0 <= 3 || hr1 == 12 || hr1 <= 3 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// computeBackdoorDraws detects backdoor flush/straight draws (flop only).
|
||||
func computeBackdoorDraws(hole [2]poker.Card, community []poker.Card) DrawInfo {
|
||||
var parts []string
|
||||
totalOuts := 0
|
||||
|
||||
// Backdoor flush: 3 to a flush with at least one hole card
|
||||
suitCounts := map[int32]int{}
|
||||
holeSuits := map[int32]bool{}
|
||||
for _, c := range []poker.Card{hole[0], hole[1]} {
|
||||
s := c.Suit()
|
||||
suitCounts[s]++
|
||||
holeSuits[s] = true
|
||||
}
|
||||
for _, c := range community {
|
||||
suitCounts[c.Suit()]++
|
||||
}
|
||||
for s, count := range suitCounts {
|
||||
if count == 3 && holeSuits[s] {
|
||||
parts = append(parts, "backdoor flush")
|
||||
totalOuts += 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Backdoor straight: 3 to a straight with connected hole cards
|
||||
// Simplified: if hole cards are within 4 ranks of each other, count it
|
||||
r0 := hole[0].Rank()
|
||||
r1 := hole[1].Rank()
|
||||
gap := r0 - r1
|
||||
if gap < 0 {
|
||||
gap = -gap
|
||||
}
|
||||
if gap >= 1 && gap <= 4 {
|
||||
parts = append(parts, "backdoor straight")
|
||||
totalOuts += 1
|
||||
}
|
||||
|
||||
if len(parts) == 0 {
|
||||
return DrawInfo{}
|
||||
}
|
||||
|
||||
return DrawInfo{
|
||||
IsDraw: true,
|
||||
TotalOuts: totalOuts,
|
||||
Description: fmt.Sprintf("%s (%d outs)", strings.Join(parts, " + "), totalOuts),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user