-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* wip sudoku engine; still buggy for expert puzzle * engine update; still buggy expert mode * better expert support with re-attempts after 3 sec
- Loading branch information
Showing
11 changed files
with
240 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,3 +13,5 @@ | |
|
||
# Dependency directories (remove the comment below to include it) | ||
# vendor/ | ||
|
||
sku |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
r5.a81afe3 | ||
r6.971f844 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Sudoku Engine | ||
This package is a **non** efficient implementation of a sudoku solver and generator. It is ~~stolen~~ ported from [this python implementation](https://www.101computing.net/sudoku-generator-algorithm/), all credits go to the author. It is a simple recursive and backtracking engine, easy to understand and read. | ||
|
||
## Motivation | ||
This minimal implementation was done due to the lack of a package that simply has the two sensible features needed by a sudoku engine: | ||
1. can both solve & generate | ||
2. do generate a one-solution puzzle | ||
|
||
without useless features or generally cumbersome development experience (e.g. overly complicated structures, interfaces, initialization methods, etc). | ||
|
||
If you need the aforementioned features this package is not the right one, since it only does point 1 and 2. | ||
|
||
If you need a super efficient implementation this package is not the right one since it is aimed to be simple at the expense of speed; if you need something like that you can use/write one that leverages a more performant algorithm such as dancing links (dlx). | ||
|
||
## Usage | ||
```go | ||
sudoku := sudoku.New(sudoku.EASY) // also available: MEDIUM, HARD, EXPERT | ||
|
||
fmt.Println(sudoku.Puzzle) // unsolved sudoku [81]int | ||
fmt.Println(sudoku.Answer) // solved sudoku [81]int | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package sudoku | ||
|
||
import ( | ||
"math/rand" | ||
"time" | ||
) | ||
|
||
const ( | ||
// available sudoku difficulties | ||
EASY = 42 | ||
MEDIUM = 36 | ||
HARD = 27 | ||
EXPERT = 25 | ||
// time constraints | ||
MAX_ATTEMPTS = 5 | ||
MAX_EXEC_TIME = 3 | ||
// sudoku board size | ||
SUDOKU_LENGTH = 9 | ||
SUDOKU_SIZE = SUDOKU_LENGTH * SUDOKU_LENGTH | ||
) | ||
|
||
type Sudoku struct { | ||
Puzzle [SUDOKU_SIZE]int | ||
Answer [SUDOKU_SIZE]int | ||
} | ||
|
||
func New(difficulty int) *Sudoku { | ||
for i := 0; i < MAX_ATTEMPTS; i++ { | ||
if s, ok := newWithTimer(difficulty); ok { | ||
return s | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func newWithTimer(difficulty int) (*Sudoku, bool) { | ||
s := &Sudoku{} | ||
|
||
done := make(chan struct{}) | ||
var ok bool | ||
|
||
go func() { | ||
s = &Sudoku{} | ||
fill(&s.Puzzle) | ||
s.Answer = s.Puzzle | ||
s.eraseSome(difficulty) | ||
close(done) | ||
}() | ||
|
||
select { | ||
case <-done: | ||
ok = true | ||
case <-time.After(time.Second * MAX_EXEC_TIME): | ||
} | ||
|
||
return s, ok | ||
} | ||
|
||
func (s *Sudoku) eraseSome(difficulty int) { | ||
for erased := 0; SUDOKU_SIZE-erased > difficulty; { | ||
idx := 0 | ||
for s.Puzzle[idx] == 0 { | ||
rand.Seed(time.Now().UnixNano()) | ||
idx = rand.Intn(SUDOKU_SIZE) | ||
} | ||
|
||
copyGrid := s.Puzzle | ||
copyGrid[idx] = 0 | ||
|
||
count := 0 | ||
solve(©Grid, &count) | ||
if count == 1 { | ||
s.Puzzle[idx] = 0 | ||
erased++ | ||
} | ||
} | ||
} | ||
|
||
func solve(grid *[SUDOKU_SIZE]int, count *int) { | ||
if *count > 1 { // no need to go further | ||
return | ||
} | ||
|
||
for idx := 0; idx < SUDOKU_SIZE; idx++ { | ||
if grid[idx] == 0 { | ||
for n := 1; n <= SUDOKU_LENGTH; n++ { | ||
if isValid(grid, idx, n) { | ||
grid[idx] = n | ||
if checkFull(grid) { | ||
*count++ | ||
} | ||
solve(grid, count) | ||
grid[idx] = 0 | ||
} | ||
} | ||
break | ||
} | ||
} | ||
} | ||
|
||
func fill(grid *[SUDOKU_SIZE]int) bool { | ||
numberList := [SUDOKU_LENGTH]int{1, 2, 3, 4, 5, 6, 7, 8, 9} | ||
|
||
for idx := 0; idx < SUDOKU_SIZE; idx++ { | ||
if grid[idx] == 0 { | ||
rand.Seed(time.Now().UnixNano()) | ||
rand.Shuffle(len(numberList), func(i, j int) { | ||
numberList[i], numberList[j] = numberList[j], numberList[i] | ||
}) | ||
|
||
for _, n := range numberList { | ||
if isValid(grid, idx, n) { | ||
grid[idx] = n | ||
if checkFull(grid) || fill(grid) { | ||
return true | ||
} | ||
grid[idx] = 0 | ||
} | ||
} | ||
break | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func checkFull(grid *[SUDOKU_SIZE]int) bool { | ||
for i := 0; i < SUDOKU_SIZE; i++ { | ||
if grid[i] == 0 { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
func isValid(grid *[SUDOKU_SIZE]int, idx, n int) bool { | ||
// check if num is valid in row, col, and 3x3 box | ||
row := idx / SUDOKU_LENGTH | ||
col := idx % SUDOKU_LENGTH | ||
for i := 0; i < SUDOKU_LENGTH; i++ { | ||
if grid[row*SUDOKU_LENGTH+i] == n || | ||
grid[i*SUDOKU_LENGTH+col] == n || | ||
grid[((row/3)*3+i/3)*SUDOKU_LENGTH+((col/3)*3+i%3)] == n { | ||
return false | ||
} | ||
} | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package sudoku | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
) | ||
|
||
func TestSudoku(t *testing.T) { | ||
t.Log("TestSudoku") | ||
var sudoku Sudoku | ||
modes := map[int]string{ | ||
EASY: "easy", | ||
MEDIUM: "medium", | ||
HARD: "hard", | ||
EXPERT: "expert", | ||
} | ||
|
||
for d, mode := range modes { | ||
t.Log("Mode:", mode) | ||
sudoku = *New(d) | ||
t.Log(printSudoku(&sudoku)) | ||
} | ||
} | ||
|
||
func printSudoku(s *Sudoku) string { | ||
board := "\n" | ||
for i := 0; i < 81; i = i + 9 { | ||
board += fmt.Sprintf("%d\n", s.Puzzle[i:i+9]) | ||
} | ||
board += "\n" | ||
for i := 0; i < 81; i = i + 9 { | ||
board += fmt.Sprintf("%d\n", s.Answer[i:i+9]) | ||
} | ||
notErased := 0 | ||
for i := 0; i < 81; i++ { | ||
if s.Puzzle[i] == 0 { | ||
board += "." | ||
} else { | ||
board += fmt.Sprintf("%d", s.Puzzle[i]) | ||
notErased++ | ||
} | ||
} | ||
board += fmt.Sprintf("\nNot erased: %d", notErased) | ||
return board | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters