sudoku_wizard 1.0.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 +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: []