Files
gogobee/internal/plugin/wordle_game.go
prosolis 3369d7d8fe gofmt: bring internal/ and cmd/ back to gofmt -l clean
Mechanical `gofmt -w ./internal ./cmd`. Mostly struct-field realignment that
had drifted, plus a few trailing-newline fixes. No behaviour change — gofmt is
semantics-preserving, and build/vet/test are green either side.

Split out from the code-review fixes that follow so those stay reviewable
instead of hiding inside a wall of realignment.
2026-07-10 07:18:07 -07:00

122 lines
2.9 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
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]
}
}
}