sudoku_wizard 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +5 -5
  2. data/lib/sudoku_wizard.rb +106 -19
  3. metadata +3 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ea6dbb8a08374d88e71ff0fd4df196405aed9796
4
- data.tar.gz: e36e4a7bcd2df738efde63028f8ee73d5cb5f4e9
2
+ SHA256:
3
+ metadata.gz: 2163e26041c92743bf1f60d8ee716aa538187de8118d1595f08d2d18923c2197
4
+ data.tar.gz: 4ece90b2e7af5b7198f839ea9060be89af8a04a89ce5771517097a20ffb51e6b
5
5
  SHA512:
6
- metadata.gz: a95ca5590169c33fe40b0932b790d74bf435f1e260f87924a1afb978e2e99a6727f18d0daf0ebf641390d79dd4898527a3bdb4624a8a01cdbab2752f2a4bb6f3
7
- data.tar.gz: 15aaa232a7b86c8821ecaabd197937df6aadb43aa03c9bfe1e595ba1ca75493bc0686616092c25949a967b0041b48c06fbc51a7cfae6c5c805558716764957b5
6
+ metadata.gz: 4949821c3b59cfcf655f7976c6a6839ab06b03d26e898979d7cbd8f55c27b49bae521d98eab6e7c2e805a9db9cc691de5eb3e2050da2879d9b1c457838928306
7
+ data.tar.gz: be87b9700ce51e10b95900a572713cc2794474296c0690270478b3d0261717c56d7ad0fe524f8feddc0acbd8992c17da38bee4fdc40c828abac24594f7deb7df
data/lib/sudoku_wizard.rb CHANGED
@@ -17,11 +17,11 @@ class Sudoku
17
17
 
18
18
  def initialize( holes = 40, status_messages = false )
19
19
  start_time = Time.now
20
- holes > 64 ? 64 : holes
20
+ self.difficulty = holes > 54 ? 54 : holes
21
21
  @status_messages = status_messages
22
22
 
23
23
  puts "Generating Game..." if @status_messages
24
- generate_game(holes)
24
+ generate_game
25
25
 
26
26
  return if !@status_messages
27
27
  puts "Board Generated in"
@@ -29,16 +29,17 @@ class Sudoku
29
29
  puts " #{ Time.now - start_time } seconds"
30
30
  end
31
31
 
32
- def generate_game(holes)
32
+ def generate_game
33
33
  begin
34
34
  # @start_time = Time.now
35
35
  @iteration_counter = 0
36
+ @poke_counter = 0
36
37
  self.solution = new_solved_board
37
- self.removed_values, self.starting_board = poke_holes(self.solution.map(&:clone), holes)
38
- self.difficulty = holes
39
- rescue
38
+ self.starting_board = poke_holes(self.solution.map(&:clone))
39
+ rescue => error
40
+ puts error
40
41
  puts "#{ format_number(@iteration_counter) } iterations, Restarting" if @status_messages
41
- generate_game(holes)
42
+ generate_game
42
43
  end
43
44
  end
44
45
 
@@ -55,7 +56,7 @@ class Sudoku
55
56
  # Fill in the empty cell
56
57
  for num in (1..9).to_a.shuffle do
57
58
  @iteration_counter += 1
58
- raise if (@iteration_counter > 1_000_000)
59
+ raise "too make buil iterations" if (@iteration_counter > 1_000_000)
59
60
  if safe(puzzle_matrix, empty_cell, num) # For a number, check if it safe to place that number in the empty cell
60
61
  puzzle_matrix[empty_cell[:row_i]][empty_cell[:col_i]] = num # if safe, place number
61
62
  return puzzle_matrix if solve(puzzle_matrix) # Recursively call solve method again.
@@ -105,24 +106,26 @@ class Sudoku
105
106
  end
106
107
 
107
108
 
108
- def poke_holes(puzzle_matrix, holes)
109
- removed_values = []
109
+ def poke_holes(puzzle_matrix)
110
+ self.removed_values = []
111
+ val = (0...81).to_a.shuffle
110
112
 
111
- while removed_values.length < holes
112
- row_i = (0..8).to_a.sample
113
- col_i = (0..8).to_a.sample
113
+ while self.removed_values.length < self.difficulty
114
+ next_val = val.pop
115
+ raise "impossible game" if !next_val
114
116
 
117
+ row_i = next_val / 9
118
+ col_i = next_val % 9
119
+
115
120
  next if (puzzle_matrix[row_i][col_i] == 0)
116
- removed_values.push({row_i: row_i, col_i: col_i, val: puzzle_matrix[row_i][col_i] })
121
+ self.removed_values.push({row_i: row_i, col_i: col_i, val: puzzle_matrix[row_i][col_i] })
117
122
  puzzle_matrix[row_i][col_i] = 0
118
-
123
+
119
124
  proposed_board = puzzle_matrix.map(&:clone)
120
- if !solve( proposed_board )
121
- puzzle_matrix[row_i][col_i] = removed_values.pop[:val]
122
- end
125
+ puzzle_matrix[row_i][col_i] = self.removed_values.pop[:val] if multiple_solutions?( proposed_board )
123
126
  end
