sudoku_wizard 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/sudoku_wizard.rb +139 -0
- metadata +45 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 24f20683ebe7c44ef816dee6fde09dfb5a0bbb3b
|
4
|
+
data.tar.gz: a515b61f2886cdd5d8b67a5e89430dd9f7acac90
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 66addfe6da9ffd1fd38d30193232fc27fcb60728404e02ae7e11263e77b82bb84ebd142c44c81ce96ddfe63a849c6ba4e25319135d3645048c195df3899535d2
|
7
|
+
data.tar.gz: 23f01f4602c93b54e8a2901ca77f6e1352d7065791267b5cce3da59ce319f4b9dbfa078afc9f05bbf8fbd7a26fb889ba65291182946f1389cf419239e6694d96
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'pry'
|
2
|
+
|
3
|
+
class Sudoku
|
4
|
+
attr_accessor :starting_board, :solution, :removed_values, :difficulty
|
5
|
+
|
6
|
+
BLANK_BOARD = [
|
7
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
8
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
9
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
10
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
11
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
12
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
13
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
14
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
15
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0]
|
16
|
+
]
|
17
|
+
|
18
|
+
def initialize(holes = 30, fail_safe = 3.0)
|
19
|
+
@start_time = Time.now
|
20
|
+
@fail_safe = fail_safe
|
21
|
+
holes > 64 ? 64 : holes
|
22
|
+
self.solution = new_solved_board
|
23
|
+
self.removed_values, self.starting_board = poke_holes(self.solution.map(&:clone), holes)
|
24
|
+
self.difficulty = holes
|
25
|
+
end
|
26
|
+
|
27
|
+
def new_solved_board
|
28
|
+
new_board = BLANK_BOARD.map(&:clone)
|
29
|
+
solve(new_board)
|
30
|
+
new_board
|
31
|
+
end
|
32
|
+
|
33
|
+
def solve (puzzle_matrix)
|
34
|
+
empty_cell = find_next_empty_cell(puzzle_matrix)
|
35
|
+
return puzzle_matrix if !empty_cell #If no empty cells, we are done. Return the completed puzzle
|
36
|
+
|
37
|
+
# Fill in the empty cell
|
38
|
+
for num in (1..9).to_a.shuffle do
|
39
|
+
abort "Puzzle Generation timed out after #{@fail_safe} seconds. Please Retry" if (Time.now - @start_time > @fail_safe)
|
40
|
+
if safe(puzzle_matrix, empty_cell, num) # For a number, check if it safe to place that number in the empty cell
|
41
|
+
puzzle_matrix[empty_cell[:row_i]][empty_cell[:col_i]] = num # if safe, place number
|
42
|
+
return puzzle_matrix if solve(puzzle_matrix) # Recursively call solve method again.
|
43
|
+
puzzle_matrix[empty_cell[:row_i]][empty_cell[:col_i]] = 0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
return false #If unable to place a number, return false, trigerring previous iteration to move to next number
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_next_empty_cell(puzzle_matrix)
|
50
|
+
# Find the coordinates of the next unsolved cell
|
51
|
+
empty_cell = {row_i:"",col_i:""}
|
52
|
+
for row in puzzle_matrix do
|
53
|
+
next_zero_index = row.find_index(0)
|
54
|
+
empty_cell[:row_i] = puzzle_matrix.find_index(row)
|
55
|
+
empty_cell[:col_i] = next_zero_index
|
56
|
+
return empty_cell if empty_cell[:col_i]
|
57
|
+
end
|
58
|
+
|
59
|
+
return false
|
60
|
+
end
|
61
|
+
|
62
|
+
def safe(puzzle_matrix, empty_cell, num)
|
63
|
+
row_safe(puzzle_matrix, empty_cell, num) &&
|
64
|
+
col_safe(puzzle_matrix, empty_cell, num) &&
|
65
|
+
box_safe(puzzle_matrix, empty_cell, num)
|
66
|
+
end
|
67
|
+
|
68
|
+
def row_safe (puzzle_matrix, empty_cell, num)
|
69
|
+
return false if puzzle_matrix[ empty_cell[:row_i] ].find_index(num)
|
70
|
+
return true
|
71
|
+
end
|
72
|
+
|
73
|
+
def col_safe (puzzle_matrix, empty_cell, num)
|
74
|
+
return false if puzzle_matrix.any?{|row| row[ empty_cell[:col_i] ] == num}
|
75
|
+
return true
|
76
|
+
end
|
77
|
+
|
78
|
+
def box_safe (puzzle_matrix, empty_cell, num)
|
79
|
+
box_start_row = (empty_cell[:row_i] - (empty_cell[:row_i] % 3))
|
80
|
+
box_start_col = (empty_cell[:col_i] - (empty_cell[:col_i] % 3))
|
81
|
+
|
82
|
+
(0..2).to_a.each do |box_row|
|
83
|
+
(0..2).to_a.each do |box_col|
|
84
|
+
return false if puzzle_matrix[box_start_row + box_row][box_start_col + box_col] == num
|
85
|
+
end
|
86
|
+
end
|
87
|
+
return true
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
def poke_holes(puzzle_matrix, holes)
|
92
|
+
removed_values = []
|
93
|
+
|
94
|
+
while removed_values.length < holes
|
95
|
+
row_i = (0..8).to_a.sample
|
96
|
+
col_i = (0..8).to_a.sample
|
97
|
+
|
98
|
+
next if (puzzle_matrix[row_i][col_i] == 0)
|
99
|
+
removed_values.push({row_i: row_i, col_i: col_i, val: puzzle_matrix[row_i][col_i] })
|
100
|
+
puzzle_matrix[row_i][col_i] = 0
|
101
|
+
|
102
|
+
proposed_board = puzzle_matrix.map(&:clone)
|
103
|
+
if !solve( proposed_board )
|
104
|
+
puzzle_matrix[row_i][col_i] = removed_values.pop[:val]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
[removed_values, puzzle_matrix]
|
109
|
+
end
|
110
|
+
|
111
|
+
def render(board_name)
|
112
|
+
b = self.send(board_name).map{|row| row.map{|col| col == 0 ? " " : col} }
|
113
|
+
puts "
|
114
|
+
┏━━━┳━━━┳━━━┱───┬───┬───┲━━━┳━━━┳━━━┓
|
115
|
+
┃ #{b[0][0]} ┃ #{b[0][1]} ┃ #{b[0][2]} ┃ #{b[0][3]} │ #{b[0][4]} │ #{b[0][5]} ┃ #{b[0][6]} ┃ #{b[0][7]} ┃ #{b[0][8]} ┃
|
116
|
+
┣━━━╋━━━╋━━━╉───┼───┼───╊━━━╋━━━╋━━━┫
|
117
|
+
┃ #{b[1][0]} ┃ #{b[1][1]} ┃ #{b[1][2]} ┃ #{b[1][3]} │ #{b[1][4]} │ #{b[1][5]} ┃ #{b[1][6]} ┃ #{b[1][7]} ┃ #{b[1][8]} ┃
|
118
|
+
┣━━━╋━━━╋━━━╉───┼───┼───╊━━━╋━━━╋━━━┫
|
119
|
+
┃ #{b[2][0]} ┃ #{b[2][1]} ┃ #{b[2][2]} ┃ #{b[2][3]} │ #{b[2][4]} │ #{b[2][5]} ┃ #{b[2][6]} ┃ #{b[2][7]} ┃ #{b[2][8]} ┃
|
120
|
+
┡━━━╇━━━╇━━━╋━━━╈━━━╈━━━╋━━━╇━━━╇━━━┩
|
121
|
+
│ #{b[3][0]} │ #{b[3][1]} │ #{b[3][2]} ┃ #{b[3][3]} ┃ #{b[3][4]} ┃ #{b[3][5]} ┃ #{b[3][6]} │ #{b[3][7]} │ #{b[3][8]} │
|
122
|
+
├───┼───┼───╊━━━╋━━━╋━━━╉───┼───┼───┤
|
123
|
+
│ #{b[4][0]} │ #{b[4][1]} │ #{b[4][2]} ┃ #{b[4][3]} ┃ #{b[4][4]} ┃ #{b[4][5]} ┃ #{b[4][6]} │ #{b[4][7]} │ #{b[4][8]} │
|
124
|
+
├───┼───┼───╊━━━╋━━━╋━━━╉───┼───┼───┤
|
125
|
+
│ #{b[5][0]} │ #{b[5][1]} │ #{b[5][2]} ┃ #{b[5][3]} ┃ #{b[5][4]} ┃ #{b[5][5]} ┃ #{b[5][6]} │ #{b[5][7]} │ #{b[5][8]} │
|
126
|
+
┢━━━╈━━━╈━━━╋━━━╇━━━╇━━━╋━━━╈━━━╈━━━┪
|
127
|
+
┃ #{b[6][0]} ┃ #{b[6][1]} ┃ #{b[6][2]} ┃ #{b[6][3]} │ #{b[6][4]} │ #{b[6][5]} ┃ #{b[6][6]} ┃ #{b[6][7]} ┃ #{b[6][8]} ┃
|
128
|
+
┣━━━╋━━━╋━━━╉───┼───┼───╊━━━╋━━━╋━━━┫
|
129
|
+
┃ #{b[7][0]} ┃ #{b[7][1]} ┃ #{b[7][2]} ┃ #{b[7][3]} │ #{b[7][4]} │ #{b[7][5]} ┃ #{b[7][6]} ┃ #{b[7][7]} ┃ #{b[7][8]} ┃
|
130
|
+
┣━━━╋━━━╋━━━╉───┼───┼───╊━━━╋━━━╋━━━┫
|
131
|
+
┃ #{b[8][0]} ┃ #{b[8][1]} ┃ #{b[8][2]} ┃ #{b[8][3]} │ #{b[8][4]} │ #{b[8][5]} ┃ #{b[8][6]} ┃ #{b[8][7]} ┃ #{b[8][8]} ┃
|
132
|
+
┗━━━┻━━━┻━━━┹───┴───┴───┺━━━┻━━━┻━━━┛
|
133
|
+
"
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
binding.pry
|
139
|
+
false
|
metadata
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sudoku_wizard
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel Sasse
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-02-17 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Uses backtracking algorithm to generate random boards of specified difficulty
|
14
|
+
and find solutions
|
15
|
+
email: dsasse07@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/sudoku_wizard.rb
|
21
|
+
homepage: https://rubygems.org/gems/sudoku-wizard
|
22
|
+
licenses:
|
23
|
+
- MIT
|
24
|
+
metadata: {}
|
25
|
+
post_install_message:
|
26
|
+
rdoc_options: []
|
27
|
+
require_paths:
|
28
|
+
- lib
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
requirements: []
|
40
|
+
rubyforge_project:
|
41
|
+
rubygems_version: 2.6.1
|
42
|
+
signing_key:
|
43
|
+
specification_version: 4
|
44
|
+
summary: Sudoku board generator & solver
|
45
|
+
test_files: []
|