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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/sudoku_wizard.rb +139 -0
  3. 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: []