- commit
- 7915414b15131915fe27634e11e95d6a17042fee
- parent
- 4d5a3a5fcfb62a74b0bac8e1ca195048f9bfdbd2
- author
- Dami <dami@Damis-MacBook-Air.local>
| M | nonogram/ | 107 | ++++++++++++---- |
| M | nonogram/ | 144 | ++++++++++++++++++---- |
| M | sudoku/ | 96 | ++++++++++----- |
| M | sudoku/ | 15 | ++ |
| M | sudoku/ | 5 | +- |
| A | sudoku/ | 197 | ++++++++++++++++++++++++++++++ |
| M | wordsearch/ | 22 | ++-- |
@@ -26,6 +26,7 @@ type Model struct {
2626 cursor cursor2727 grid grid2828 keys KeyMap29+ modeName string2930 }3031 3132 func New(mode NonogramMode, hints Hints, save ...string) (game.Gamer, error) {@@ -45,6 +46,7 @@ func New(mode NonogramMode, hints Hints, save ...string) (game.Gamer, error) {
4546 colHints: c,4647 grid: newGrid(s),4748 keys: DefaultKeyMap,49+ modeName: mode.title,4850 }4951 5052 return m, nil@@ -88,52 +90,101 @@ func (m Model) Update(msg tea.Msg) (game.Gamer, tea.Cmd) {
8890 }8991 9092 func (m Model) View() string {93+ curr := generateTomography(m.grid)94+ solved := curr.rows.equal(m.rowHints) && curr.cols.equal(m.colHints)95+9196 maxWidth, maxHeight := m.rowHints.RequiredLen()*cellWidth, m.colHints.RequiredLen()92- g := gridView(m.grid, m.cursor)93- r := rowHintView(m.rowHints, maxWidth)94- c := colHintView(m.colHints, maxHeight)97+98+ title := nonoTitleBarView(m.modeName, solved)99+ g := gridView(m.grid, m.cursor, solved)100+ r := rowHintView(m.rowHints, maxWidth, curr.rows)101+ c := colHintView(m.colHints, maxHeight, curr.cols)95102 spacer := baseStyle.Width(maxWidth).Height(maxHeight).Render("")103+ status := nonoStatusBarView(m.keys)96104 97105 s1 := lipgloss.JoinHorizontal(lipgloss.Bottom, spacer, c)98106 s2 := lipgloss.JoinHorizontal(lipgloss.Top, r, g)99107 100- s := lipgloss.JoinVertical(lipgloss.Left, s1, s2)108+ grid := lipgloss.JoinVertical(lipgloss.Left, s1, s2)101109 102- return s110+ return lipgloss.JoinVertical(lipgloss.Left, title, grid, status)103111 }104112 105113 func (m Model) GetDebugInfo() string {106114 curr := generateTomography(m.grid)107115 solved := curr.rows.equal(m.rowHints) && curr.cols.equal(m.colHints)116+117+ status := "In Progress"118+ if solved {119+ status = "Solved"120+ }121+108122 s := fmt.Sprintf(109- "# Nonogram - Dev Info\n"+110- "| Property | Value |\n"+111- "| :----- | :---- |\n"+112- "| Solved | %v |\n"+113- "| Cursor | (%d,%d) |\n"+114- "| Dimensions | [%dx%d] |\n"+115- "| Hint Render Width | %dr %dc |\n"+116- "\n"+117- "Row Tomography\n\t%v\n"+118- "Row Hints\n\t\t%v\n\n"+119- "Col Tomography\n\t%v\n"+120- "Col Hints\n\t\t%v\n\n",121- solved,122- m.cursor.x,123- m.cursor.y,124- m.width,125- m.height,126- m.rowHints.RequiredLen()*cellWidth,127- m.colHints.RequiredLen(),128- curr.rows,129- m.rowHints,130- curr.cols,131- m.colHints,123+ "# Nonogram\n\n"+124+ "## Game State\n\n"+125+ "| Property | Value |\n"+126+ "| :--- | :--- |\n"+127+ "| Status | %s |\n"+128+ "| Cursor | (%d, %d) |\n"+129+ "| Grid Size | %d x %d |\n"+130+ "| Hint Widths | row: %d, col: %d |\n",131+ status,132+ m.cursor.x, m.cursor.y,133+ m.width, m.height,134+ m.rowHints.RequiredLen()*cellWidth, m.colHints.RequiredLen(),132135 )133136 137+ s += "\n## Row Tomography\n\n"138+ s += "| Row | Hint | Current | Match |\n"139+ s += "| :--- | :--- | :--- | :--- |\n"140+ for i, hint := range m.rowHints {141+ var currStr string142+ match := false143+ if i < len(curr.rows) {144+ currStr = intSliceStr(curr.rows[i])145+ match = intSliceEqual(hint, curr.rows[i])146+ }147+ matchStr := "No"148+ if match {149+ matchStr = "Yes"150+ }151+ s += fmt.Sprintf("| %d | %s | %s | %s |\n", i, intSliceStr(hint), currStr, matchStr)152+ }153+154+ s += "\n## Column Tomography\n\n"155+ s += "| Col | Hint | Current | Match |\n"156+ s += "| :--- | :--- | :--- | :--- |\n"157+ for i, hint := range m.colHints {158+ var currStr string159+ match := false160+ if i < len(curr.cols) {161+ currStr = intSliceStr(curr.cols[i])162+ match = intSliceEqual(hint, curr.cols[i])163+ }164+ matchStr := "No"165+ if match {166+ matchStr = "Yes"167+ }168+ s += fmt.Sprintf("| %d | %s | %s | %s |\n", i, intSliceStr(hint), currStr, matchStr)169+ }170+134171 return s135172 }136173 174+func intSliceStr(s []int) string {175+ if len(s) == 0 {176+ return "[]"177+ }178+ result := ""179+ for i, v := range s {180+ if i > 0 {181+ result += ", "182+ }183+ result += fmt.Sprintf("%d", v)184+ }185+ return result186+}187+137188 func (m *Model) updateTile(r rune) {138189 m.grid[m.cursor.y][m.cursor.x] = r139190 }@@ -11,18 +11,43 @@ var (
1111 baseStyle = lipgloss.NewStyle()1212 1313 filledStyle = baseStyle.14- Background(lipgloss.Color("255")).15- Foreground(lipgloss.Color("255"))14+ Foreground(lipgloss.Color("#ffffff")).15+ Background(lipgloss.Color("#4a4a4a"))1616 1717 markedStyle = baseStyle.18- Foreground(lipgloss.Color("245"))18+ Foreground(lipgloss.Color("#ff6b6b")).19+ Background(lipgloss.Color("#2a1a1a"))1920 2021 emptyStyle = baseStyle.21- Foreground(lipgloss.Color("250"))22+ Foreground(lipgloss.Color("#333333")).23+ Background(lipgloss.Color("#1a1a1a"))2224 23- highlightStyle = baseStyle.24- Background(lipgloss.Color("205")).25- Foreground(lipgloss.Color("255"))25+ cursorStyle = baseStyle.26+ Bold(true).27+ Foreground(lipgloss.Color("#ffffff")).28+ Background(lipgloss.Color("#ff00ff"))29+30+ crosshairBG = lipgloss.Color("#252525")31+32+ hintStyle = baseStyle.33+ Foreground(lipgloss.Color("#888888"))34+35+ hintSatisfiedStyle = baseStyle.36+ Foreground(lipgloss.Color("#00ff00"))37+38+ nonoTitleStyle = lipgloss.NewStyle().39+ Bold(true).40+ Foreground(lipgloss.Color("#ffffff")).41+ Background(lipgloss.Color("#7B2FBE")).42+ Padding(0, 1)43+44+ nonoSolvedBadgeStyle = lipgloss.NewStyle().45+ Bold(true).46+ Foreground(lipgloss.Color("#00ff00"))47+48+ nonoStatusBarStyle = lipgloss.NewStyle().49+ Foreground(lipgloss.Color("#888888")).50+ MarginTop(1)2651 2752 cellWidth = 32853 @@ -35,22 +60,36 @@ var (
3560 }3661 3762 renderRuneMap = map[rune]string{38- filledTile: "⬤",39- markedTile: "⊗",40- emptyTile: "◯",63+ filledTile: "███",64+ markedTile: "✕",65+ emptyTile: " ",4166 }4267 )4368 44-func colHintView(c TomographyDefinition, height int) string {69+func colHintView(c TomographyDefinition, height int, current ...TomographyDefinition) string {70+ var hasCurrent bool71+ var curr TomographyDefinition72+ if len(current) > 0 {73+ hasCurrent = true74+ curr = current[0]75+ }76+4577 var renderedCols []string46- for _, hints := range c {78+ for i, hints := range c {4779 var colHints []string4880 for range height - len(hints) {4981 spacer := hintCellStyle.Render(" ")5082 colHints = append(colHints, spacer)5183 }84+85+ satisfied := hasCurrent && i < len(curr) && intSliceEqual(hints, curr[i])86+5287 for _, hint := range hints {53- hintCell := hintCellStyle.88+ style := hintStyle89+ if satisfied {90+ style = hintSatisfiedStyle91+ }92+ hintCell := style.Width(cellWidth).5493 Align(lipgloss.Center).5594 Render(fmt.Sprintf("%d", hint))5695 colHints = append(colHints, hintCell)@@ -60,15 +99,30 @@ func colHintView(c TomographyDefinition, height int) string {
6099 }61100 return lipgloss.JoinHorizontal(lipgloss.Top, renderedCols...)62101 }63-func rowHintView(r TomographyDefinition, width int) string {102+103+func rowHintView(r TomographyDefinition, width int, current ...TomographyDefinition) string {104+ var hasCurrent bool105+ var curr TomographyDefinition106+ if len(current) > 0 {107+ hasCurrent = true108+ curr = current[0]109+ }110+64111 var renderedRows []string65- for _, hints := range r {112+ for i, hints := range r {113+ satisfied := hasCurrent && i < len(curr) && intSliceEqual(hints, curr[i])114+115+ style := hintStyle116+ if satisfied {117+ style = hintSatisfiedStyle118+ }119+66120 var rowHints []string67121 for _, hint := range hints {68122 hintCell := fmt.Sprintf("%2d", hint)69123 rowHints = append(rowHints, hintCell)70124 }71- renderedRow := baseStyle.Width(width).125+ renderedRow := style.Width(width).72126 Align(lipgloss.Right).73127 Render(strings.Join(rowHints, " "))74128 renderedRows = append(renderedRows, renderedRow)@@ -77,34 +131,72 @@ func rowHintView(r TomographyDefinition, width int) string {
77131 return s78132 }79133 80-func gridView(g grid, c cursor) string {134+func gridView(g grid, c cursor, solved bool) string {81135 var rows []string82136 for y, row := range g {83- var rowBuider []string137+ var rowBuilder []string84138 for x, cell := range row {85- highlighted := x == c.x && y == c.y86- cell := tileView(cell, highlighted)87- rowBuider = append(rowBuider, cell)88-139+ isCursor := x == c.x && y == c.y140+ inCursorRow := y == c.y141+ inCursorCol := x == c.x142+ cell := tileView(cell, isCursor, inCursorRow, inCursorCol, solved)143+ rowBuilder = append(rowBuilder, cell)89144 }90- row := lipgloss.JoinHorizontal(lipgloss.Top, rowBuider...)145+ row := lipgloss.JoinHorizontal(lipgloss.Top, rowBuilder...)91146 rows = append(rows, row)92147 }93148 grid := lipgloss.JoinVertical(lipgloss.Left, rows...)94149 return grid95150 }96151 97-func tileView(val rune, h bool) string {152+func tileView(val rune, isCursor, inCursorRow, inCursorCol, solved bool) string {98153 s, ok := renderStyleMap[val]99154 if !ok {100155 s = renderStyleMap[emptyTile]101156 }102- if h {103- s = highlightStyle157+158+ if isCursor && !solved {159+ s = cursorStyle160+ } else if !solved && (inCursorRow || inCursorCol) {161+ // Apply crosshair background tint162+ s = s.Background(crosshairBG)163+ }164+165+ if solved {166+ // Brighten filled tiles when solved167+ if val == filledTile {168+ s = s.Background(lipgloss.Color("#2a6a2a"))169+ }104170 }171+105172 r, ok := renderRuneMap[val]106173 if !ok {107174 r = renderRuneMap[emptyTile]108175 }109176 return s.Width(cellWidth).AlignHorizontal(lipgloss.Center).Render(r)110177 }178+179+func nonoTitleBarView(modeName string, solved bool) string {180+ title := nonoTitleStyle.Render("Nonogram " + modeName)181+ if solved {182+ badge := nonoSolvedBadgeStyle.Render(" SOLVED")183+ return title + badge184+ }185+ return title186+}187+188+func nonoStatusBarView(_ KeyMap) string {189+ return nonoStatusBarStyle.Render("arrows/wasd: move z: fill x: mark bkspc: clear ctrl+n: menu ctrl+e: debug")190+}191+192+func intSliceEqual(a, b []int) bool {193+ if len(a) != len(b) {194+ return false195+ }196+ for i, v := range a {197+ if v != b[i] {198+ return false199+ }200+ }201+ return true202+}@@ -13,12 +13,6 @@ import (
1313 1414 const GRIDSIZE = 91515 16-const (17- cellBGColor = lipgloss.Color("249")18- providedFGColor = lipgloss.Color("229")19- cursorBGColor = lipgloss.Color("10")20-)21-2216 type cursor struct {2317 x, y int2418 }@@ -28,6 +22,7 @@ type Model struct {
2822 grid grid2923 provided []cell3024 keys KeyMap25+ modeName string3126 }3227 3328 // New creates a new sudoku game using the provided cell values.@@ -37,6 +32,7 @@ func New(mode SudokuMode, provided []cell, save ...string) (game.Gamer, error) {
3732 grid: g,3833 provided: provided,3934 keys: DefaultKeyMap,35+ modeName: mode.title,4036 }4137 return m, nil4238 }@@ -54,6 +50,8 @@ func (m Model) Update(msg tea.Msg) (game.Gamer, tea.Cmd) {
5450 case key.Matches(msg, m.keys.FillValue):5551 val, _ := strconv.Atoi(msg.String())5652 m.updateCell(val)53+ case key.Matches(msg, m.keys.ClearCell):54+ m.updateCell(0)5755 case key.Matches(msg, m.keys.Up):5856 if m.cursor.y > 0 {5957 m.cursor.y--@@ -80,43 +78,77 @@ func (m Model) Update(msg tea.Msg) (game.Gamer, tea.Cmd) {
8078 func (m *Model) updateCell(v int) {8179 c := &m.grid[m.cursor.y][m.cursor.x]8280 if slices.Contains(m.provided, *c) {83-81+ return8482 }8583 c.v = v8684 }8785 8886 // View implements game.Gamer.8987 func (m Model) View() string {90- var rows []string91- for _, row := range m.grid {92- var cells []string93- for _, cell := range row {94-95- cellStyle := lipgloss.NewStyle().Background(cellBGColor)96- if slices.Contains(m.provided, cell) {97- cellStyle = cellStyle.Foreground(providedFGColor)98- }99- if m.cursor.x == cell.x && m.cursor.y == cell.y {100- cellStyle = cellStyle.Background(cursorBGColor)101- }88+ title := titleBarView(m.modeName, m.isSolved())89+ grid := renderGrid(m)90+ status := statusBarView()10291 103- c := cellStyle.Render(fmt.Sprintf("%d", cell.v))104- cells = append(cells, c)105- }106- r := lipgloss.JoinHorizontal(lipgloss.Right, cells...)107- rows = append(rows, r)108- }109- return lipgloss.JoinVertical(lipgloss.Left, rows...)92+ return lipgloss.JoinVertical(lipgloss.Left, title, grid, status)11093 }11194 11295 // GetDebugInfo implements game.Gamer.11396 func (m Model) GetDebugInfo() string {114- return fmt.Sprintf(`115-# Sudoko116-Cursor (%dx%d)117-Provided Cells: '%v'118-`,97+ cursorCell := m.grid[m.cursor.y][m.cursor.x]98+ isProvided := slices.Contains(m.provided, cursorCell)99+ conflict := hasConflict(m, cursorCell, m.cursor.x, m.cursor.y)100+ solved := m.isSolved()101+102+ filledCount := 0103+ conflictCount := 0104+ for y := range GRIDSIZE {105+ for x := range GRIDSIZE {106+ c := m.grid[y][x]107+ if c.v != 0 {108+ filledCount++109+ }110+ if c.v != 0 && hasConflict(m, c, x, y) {111+ conflictCount++112+ }113+ }114+ }115+116+ status := "In Progress"117+ if solved {118+ status = "Solved"119+ }120+121+ s := fmt.Sprintf(122+ "# Sudoku\n\n"+123+ "## Game State\n\n"+124+ "| Property | Value |\n"+125+ "| :--- | :--- |\n"+126+ "| Status | %s |\n"+127+ "| Cursor | (%d, %d) |\n"+128+ "| Cell Value | %s |\n"+129+ "| Is Provided | %v |\n"+130+ "| Has Conflict | %v |\n"+131+ "| Cells Filled | %d / 81 |\n"+132+ "| Conflict Count | %d |\n"+133+ "| Provided Count | %d |\n",134+ status,119135 m.cursor.x, m.cursor.y,120- m.provided,136+ cellContent(cursorCell),137+ isProvided,138+ conflict,139+ filledCount,140+ conflictCount,141+ len(m.provided),121142 )143+144+ if len(m.provided) > 0 {145+ s += "\n## Provided Cells\n\n"146+ s += "| Row | Col | Value |\n"147+ s += "| :--- | :--- | :--- |\n"148+ for _, p := range m.provided {149+ s += fmt.Sprintf("| %d | %d | %d |\n", p.y, p.x, p.v)150+ }151+ }152+153+ return s122154 }@@ -52,3 +52,18 @@ func loadSave(def grid, optionalSave ...string) grid {
5252 func (c cell) String() string {5353 return fmt.Sprintf("r%dc%d=%d", c.x, c.y, c.v)5454 }55+56+func (m Model) isSolved() bool {57+ for y := range GRIDSIZE {58+ for x := range GRIDSIZE {59+ c := m.grid[y][x]60+ if c.v == 0 {61+ return false62+ }63+ if hasConflict(m, c, x, y) {64+ return false65+ }66+ }67+ }68+ return true69+}@@ -68,5 +68,8 @@ func (m Model) IsProvidedCell() bool {
6868 6969 // GetFullHelp implements game.Gamer.7070 func (m Model) GetFullHelp() [][]key.Binding {71- panic("unimplemented")71+ return [][]key.Binding{72+ {m.keys.Up, m.keys.Down, m.keys.Left, m.keys.Right},73+ {m.keys.FillValue, m.keys.ClearCell},74+ }7275 }@@ -0,0 +1,197 @@
1+package sudoku2+3+import (4+ "fmt"5+ "slices"6+ "strings"7+8+ "github.com/charmbracelet/lipgloss"9+)10+11+var (12+ titleStyle = lipgloss.NewStyle().13+ Bold(true).14+ Foreground(lipgloss.Color("#ffffff")).15+ Background(lipgloss.Color("#2E86AB")).16+ Padding(0, 1)17+18+ solvedBadgeStyle = lipgloss.NewStyle().19+ Bold(true).20+ Foreground(lipgloss.Color("#00ff00"))21+22+ emptyCellStyle = lipgloss.NewStyle().23+ Foreground(lipgloss.Color("#555555")).24+ Background(lipgloss.Color("#1a1a1a"))25+26+ providedCellStyle = lipgloss.NewStyle().27+ Bold(true).28+ Foreground(lipgloss.Color("#FFD700")).29+ Background(lipgloss.Color("#1a1a1a"))30+31+ userCellStyle = lipgloss.NewStyle().32+ Foreground(lipgloss.Color("#ffffff")).33+ Background(lipgloss.Color("#1a1a1a"))34+35+ cursorCellStyle = lipgloss.NewStyle().36+ Bold(true).37+ Foreground(lipgloss.Color("#ffffff")).38+ Background(lipgloss.Color("#ff00ff"))39+40+ conflictCellStyle = lipgloss.NewStyle().41+ Foreground(lipgloss.Color("#ff4444")).42+ Background(lipgloss.Color("#3a1111"))43+44+ crosshairBG = lipgloss.Color("#2a2a2a")45+46+ boxBorderFG = lipgloss.Color("#555555")47+48+ gridBorderStyle = lipgloss.NewStyle().49+ Border(lipgloss.RoundedBorder()).50+ BorderForeground(lipgloss.Color("#555555"))51+52+ gridBorderSolvedStyle = lipgloss.NewStyle().53+ Border(lipgloss.RoundedBorder()).54+ BorderForeground(lipgloss.Color("#00ff00"))55+56+ statusBarStyle = lipgloss.NewStyle().57+ Foreground(lipgloss.Color("#888888")).58+ MarginTop(1)59+60+ sudokuCellWidth = 261+)62+63+func renderGrid(m Model) string {64+ var rows []string65+66+ for y := range GRIDSIZE {67+ var cells []string68+ for x := range GRIDSIZE {69+ c := m.grid[y][x]70+ style := getCellStyle(m, c, x, y)71+ content := cellContent(c)72+ rendered := style.Width(sudokuCellWidth).Align(lipgloss.Center).Render(content)73+ cells = append(cells, rendered)74+75+ // Insert vertical box separator after columns 3 and 676+ if x == 2 || x == 5 {77+ sep := lipgloss.NewStyle().Foreground(boxBorderFG).Render("│")78+ cells = append(cells, sep)79+ }80+ }81+ row := lipgloss.JoinHorizontal(lipgloss.Top, cells...)82+ rows = append(rows, row)83+84+ // Insert horizontal box separator after rows 3 and 685+ if y == 2 || y == 5 {86+ sepLine := strings.Repeat("─", sudokuCellWidth)87+ var sepParts []string88+ for x := range GRIDSIZE {89+ sepParts = append(sepParts, sepLine)90+ if x == 2 || x == 5 {91+ sepParts = append(sepParts, "┼")92+ }93+ }94+ sep := lipgloss.NewStyle().Foreground(boxBorderFG).Render(strings.Join(sepParts, ""))95+ rows = append(rows, sep)96+ }97+ }98+99+ grid := lipgloss.JoinVertical(lipgloss.Left, rows...)100+101+ if m.isSolved() {102+ return gridBorderSolvedStyle.Render(grid)103+ }104+ return gridBorderStyle.Render(grid)105+}106+107+func getCellStyle(m Model, c cell, x, y int) lipgloss.Style {108+ // Priority: cursor > conflict > provided > user > empty109+ if m.cursor.x == x && m.cursor.y == y {110+ return cursorCellStyle111+ }112+113+ if c.v != 0 && hasConflict(m, c, x, y) {114+ return conflictCellStyle115+ }116+117+ isProvided := slices.Contains(m.provided, c)118+ inCursorRow := m.cursor.y == y119+ inCursorCol := m.cursor.x == x120+ inCursorBox := (x/3 == m.cursor.x/3) && (y/3 == m.cursor.y/3)121+ inCrosshair := inCursorRow || inCursorCol || inCursorBox122+123+ if isProvided {124+ s := providedCellStyle125+ if inCrosshair {126+ s = s.Background(crosshairBG)127+ }128+ return s129+ }130+131+ if c.v != 0 {132+ s := userCellStyle133+ if inCrosshair {134+ s = s.Background(crosshairBG)135+ }136+ return s137+ }138+139+ s := emptyCellStyle140+ if inCrosshair {141+ s = s.Background(crosshairBG)142+ }143+ return s144+}145+146+func cellContent(c cell) string {147+ if c.v == 0 {148+ return "·"149+ }150+ return fmt.Sprintf("%d", c.v)151+}152+153+func hasConflict(m Model, c cell, x, y int) bool {154+ if c.v == 0 {155+ return false156+ }157+158+ // Check row159+ for cx := range GRIDSIZE {160+ if cx != x && m.grid[y][cx].v == c.v {161+ return true162+ }163+ }164+165+ // Check column166+ for cy := range GRIDSIZE {167+ if cy != y && m.grid[cy][x].v == c.v {168+ return true169+ }170+ }171+172+ // Check 3x3 box173+ boxStartX := (x / 3) * 3174+ boxStartY := (y / 3) * 3175+ for by := boxStartY; by < boxStartY+3; by++ {176+ for bx := boxStartX; bx < boxStartX+3; bx++ {177+ if (bx != x || by != y) && m.grid[by][bx].v == c.v {178+ return true179+ }180+ }181+ }182+183+ return false184+}185+186+func titleBarView(modeName string, solved bool) string {187+ title := titleStyle.Render("Sudoku " + modeName)188+ if solved {189+ badge := solvedBadgeStyle.Render(" SOLVED")190+ return title + badge191+ }192+ return title193+}194+195+func statusBarView() string {196+ return statusBarStyle.Render("arrows/wasd: move 1-9: fill bkspc: clear ctrl+n: menu ctrl+e: debug")197+}@@ -3,24 +3,22 @@ package wordsearch
33 import (44 "math/rand"55 "strings"6- "time"76 )87 98 // Word list organized by length for difficulty filtering109 var wordsByLength = map[int][]string{11- 3: {"CAT", "DOG", "SUN", "HAT", "BAT", "RUN", "FUN", "HOT", "TOP", "BOX"},12- 4: {"TREE", "BIRD", "FISH", "MOON", "STAR", "WIND", "RAIN", "SNOW", "BLUE", "JUMP", "PLAY", "SWIM", "WALK", "TALK", "BOOK", "DOOR", "FIRE", "GOLD", "HAND", "KING"},13- 5: {"HAPPY", "WATER", "LIGHT", "BROWN", "GREEN", "HOUSE", "MUSIC", "OCEAN", "PLANT", "QUICK", "RIVER", "SMILE", "TIGER", "UNDER", "WATCH", "YOUTH", "ZEBRA", "APPLE", "BEACH", "CLOUD"},14- 6: {"FRIEND", "GARDEN", "JUNGLE", "KITTEN", "LAPTOP", "MONKEY", "ORANGE", "PENCIL", "PUZZLE", "QUEEN", "RABBIT", "SCHOOL", "TRAVEL", "WINTER", "YELLOW", "ANIMAL", "BASKET", "CASTLE", "DRAGON", "FOREST"},15- 7: {"ABILITY", "BALANCE", "CAPTAIN", "DIAMOND", "ELECTRIC", "FREEDOM", "GIRAFFE", "HARMONY", "JOURNEY", "KITCHEN", "LIBRARY", "MONSTER", "MYSTERY", "PICTURE", "QUALITY", "RAINBOW", "SUCCESS", "THUNDER", "VICTORY", "WEATHER"},16- 8: {"ADVENTURE", "BIRTHDAY", "CALENDAR", "DAUGHTER", "ELEPHANT", "FOOTBALL", "GIGANTIC", "HOSPITAL", "INFINITY", "KANGAROO", "LANGUAGE", "MOUNTAIN", "NEIGHBOR", "PARADISE", "QUESTION", "SANDWICH", "TREASURE", "UNIVERSE", "VACATION", "WILDLIFE"},17- 9: {"AMBITIOUS", "BEAUTIFUL", "CHALLENGE", "DANGEROUS", "EDUCATION", "FANTASTIC", "GENERATOR", "HALLOWEEN", "IMPORTANT", "KNOWLEDGE", "LANDSCAPE", "MEMORABLE", "NECESSARY", "CHOCOLATE", "PINEAPPLE", "RASPBERRY", "SUBMARINE", "TELEPHONE", "CROCODILE", "BUTTERFLY"},10+ 3: {"CAT", "DOG", "SUN", "HAT", "BAT", "RUN", "FUN", "HOT", "TOP", "BOX"},11+ 4: {"TREE", "BIRD", "FISH", "MOON", "STAR", "WIND", "RAIN", "SNOW", "BLUE", "JUMP", "PLAY", "SWIM", "WALK", "TALK", "BOOK", "DOOR", "FIRE", "GOLD", "HAND", "KING"},12+ 5: {"HAPPY", "WATER", "LIGHT", "BROWN", "GREEN", "HOUSE", "MUSIC", "OCEAN", "PLANT", "QUICK", "RIVER", "SMILE", "TIGER", "UNDER", "WATCH", "YOUTH", "ZEBRA", "APPLE", "BEACH", "CLOUD"},13+ 6: {"FRIEND", "GARDEN", "JUNGLE", "KITTEN", "LAPTOP", "MONKEY", "ORANGE", "PENCIL", "PUZZLE", "QUEEN", "RABBIT", "SCHOOL", "TRAVEL", "WINTER", "YELLOW", "ANIMAL", "BASKET", "CASTLE", "DRAGON", "FOREST"},14+ 7: {"ABILITY", "BALANCE", "CAPTAIN", "DIAMOND", "ELECTRIC", "FREEDOM", "GIRAFFE", "HARMONY", "JOURNEY", "KITCHEN", "LIBRARY", "MONSTER", "MYSTERY", "PICTURE", "QUALITY", "RAINBOW", "SUCCESS", "THUNDER", "VICTORY", "WEATHER"},15+ 8: {"ADVENTURE", "BIRTHDAY", "CALENDAR", "DAUGHTER", "ELEPHANT", "FOOTBALL", "GIGANTIC", "HOSPITAL", "INFINITY", "KANGAROO", "LANGUAGE", "MOUNTAIN", "NEIGHBOR", "PARADISE", "QUESTION", "SANDWICH", "TREASURE", "UNIVERSE", "VACATION", "WILDLIFE"},16+ 9: {"AMBITIOUS", "BEAUTIFUL", "CHALLENGE", "DANGEROUS", "EDUCATION", "FANTASTIC", "GENERATOR", "HALLOWEEN", "IMPORTANT", "KNOWLEDGE", "LANDSCAPE", "MEMORABLE", "NECESSARY", "CHOCOLATE", "PINEAPPLE", "RASPBERRY", "SUBMARINE", "TELEPHONE", "CROCODILE", "BUTTERFLY"},1817 10: {"ACCOMPLISH", "BACKGROUND", "BASKETBALL", "CELEBRATION", "DELIGHTFUL", "EVERYTHING", "GENERATION", "JAVASCRIPT", "MOTIVATION", "WATERMELON", "STRAWBERRY", "TRAMPOLINE", "SKATEBOARD", "GREENHOUSE", "UNDERSTAND", "California", "GIRLFRIEND", "TOURNAMENT", "PLAYGROUND", "REVOLUTION"},1918 }2019 2120 // GenerateWordSearch creates a new word search grid with the specified parameters2221 func GenerateWordSearch(width, height, wordCount, minLen, maxLen int, allowedDirs []Direction) (grid, []Word) {23- rand.Seed(time.Now().UnixNano())2422 2523 g := createEmptyGrid(height, width)2624 words := selectWords(wordCount, minLen, maxLen)@@ -68,7 +66,7 @@ func tryPlaceWord(g grid, text string, allowedDirs []Direction, maxAttempts int)
6866 height := len(g)6967 width := len(g[0])7068 71- for attempt := 0; attempt < maxAttempts; attempt++ {69+ for range maxAttempts {7270 // Random starting position7371 x := rand.Intn(width)7472 y := rand.Intn(height)@@ -87,7 +85,7 @@ func tryPlaceWord(g grid, text string, allowedDirs []Direction, maxAttempts int)
8785 8886 // Check if placement is valid (no conflicts, allow letter overlap)8987 valid := true90- for i := 0; i < len(text); i++ {88+ for i := range len(text) {9189 cx := x + dx*i9290 cy := y + dy*i9391 existing := g.Get(cx, cy)@@ -103,7 +101,7 @@ func tryPlaceWord(g grid, text string, allowedDirs []Direction, maxAttempts int)
103101 }104102 105103 // Place the word106- for i := 0; i < len(text); i++ {104+ for i := range len(text) {107105 cx := x + dx*i108106 cy := y + dy*i109107 g.Set(cx, cy, rune(text[i]))