clone

fifteen puzzle games for the terminal, with daily puzzles, weekly ladders and xp, built on bubble tea

updated word search help text. implemented sudoku generator

Dami committed 8f65ed8
commit
8f65ed82faf045dab226d344dfc2caa453e36138
parent
7915414b15131915fe27634e11e95d6a17042fee
author
Dami <dami@Damis-MacBook-Air.local>
3 files changed, +127 -15
Msudoku/cell.go13---
Asudoku/generator.go115++++++++++++++++++++++++++++++
Mwordsearch/style.go14+++-
M sudoku/cell.go +0 -13
A sudoku/generator.go +115 -0
@@ -0,0 +1,115 @@
1+package sudoku2+3+import "math/rand/v2"4+5+// isValid checks whether placing val at (x, y) in the grid is valid.6+func isValid(g *grid, val, x, y int) bool {7+ for i := range GRIDSIZE {8+ if i != x && g[y][i].v == val {9+ return false10+ }11+ if i != y && g[i][x].v == val {12+ return false13+ }14+ }15+ boxX, boxY := (x/3)*3, (y/3)*316+ for by := boxY; by < boxY+3; by++ {17+ for bx := boxX; bx < boxX+3; bx++ {18+ if (bx != x || by != y) && g[by][bx].v == val {19+ return false20+ }21+ }22+ }23+ return true24+}25+26+// fillGrid fills an empty grid with a random valid sudoku solution using backtracking.27+func fillGrid(g *grid) bool {28+ for y := range GRIDSIZE {29+ for x := range GRIDSIZE {30+ if g[y][x].v == 0 {31+ order := rand.Perm(GRIDSIZE)32+ for _, i := range order {33+ val := i + 134+ if isValid(g, val, x, y) {35+ g[y][x].v = val36+ if fillGrid(g) {37+ return true38+ }39+ g[y][x].v = 040+ }41+ }42+ return false43+ }44+ }45+ }46+ return true47+}48+49+// countSolutions counts solutions of the grid up to limit using backtracking.50+func countSolutions(g *grid, limit int) int {51+ for y := range GRIDSIZE {52+ for x := range GRIDSIZE {53+ if g[y][x].v == 0 {54+ count := 055+ for val := 1; val <= GRIDSIZE; val++ {56+ if isValid(g, val, x, y) {57+ g[y][x].v = val58+ count += countSolutions(g, limit-count)59+ g[y][x].v = 060+ if count >= limit {61+ return count62+ }63+ }64+ }65+ return count66+ }67+ }68+ }69+ return 170+}71+72+// GenerateProvidedCells generates a random sudoku puzzle with the number of73+// provided cells determined by the mode's ProvidedCount.74+func GenerateProvidedCells(m SudokuMode) []cell {75+ g := newGrid(nil)76+ fillGrid(&g)77+78+ // Build shuffled list of all positions79+ type pos struct{ x, y int }80+ positions := make([]pos, 0, GRIDSIZE*GRIDSIZE)81+ for y := range GRIDSIZE {82+ for x := range GRIDSIZE {83+ positions = append(positions, pos{x, y})84+ }85+ }86+ rand.Shuffle(len(positions), func(i, j int) {87+ positions[i], positions[j] = positions[j], positions[i]88+ })89+90+ // Iteratively remove cells, ensuring unique solution91+ filled := GRIDSIZE * GRIDSIZE92+ for _, p := range positions {93+ if filled <= m.ProvidedCount {94+ break95+ }96+ saved := g[p.y][p.x].v97+ g[p.y][p.x].v = 098+ if countSolutions(&g, 2) != 1 {99+ g[p.y][p.x].v = saved100+ } else {101+ filled--102+ }103+ }104+105+ // Collect remaining filled cells as provided hints106+ var cells []cell107+ for y := range GRIDSIZE {108+ for x := range GRIDSIZE {109+ if g[y][x].v != 0 {110+ cells = append(cells, g[y][x])111+ }112+ }113+ }114+ return cells115+}
M wordsearch/style.go +12 -2