Files
gogobee/internal/plugin/wordle_game.go
prosolis 0a1359de46 Add masterwork rework, arena combat log, Death's Reprieve fix, and wordle improvements
Adventure — Masterwork Rework:
- Expand from 3 generic items to 15 tiered masterwork items across
  mining (weapon), fishing (armor), and foraging (boots) with per-tier
  drop rates (5%/4%/3%/2%/1.5%) and location gating
- Skill-specific cross-skill bonuses replace flat masterwork check
- Auto-equip if better, silent discard for duplicates, inventory for rest
- Add !adventure equip command for manual masterwork equipping
- Tiered room announcements: T1-2 DM only, T3 quiet, T4-5 full
- First-drop detection with special message
- Character sheet shows /⚔️ markers for masterwork/arena gear
- Sell protection for special gear in shop

Adventure — Arena Combat Log:
- Turn-based Dragon Quest style narrative for arena fights
- Outcome-first design: roll determines win/loss, log assembled backward
- No-repeat flavor text via actionPicker with per-pool tracking
- 60 participation XP on arena loss

Adventure — Death's Reprieve Fix:
- All equipment set to 1 condition instead of death-level degradation

Wordle:
- Refactored word sourcing and expanded fallback word list
- Simplified Wordnik integration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 16:24:59 -07:00

122 lines
3.0 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
}
// WordleCategory identifies the language/category of a puzzle.
type WordleCategory string
const (
WordleCategoryEN WordleCategory = "" // English (default)
WordleCategoryPT WordleCategory = "pt" // European Portuguese
WordleCategoryFR WordleCategory = "fr" // French
WordleCategoryGames WordleCategory = "games" // Video game words (English)
)
// 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
Category WordleCategory
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]
}
}
}