z3 0.0.20181229 → 0.0.20211213

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -2
  3. data/Rakefile +8 -1
  4. data/examples/abc_path +187 -0
  5. data/examples/abc_path-1.txt +7 -0
  6. data/examples/algebra_problems +12 -12
  7. data/examples/aquarium +133 -0
  8. data/examples/aquarium-1.txt +11 -0
  9. data/examples/bridges +2 -2
  10. data/examples/cats_organized_neatly +133 -0
  11. data/examples/cats_organized_neatly-10.txt +15 -0
  12. data/examples/cats_organized_neatly-3.txt +8 -0
  13. data/examples/cats_organized_neatly-48.txt +32 -0
  14. data/examples/circuit_problems +4 -4
  15. data/examples/clogic_puzzle +2 -2
  16. data/examples/color_nonogram +150 -0
  17. data/examples/color_nonogram-1.txt +23 -0
  18. data/examples/crossflip +2 -4
  19. data/examples/dominion +153 -0
  20. data/examples/dominion-1.txt +8 -0
  21. data/examples/dominosa +133 -0
  22. data/examples/dominosa-1.txt +8 -0
  23. data/examples/eulero +99 -0
  24. data/examples/eulero-1.txt +5 -0
  25. data/examples/four_hackers_puzzle +2 -2
  26. data/examples/futoshiki +128 -0
  27. data/examples/futoshiki-1.txt +17 -0
  28. data/examples/kakurasu +73 -0
  29. data/examples/kakurasu-1.txt +2 -0
  30. data/examples/kakuro +2 -2
  31. data/examples/killer_sudoku +88 -0
  32. data/examples/killer_sudoku-1.txt +17 -0
  33. data/examples/killer_sudoku-2.txt +53 -0
  34. data/examples/kinematics_problems +20 -20
  35. data/examples/knights_puzzle +2 -2
  36. data/examples/kropki +100 -0
  37. data/examples/kropki-1.txt +13 -0
  38. data/examples/letter_connections +2 -2
  39. data/examples/light_up +2 -2
  40. data/examples/minisudoku +2 -2
  41. data/examples/miracle_sudoku +135 -0
  42. data/examples/miracle_sudoku-1.txt +9 -0
  43. data/examples/mortal_coil_puzzle +2 -2
  44. data/examples/nanro +245 -0
  45. data/examples/nanro-1.txt +8 -0
  46. data/examples/nine_clocks +106 -0
  47. data/examples/nonogram +2 -2
  48. data/examples/pyramid_nonogram +2 -2
  49. data/examples/regexp_crossword_solver +2 -2
  50. data/examples/regexp_solver +2 -2
  51. data/examples/renzoku +124 -0
  52. data/examples/renzoku-1.txt +17 -0
  53. data/examples/sandwich_sudoku +101 -0
  54. data/examples/sandwich_sudoku-1.txt +10 -0
  55. data/examples/selfref +2 -2
  56. data/examples/skyscrapers +118 -0
  57. data/examples/skyscrapers-1.txt +6 -0
  58. data/examples/skyscrapers-2.txt +11 -0
  59. data/examples/star_battle +134 -0
  60. data/examples/star_battle-1.txt +10 -0
  61. data/examples/stitches +180 -0
  62. data/examples/stitches-1.txt +11 -0
  63. data/examples/sudoku +2 -2
  64. data/examples/suguru +199 -0
  65. data/examples/suguru-1.txt +17 -0
  66. data/examples/verbal_arithmetic +2 -2
  67. data/examples/yajilin +268 -0
  68. data/examples/yajilin-1.txt +10 -0
  69. data/lib/z3/expr/expr.rb +3 -3
  70. data/lib/z3/low_level_auto.rb +138 -10
  71. data/lib/z3/optimize.rb +1 -0
  72. data/lib/z3/very_low_level.rb +5 -1
  73. data/lib/z3/very_low_level_auto.rb +35 -3
  74. data/spec/integration/abc_path_spec.rb +21 -0
  75. data/spec/integration/aquarium_spec.rb +27 -0
  76. data/spec/integration/cats_organized_neatly_spec.rb +14 -0
  77. data/spec/integration/color_nonogram_spec.rb +28 -0
  78. data/spec/integration/dominion_spec.rb +14 -0
  79. data/spec/integration/dominosa_spec.rb +21 -0
  80. data/spec/integration/eulero_spec.rb +11 -0
  81. data/spec/integration/futoshiki_spec.rb +23 -0
  82. data/spec/integration/kakurasu_spec.rb +18 -0
  83. data/spec/integration/killer_sudoku_spec.rb +10 -0
  84. data/spec/integration/knights_puzzle_spec.rb +11 -11
  85. data/spec/integration/kropki_spec.rb +19 -0
  86. data/spec/integration/miracle_sudoku_spec.rb +15 -0
  87. data/spec/integration/mortal_coil_puzzle_spec.rb +8 -6
  88. data/spec/integration/nanro_spec.rb +39 -0
  89. data/spec/integration/nine_clocks_spec.rb +30 -0
  90. data/spec/integration/regexp_crossword_solver_spec.rb +1 -1
  91. data/spec/integration/renzoku_spec.rb +23 -0
  92. data/spec/integration/sandwich_sudoku_spec.rb +15 -0
  93. data/spec/integration/skyscraper_spec.rb +10 -0
  94. data/spec/integration/star_battle_spec.rb +27 -0
  95. data/spec/integration/stitches_spec.rb +25 -0
  96. data/spec/integration/suguru_spec.rb +23 -0
  97. data/spec/integration/yajilin_spec.rb +25 -0
  98. data/spec/optimize_spec.rb +3 -1
  99. data/spec/set_expr_spec.rb +15 -9
  100. data/spec/solver_spec.rb +1 -2
  101. data/spec/spec_helper.rb +15 -0
  102. metadata +86 -7
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pathname"
4
+ require_relative "../lib/z3"
5
+
6
+ class MiracleSudokuSolver
7
+ def initialize(path)
8
+ data = Pathname(path).read
9
+ data = data.strip.split("\n").map do |line|
10
+ line.split.map{|c| c == "." ? nil : c.to_i}
11
+ end
12
+ @data = data
13
+ raise "Bad size" unless @data.size == 9
14
+ raise "Bad size" unless @data.all?{|row| row.size == 9}
15
+ @solver = Z3::Solver.new
16
+ end
17
+
18
+ def king_moves
19
+ [
20
+ [-1,-1],
21
+ [-1, 0],
22
+ [-1,+1],
23
+ [ 0,-1],
24
+ [ 0,+1],
25
+ [+1,-1],
26
+ [+1, 0],
27
+ [+1,+1],
28
+ ]
29
+ end
30
+
31
+ def knight_moves
32
+ [
33
+ [-2, -1],
34
+ [-2, +1],
35
+ [-1, -2],
36
+ [-1, +2],
37
+ [+1, -2],
38
+ [+1, +2],
39
+ [+2, -1],
40
+ [+2, +1],
41
+ ]
42
+ end
43
+
44
+ def orthogonal_moves
45
+ [
46
+ [-1, 0],
47
+ [+1, 0],
48
+ [ 0, -1],
49
+ [ 0, +1],
50
+ ]
51
+ end
52
+
53
+ def call
54
+ @cells = (0..8).map do |y|
55
+ (0..8).map do |x|
56
+ cell_var(data_at(x,y), x, y)
57
+ end
58
+ end
59
+
60
+ @cells.each do |row|
61
+ @solver.assert Z3.Distinct(*row)
62
+ end
63
+ @cells.transpose.each do |column|
64
+ @solver.assert Z3.Distinct(*column)
65
+ end
66
+ @cells.each_slice(3) do |rows|
67
+ rows.transpose.each_slice(3) do |square|
68
+ @solver.assert Z3.Distinct(*square.flatten)
69
+ end
70
+ end
71
+
72
+ each_coord do |x,y|
73
+ [*king_moves, *knight_moves].each do |dx, dy|
74
+ x2 = x + dx
75
+ y2 = y + dy
76
+ next unless in_bounds?(x2, y2)
77
+ @solver.assert var_at(x,y) != var_at(x2,y2)
78
+ end
79
+
80
+ orthogonal_moves.each do |dx, dy|
81
+ x2 = x + dx
82
+ y2 = y + dy
83
+ next unless in_bounds?(x2, y2)
84
+ @solver.assert var_at(x,y) != var_at(x2,y2) + 1
85
+ @solver.assert var_at(x,y) != var_at(x2,y2) - 1
86
+ end
87
+ end
88
+
89
+ if @solver.satisfiable?
90
+ @model = @solver.model
91
+ print_answer!
92
+ else
93
+ puts "failed to solve"
94
+ end
95
+ end
96
+
97
+ def each_coord
98
+ 9.times do |x|
99
+ 9.times do |y|
100
+ yield(x, y)
101
+ end
102
+ end
103
+ end
104
+
105
+ def in_bounds?(x,y)
106
+ (0..8).include?(x) and (0..8).include?(y)
107
+ end
108
+
109
+ def var_at(x, y)
110
+ return nil unless in_bounds?(x,y)
111
+ @cells[y][x]
112
+ end
113
+
114
+ def data_at(x, y)
115
+ return nil unless in_bounds?(x,y)
116
+ @data[y][x]
117
+ end
118
+
119
+ def cell_var(cell, x, y)
120
+ v = Z3.Int("cell[#{x+1},#{y+1}]")
121
+ @solver.assert v >= 1
122
+ @solver.assert v <= 9
123
+ @solver.assert v == cell if cell != nil
124
+ v
125
+ end
126
+
127
+ def print_answer!
128
+ @cells.each do |row|
129
+ puts row.map{|v| @model[v]}.join(" ")
130
+ end
131
+ end
132
+ end
133
+
134
+ path = ARGV[0] || Pathname(__dir__) + "miracle_sudoku-1.txt"
135
+ MiracleSudokuSolver.new(path).call
@@ -0,0 +1,9 @@
1
+ . . . . . . . . .
2
+ . . . . . . . . .
3
+ . . . . . . . . .
4
+ . . . . . . . . .
5
+ . . 1 . . . . . .
6
+ . . . . . . 2 . .
7
+ . . . . . . . . .
8
+ . . . . . . . . .
9
+ . . . . . . . . .
@@ -78,7 +78,7 @@ class MortalCoilSolver
78
78
  end