124
127
 
125
- [removed_values, puzzle_matrix]
128
+ puzzle_matrix
126
129
  end
127
130
 
128
131
  def render(board_name)
@@ -151,6 +154,63 @@ class Sudoku
151
154
  end
152
155
 
153
156
  private
157
+ def bring_index_to_front(index, array)
158
+ starting_point = array.slice(index)
159
+ array.delete_at(index)
160
+ array.unshift(starting_point)
161
+ end
162
+
163
+
164
+ ###############
165
+ def multiple_solutions?(board_to_check)
166
+ possible_solutions = []
167
+ empty_cell_array = empty_cell_coords(board_to_check)
168
+ empty_cell_array.each_with_index do |coord, index|
169
+ board_clone = board_to_check.map{|row| row.map{|col| col}}
170
+ empty_cell_array_clone = empty_cell_array.map{|coord| coord}
171
+ bring_index_to_front(index, empty_cell_array_clone)
172
+ this_solution = fill_from_array( board_clone, empty_cell_array_clone )
173
+ possible_solutions.push(this_solution)
174
+
175
+ return true if possible_solutions.uniq.length > 1
176
+ end
177
+ return false
178
+ end
179
+
180
+ def fill_from_array(board, empty_cell_array)
181
+ empty_cell = next_still_empty_cell(board, empty_cell_array)
182
+ return board if !empty_cell
183
+ for num in (1..9).to_a.shuffle do
184
+ @poke_counter += 1
185
+ raise "too many pokes: #{@poke_counter}, #{board}, removed: #{@removed_values.length}" if (@poke_counter > 10_000_000)
186
+ if safe(board, empty_cell, num)
187
+ board[empty_cell[:row_i]][empty_cell[:col_i]] = num
188
+ return board if fill_from_array(board, empty_cell_array)
189
+ board[empty_cell[:row_i]][empty_cell[:col_i]] = 0
190
+ end
191
+ end
192
+ false
193
+ end
194
+
195
+ def next_still_empty_cell(board, empty_cell_array)
196
+ empty_cell_array.each do |coords|
197
+ next if board[ coords[:row] ][ coords[:col] ] != 0
198
+ return {row_i: coords[:row], col_i: coords[:col] }
199
+ end
200
+ false
201
+ end
202
+
203
+ def empty_cell_coords(board)
204
+ (0..8).to_a.each_with_object( [] ) do |row, empty_cells|
205
+ (0..8).to_a.each do |col|
206
+ next if board[row][col] != 0
207
+ empty_cells << {row:row, col:col}
208
+ end
209
+ end
210
+ end
211
+
212
+ #################
213
+
154
214
 
155
215
  def format_number(integer)
156
216
  number_of_digits = Math.log(integer, 10).floor + 1
@@ -169,5 +229,32 @@ class Sudoku
169
229
 
170
230
  end
171
231
 
232
+ # SOLVED_BOARD = [
233
+ # [2, 8, 4, 7, 9, 1, 5, 3, 6],
234
+ # [7, 3, 6, 5, 8, 2, 4, 9, 1],
235
+ # [9, 1, 5, 3, 4, 6, 2, 7, 8],
236
+ # [5, 4, 1, 2, 7, 3, 8, 6, 9],
237
+ # [8, 6, 7, 9, 5, 4, 3, 1, 2],
238
+ # [3, 9, 2, 1, 6, 8, 7, 5, 4],
239
+ # [1, 7, 3, 4, 2, 9, 6, 8, 5],
240
+ # [6, 2, 9, 8, 3, 5, 1, 4, 7],
241
+ # [4, 5, 8, 6, 1, 7, 9, 2, 3]
242
+ # ]
243
+
244
+ # MULTIPLE_SOLUTION = [
245
+ # [0,1,7,5,6,0,0,0,8],
246
+ # [0,5,0,0,0,1,3,0,7],
247
+ # [0,9,2,0,0,0,6,0,1],
248
+ # [0,7,0,2,3,0,8,4,0],
249
+ # [4,3,0,6,1,0,0,7,9],
250
+ # [0,6,8,0,0,7,0,3,0],
251
+ # [7,0,0,1,5,0,9,8,4],
252
+ # [0,0,1,7,0,3,5,0,0],
253
+ # [0,0,6,0,2,0,7,0,0]
254
+ # ]
255
+
256
+
257
+
258
+
172
259
  # binding.pry
173
260
  # false
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sudoku_wizard
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Sasse
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-18 00:00:00.000000000 Z
11
+ date: 2021-03-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Uses backtracking algorithm to generate random boards of specified difficulty
14
14
  and find solutions
@@ -37,8 +37,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
37
37
  - !ruby/object:Gem::Version
38
38
  version: '0'
39
39
  requirements: []
40
- rubyforge_project:
41
- rubygems_version: 2.6.1
40
+ rubygems_version: 3.0.9
42
41
  signing_key:
43
42
  specification_version: 4
44
43
  summary: Sudoku board generator & solver