Files
gogobee/internal/plugin/wordle_game.go
prosolis c7c1b76589 Add adventure plugin, holdem CFR fixes, and wordle plugin
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>
2026-03-22 10:09:57 -07:00

111 lines
2.6 KiB
Go

package plugin
import (
"time"
"maunium.net/go/mautrix/id"
)
// LetterResult represents the result of a single letter in a Wordle guess.
type LetterResult int
const (
LetterAbsent LetterResult = iota // ⬛ not in word
LetterPresent // 🟨 right letter, wrong position
LetterCorrect // 🟩 right letter, right position
)
// WordleGuess stores a single guess and its evaluation.
type WordleGuess struct {
Word string
PlayerID id.UserID
PlayerName string
Results []LetterResult
Timestamp time.Time
}
// WordlePuzzle holds all state for one day's puzzle.
type WordlePuzzle struct {
PuzzleID string // YYYY-MM-DD
PuzzleNumber int
RoomID id.RoomID
Answer string // uppercased
WordLength int
MaxGuesses int // always 6
Guesses []WordleGuess
Solved bool
Failed bool
StartedAt time.Time
SolvedAt *time.Time
LetterStates map[rune]LetterResult // best known state per letter
}
// WordlePlayerStat tracks a player's all-time Wordle stats.
type WordlePlayerStat struct {
UserID id.UserID
DisplayName string
TotalGuesses int
PuzzlesPlayed int
PuzzlesSolved int
WinningGuesses int
}
// scoreGuess evaluates a guess against the answer using the standard
// two-pass Wordle algorithm for correct duplicate-letter handling.
func scoreGuess(guess, answer string) []LetterResult {
n := len(answer)
results := make([]LetterResult, n)
pool := make([]rune, 0, n)
guessRunes := []rune(guess)
answerRunes := []rune(answer)
// First pass: mark exact matches (Correct).
used := make([]bool, n)
for i := 0; i < n; i++ {
if guessRunes[i] == answerRunes[i] {
results[i] = LetterCorrect
used[i] = true
}
}
// Build pool of unmatched answer letters.
for i := 0; i < n; i++ {
if !used[i] {
pool = append(pool, answerRunes[i])
}
}
// Second pass: mark Present or Absent.
for i := 0; i < n; i++ {
if results[i] == LetterCorrect {
continue
}
found := false
for j, r := range pool {
if guessRunes[i] == r {
results[i] = LetterPresent
pool = append(pool[:j], pool[j+1:]...)
found = true
break
}
}
if !found {
results[i] = LetterAbsent
}
}
return results
}
// updateLetterStates updates the keyboard state map with results from a guess.
// A letter's state only upgrades: Absent → Present → Correct.
func updateLetterStates(states map[rune]LetterResult, guess string, results []LetterResult) {
for i, r := range []rune(guess) {
existing, ok := states[r]
if !ok || results[i] > existing {
states[r] = results[i]
}
}
}