79
79
  end
80
80
 
81
- def solve!
81
+ def call
82
82
  setup_vars!
83
83
  line_continuity!
84
84
  line_goes_until_it_hits_something!
@@ -102,4 +102,4 @@ class MortalCoilSolver
102
102
  end
103
103
 
104
104
  path = ARGV[0] || Pathname(__dir__) + "mortal_coil_puzzle-9.txt"
105
- MortalCoilSolver.new(path).solve!
105
+ MortalCoilSolver.new(path).call
data/examples/nanro ADDED
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/z3"
4
+ require "pathname"
5
+
6
+ # https://www.gmpuzzles.com/blog/nanro-rules-and-info/
7
+
8
+ class Nanro
9
+ def initialize(path)
10
+ data = Pathname(path)
11
+ .readlines
12
+ .map{|row| row.chomp.split}
13
+ @ysize = data.size
14
+ @xsize = data[0].size
15
+ raise unless data.all?{|row| row.size == @ysize}
16
+ @regions = data
17
+ .map{|row|
18
+ row.map{|cell|
19
+ cell.gsub(/\d|\./, "")
20
+ }
21
+ }
22
+ @hints = data
23
+ .map{|row|
24
+ row.map{|cell|
25
+ cell[1] =~ /(\d+)/ ? $1.to_i : nil
26
+ }
27
+ }
28
+ @solver = Z3::Solver.new
29
+ end
30
+
31
+ def assert_each_cell_is_0_or_rvar
32
+ each_xy do |x,y|
33
+ @solver.assert Z3.Or(
34
+ cvar(x,y) == 0,
35
+ cvar(x,y) == rvar(region_at(x,y))
36
+ )
37
+ end
38
+ end
39
+
40
+ def assert_rvar_equals_number_of_numbered_cells_in_region
41
+ cells_by_region.each do |r, xys|
42
+ nonzeroes = xys.map{|x,y| (cvar(x,y) == 0).ite(0,1)}
43
+ @solver.assert rvar(r) == Z3.Add(*nonzeroes)
44
+ @solver.assert rvar(r) >= 1
45
+ end
46
+ end
47
+
48
+ def assert_hints_obeyed
49
+ each_xy do |x,y|
50
+ if hint_at(x,y)
51
+ @solver.assert cvar(x,y) == hint_at(x,y)
52
+ end
53
+ end
54
+ end
55
+
56
+ def assert_no_numbered_squares
57
+ each_xy do |x,y|
58
+ next unless on_board?(x+1,y+1)
59
+ @solver.assert Z3.Or(
60
+ cvar(x,y) == 0,
61
+ cvar(x+1,y) == 0,
62
+ cvar(x,y+1) == 0,
63
+ cvar(x+1,y+1) == 0,
64
+ )
65
+ end
66
+ end
67
+
68
+ def assert_no_same_number_between_regions
69
+ each_xy do |x,y|
70
+ neighbours(x,y).each do |nx,ny|
71
+ next if region_at(x,y) == region_at(nx,ny)
72
+ @solver.assert (cvar(x,y) != 0).implies(cvar(x,y) != cvar(nx,ny))
73
+ end
74
+ end
75
+ end
76
+
77
+ def assert_numbered_cells_all_connected
78
+ max_value = @xsize * @ysize + 10
79
+
80
+ each_xy do |x,y|
81
+ @solver.assert nvar(x,y) >= 0
82
+ @solver.assert (cvar(x,y) == 0).implies(nvar(x,y) == max_value)
83
+ @solver.assert (cvar(x,y) != 0).implies(nvar(x,y) < max_value)
84
+
85
+ nval = neighbours(x,y)
86
+ .map{|nx,ny|
87
+ (cvar(nx,ny) == 0).ite(max_value, nvar(nx,ny) + 1)
88
+ }
89
+ .reduce{|a,b| (a <= b).ite(a, b) }
90
+
91
+ @solver.assert (cvar(x,y) != 0).implies(Z3.Or(nvar(x,y) == 0, nvar(x,y) == nval))
92
+ end
93
+
94
+ @solver.assert Z3.Add(
95
+ *enum_for(:each_xy).map{|x,y|
96
+ (nvar(x,y) == 0).ite(1,0)
97
+ }) == 1
98
+ end
99
+
100
+ def call
101
+ assert_hints_obeyed
102
+ assert_no_numbered_squares
103
+ assert_each_cell_is_0_or_rvar
104
+ assert_rvar_equals_number_of_numbered_cells_in_region
105
+ assert_no_same_number_between_regions
106
+ assert_numbered_cells_all_connected
107
+
108
+ if @solver.satisfiable?
109
+ @model = @solver.model
110
+ print_answer!
111
+ else
112
+ puts "failed to solve"
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ def neighbours(x,y)
119
+ [
120
+ [x+1,y],
121
+ [x-1,y],
122
+ [x,y+1],
123
+ [x,y-1],
124
+ ].select{|nx,ny| on_board?(nx,ny)}
125
+ end
126
+
127
+ def cells_by_region
128
+ @cells_by_region ||= enum_for(:each_xy).group_by{|x,y| region_at(x,y)}
129
+ end
130
+
131
+ def rvar(r)
132
+ Z3.Int("r[#{r}]")
133
+ end
134
+
135
+ def cvar(x,y)
136
+ Z3.Int("c[#{x},#{y}]")
137
+ end
138
+
139
+ def nvar(x,y)
140
+ Z3.Int("n[#{x},#{y}]")
141
+ end
142
+
143
+ def each_xy
144
+ @ysize.times do |y|
145
+ @xsize.times do |x|
146
+ yield(x,y)
147
+ end
148
+ end
149
+ end
150
+
151
+ def on_board?(x,y)
152
+ x >= 0 and y >= 0 and x < @xsize and y < @ysize
153
+ end
154
+
155
+ def region_at(x,y)
156
+ return nil unless on_board?(x,y)
157
+ @regions[y][x]
158
+ end
159
+
160
+ def hint_at(x,y)
161
+ return nil unless on_board?(x,y)
162
+ @hints[y][x]
163
+ end
164
+
165
+ def print_corner?(x, y)
166
+ [
167
+ region_at(x,y),
168
+ region_at(x-1,y),
169
+ region_at(x,y-1),
170
+ region_at(x-1,y-1),
171
+ ].uniq.size > 1
172
+ end
173
+
174
+ def print_edge?(x1, y1, x2, y2)
175
+ region_at(x1,y1) != region_at(x2,y2)
176
+ end
177
+
178
+ def corner_output(x,y)
179
+ if print_corner?(x, y)
180
+ "*"
181
+ else
182
+ " "
183
+ end
184
+ end
185
+
186
+ def vedge_output(x,y)
187
+ if print_edge?(x, y, x, y-1)
188
+ "---"
189
+ else
190
+ " "
191
+ end
192
+ end
193
+
194
+ def hedge_output(x,y)
195
+ if print_edge?(x, y, x-1, y)
196
+ "|"
197
+ else
198
+ " "
199
+ end
200
+ end
201
+
202
+ def print_answer!
203
+ (0..@ysize).each do |y|
204
+ (0..@xsize).each do |x|
205
+ print corner_output(x,y)
206
+ next if x == @xsize
207
+ print vedge_output(x,y)
208
+ end
209
+ print "\n"
210
+
211
+ next if y == @ysize
212
+
213
+ (0..@xsize).each do |x|
214
+ print hedge_output(x,y)
215
+ next if x == @xsize
216
+ print " "
217
+ end
218
+ print "\n"
219
+
220
+ (0..@xsize).each do |x|
221
+ print hedge_output(x,y)
222
+ next if x == @xsize
223
+ print " "
224
+ c = @model[cvar(x,y)].to_i
225
+ if c == 0
226
+ print " "
227
+ else
228
+ print c
229
+ end
230
+ print " "
231
+ end
232
+ print "\n"
233
+
234
+ (0..@xsize).each do |x|
235
+ print hedge_output(x,y)
236
+ next if x == @xsize
237
+ print " "
238
+ end
239
+ print "\n"
240
+ end
241
+ end
242
+ end
243
+
244
+ path = ARGV[0] || Pathname(__dir__) + "nanro-1.txt"
245
+ Nanro.new(path).call
@@ -0,0 +1,8 @@
1
+ a6 a. a. a. a. e. f. f3
2
+ a. c. a. a. d. e. f. f.
3
+ b. c. t. t. w. e. u. f.
4
+ g. r. s. x. w. v. u. f.
5
+ g. r. x. x. w. u. u3 p.
6
+ g. q. x. l. m. o. o. p.
7
+ g. i. j. l. o. o3 o. p.
8
+ h. i. j. k. n2 n. n. p.
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env ruby
2
+ # From Tested on Humans Escape Room game
3
+
4
+ require_relative "../lib/z3"
5
+
6
+ class NineClocks
7
+ def initialize
8
+ @solver = Z3::Solver.new
9
+
10
+ @inputs = {}
11
+ @outputs = {}
12
+
13
+ # Setup
14
+ each_xy do |x,y|
15
+ @inputs[[x,y]] = Z3.Bitvec("i#{x},#{y}", 2)
16
+ @outputs[[x,y]] = Z3.Bitvec("o#{x},#{y}", 2)
17
+ end
18
+
19
+ # Rules
20
+ neighbours = {}
21
+ each_xy do |x,y|
22
+ out = @outputs[[x,y]]
23
+ neighbours[out] = []
24
+ each_xy do |xx, yy|
25
+ if (xx-x).abs + (yy-y).abs <= 1
26
+ neighbours[out] << @inputs[[xx,yy]]
27
+ end
28
+ end
29
+
30
+ neighbours.each do |ovar, ivars|
31
+ @solver.assert ovar == Z3.Add(*ivars)
32
+ end
33
+ end
34
+ end
35
+
36
+ def each_xy
37
+ 3.times do |x|
38
+ 3.times do |y|
39
+ yield(x,y)
40
+ end
41
+ end
42
+ end
43
+
44
+ def target_corner
45
+ each_xy do |x,y|
46
+ if x == 0 and y == 0
47
+ @solver.assert @outputs[[x,y]] == 1
48
+ else
49
+ @solver.assert @outputs[[x,y]] == 0
50
+ end
51
+ end
52
+ self
53
+ end
54
+
55
+ def target_edge
56
+ each_xy do |x,y|
57
+ if x == 0 and y == 1
58
+ @solver.assert @outputs[[x,y]] == 1
59
+ else
60
+ @solver.assert @outputs[[x,y]] == 0
61
+ end
62
+ end
63
+ self
64
+ end
65
+
66
+ def target_center
67
+ each_xy do |x,y|
68
+ if x == 1 and y == 1
69
+ @solver.assert @outputs[[x,y]] == 1
70
+ else
71
+ @solver.assert @outputs[[x,y]] == 0
72
+ end
73
+ end
74
+ self
75
+ end
76
+
77
+ def solve
78
+ if @solver.satisfiable?
79
+ model = @solver.model
80
+
81
+ puts "IN:"
82
+ 3.times do |x|
83
+ 3.times do |y|
84
+ ivar = model[@inputs[[x,y]]]
85
+ print ivar, " "
86
+ end
87
+ print "\n"
88
+ end
89
+
90
+ puts "OUT:"
91
+ 3.times do |x|
92
+ 3.times do |y|
93
+ ivar = model[@outputs[[x,y]]]
94
+ print ivar, " "
95
+ end
96
+ print "\n"
97
+ end
98
+ else
99
+ puts "Not satisfiable"
100
+ end
101
+ end
102
+ end
103
+
104
+ NineClocks.new.target_corner.solve
105
+ NineClocks.new.target_edge.solve
106
+ NineClocks.new.target_center.solve
data/examples/nonogram CHANGED
@@ -76,7 +76,7 @@ class Nonogram
76
76
  end
