package loldle import ( "fmt" "html" "strings" ) // guessRow is a single per-attribute render line built up from a comparison. type guessRow struct { marker string label string value string } const ( markerCorrect = "✅" markerPartial = "🟨" markerWrong = "❌" markerName = "🎯" arrowUp = "⬆️" arrowDown = "⬇️" nameLabel = "Champion" ) func markerFor(result string) string { switch result { case ResultCorrect: return markerCorrect case ResultPartial: return markerPartial default: return markerWrong } } func arrowFor(direction string) string { switch direction { case "up": return arrowUp case "down": return arrowDown } return "" } // buildRows produces the labelled rows for a single guess: a name header // followed by one row per classic attribute. release_date wrong rows append // the direction arrow. func buildRows(championName string, results []AttributeRow) []guessRow { rows := make([]guessRow, 0, 1+len(results)) rows = append(rows, guessRow{marker: markerName, label: nameLabel, value: strings.ToUpper(championName)}) for _, r := range results { value := r.GuessValue if r.Key == "release_date" && r.Result != ResultCorrect { if a := arrowFor(r.Direction); a != "" { value = value + " " + a } } rows = append(rows, guessRow{marker: markerFor(r.Result), label: r.Label, value: value}) } return rows } // formatRowGroups joins one or more guess-row groups into a single
// block. Label column width is the max label across ALL groups so stacked
// guesses on a board align with each other.
func formatRowGroups(groups [][]guessRow) string {
width := 0
for _, g := range groups {
for _, r := range g {
if n := len(r.label); n > width {
width = n
}
}
}
blocks := make([]string, len(groups))
for i, g := range groups {
lines := make([]string, len(g))
for j, r := range g {
lines[j] = fmt.Sprintf("%s %s %s",
r.marker, padRight(r.label, width), html.EscapeString(r.value))
}
blocks[i] = strings.Join(lines, "\n")
}
return "" + strings.Join(blocks, "\n\n") + "
"
}
func padRight(s string, n int) string {
if len(s) >= n {
return s
}
return s + strings.Repeat(" ", n-len(s))
}
// renderGuess formats a single guess as the HTML block.
func renderGuess(championName string, results []AttributeRow) string {
return formatRowGroups([][]guessRow{buildRows(championName, results)})
}
// boardEntry is one prior guess on the rehydrated board.
type boardEntry struct {
Champion string
Results []AttributeRow
}
// renderBoard formats every prior guess in one aligned block. Empty
// board returns the placeholder hint instead of an empty .
func renderBoard(entries []boardEntry) string {
if len(entries) == 0 {
return "No guesses yet. Reply with /loldle <champion>."
}
groups := make([][]guessRow, len(entries))
for i, e := range entries {
groups[i] = buildRows(e.Champion, e.Results)
}
return formatRowGroups(groups)
}