77
77
  end
78
78
 
79
- def solve!
79
+ def call
80
80
  @cells = (0...@ysize).map{|y|
81
81
  (0...@xsize).map{|x|
82
82
  Z3.Bool("cell[#{x+1},#{y+1}]")
@@ -149,4 +149,4 @@ nonogram = Nonogram.new(
149
149
  ]
150
150
  )
151
151
 
152
- nonogram.solve!
152
+ nonogram.call
@@ -27,7 +27,7 @@ class PyramidNonogram
27
27
  end
28
28
  end
29
29
 
30
- def solve!
30
+ def call
31
31
  if @solver.satisfiable?
32
32
  @model = @solver.model
33
33
  print_answer!
@@ -163,4 +163,4 @@ pyramid_nonogram.left_to_bottom(
163
163
  [_],
164
164
  [_],
165
165
  )
166
- pyramid_nonogram.solve!
166
+ pyramid_nonogram.call
@@ -43,7 +43,7 @@ class RegexpCrosswordSolver
43
43
  SimpleRegexpParser.new(@rows[y], "row-#{y}").parse
44
44
  end
45
45
 
46
- def solve!
46
+ def call
47
47
  @crossword = {}
48
48
  @xsize.times do |x|
49
49
  @ysize.times do |y|
@@ -77,4 +77,4 @@ class RegexpCrosswordSolver
77
77
  end
78
78
 
79
79
  path = ARGV[0] || Pathname(__dir__) + "regexp_crossword/tutorial-1.txt"
80
- RegexpCrosswordSolver.new(path).solve!
80
+ RegexpCrosswordSolver.new(path).call
@@ -11,7 +11,7 @@ class RegexpSolver
11
11
  @solver = Z3::Solver.new
12
12
  end
13
13
 
14
- def solve!
14
+ def call
15
15
  @str = (0...@length).map do |i|
16
16
  v = Z3.Int("char[#{i}]")
17
17
  @solver.assert v >= 0
@@ -40,4 +40,4 @@ end
40
40
 
41
41
  length = ARGV[0].to_i
42
42
  regexp = ARGV[1]
43
- RegexpSolver.new(length, regexp).solve!
43
+ RegexpSolver.new(length, regexp).call
data/examples/renzoku ADDED
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pathname"
4
+ require_relative "../lib/z3"
5
+
6
+ class Renzoku
7
+ # This needs to be very exactly formatted
8
+ def initialize(path)
9
+ @data = Pathname(path).readlines.map(&:chomp)
10
+ @size = (@data.size+1)/2
11
+ @solver = Z3::Solver.new
12
+ end
13
+
14
+ def cell_value(x, y)
15
+ v = @data[y*2][x*2]
16
+ if v == "#"
17
+ nil
18
+ elsif v =~ /\d/
19
+ v.to_i
20
+ else
21
+ raise "Bad cell value"
22
+ end
23
+ end
24
+
25
+ def dot_right?(x, y)
26
+ v = @data[y*2][x*2 + 1]
27
+ if v == "."
28
+ true
29
+ elsif v == " "
30
+ false
31
+ else
32
+ raise "Bad dot value"
33
+ end
34
+ end
35
+
36
+ def dot_bottom?(x, y)
37
+ v = @data[y*2 + 1][x*2]
38
+ if v == "."
39
+ true
40
+ elsif v == " " or v == nil
41
+ false
42
+ else
43
+ raise "Bad dot value"
44
+ end
45
+ end
46
+
47
+ def call
48
+ @vars = {}
49
+ @size.times do |y|
50
+ @size.times do |x|
51
+ v = Z3.Int("v#{x},#{y}")
52
+ @vars[[x,y]] = v
53
+ cv = cell_value(x, y)
54
+ if cv
55
+ @solver.assert (v == cv)
56
+ else
57
+ @solver.assert (v >= 1) & (v <= @size)
58
+ end
59
+ end
60
+ end
61
+
62
+ @size.times do |x|
63
+ line = @size.times.map{|y| @vars[[x,y]] }
64
+ @solver.assert Z3.Distinct(*line)
65
+ end
66
+
67
+ @size.times do |y|
68
+ line = @size.times.map{|x| @vars[[x,y]] }
69
+ @solver.assert Z3.Distinct(*line)
70
+ end
71
+
72
+ # Dots right
73
+ (0..@size-1).each do |y|
74
+ (0..@size-2).each do |x|
75
+ lv = @vars[[x,y]]
76
+ rv = @vars[[x+1,y]]
77
+ if dot_right?(x,y)
78
+ @solver.assert (lv == (rv + 1)) | (lv == (rv - 1))
79
+ else
80
+ @solver.assert lv != (rv + 1)
81
+ @solver.assert lv != (rv - 1)
82
+ end
83
+ end
84
+ end
85
+
86
+ # Dots bottom
87
+ (0..@size-2).each do |y|
88
+ (0..@size-1).each do |x|
89
+ tv = @vars[[x,y]]
90
+ bv = @vars[[x,y+1]]
91
+ if dot_bottom?(x,y)
92
+ @solver.assert (tv == (bv + 1)) | (tv == (bv - 1))
93
+ else
94
+ @solver.assert tv != (bv + 1)
95
+ @solver.assert tv != (bv - 1)
96
+ end
97
+ end
98
+ end
99
+
100
+ if @solver.satisfiable?
101
+ @model = @solver.model
102
+ print_answer!
103
+ else
104
+ puts "failed to solve"
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ def print_answer!
111
+ output = @data.map(&:dup)
112
+ @size.times do |y|
113
+ @size.times do |x|
114
+ v = @model[@vars[[x,y]]]
115
+ output[2*y][2*x] = "#{v}"
116
+ end
117
+ end
118
+
119
+ puts output
120
+ end
121
+ end
122
+
123
+ path = ARGV[0] || Pathname(__dir__) + "renzoku-1.txt"
124
+ Renzoku.new(path).call
@@ -0,0 +1,17 @@
1
+ # # # # # #.# #.#
2
+ . . . .
3
+ #.# #.# # #.#.# #
4
+ . .
5
+ # # 2 #.# #.# # #
6
+
7
+ 4 #.# #.# # # # #
8
+ . . .
9
+ # # # # # # #.# #
10
+ .
11
+ # #.# # # #.# 2 #
12
+ . .
13
+ 1.# # #.# # # # #
14
+ . .
15
+ # # # #.#.#.#.# 2
16
+ . .
17
+ # #.# # # # # # #