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,101 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pathname"
4
+ require_relative "../lib/z3"
5
+ require "paint"
6
+
7
+ class SandwichSudokuSolver
8
+ def initialize(path)
9
+ data = Pathname(path).read
10
+ data = data.strip.split("\n").map do |line|
11
+ line.split.map{|c| c =~ /\A\d+\z/ ? c.to_i : nil}
12
+ end
13
+ @col_counts = data.shift
14
+ @row_counts = data.map(&:shift)
15
+ @xsize = @col_counts.size
16
+ @ysize = @row_counts.size
17
+ @data = data
18
+ raise unless data.size == @ysize
19
+ raise unless data.all?{|r| r.size == @ysize}
20
+ @solver = Z3::Solver.new
21
+ end
22
+
23
+ def call
24
+ @cells = (0..8).map do |y|
25
+ (0..8).map do |x|
26
+ cell_var(@data[y][x], x, y)
27
+ end
28
+ end
29
+
30
+ @cells.each do |row|
31
+ @solver.assert Z3.Distinct(*row)
32
+ end
33
+ @cells.transpose.each do |column|
34
+ @solver.assert Z3.Distinct(*column)
35
+ end
36
+ @cells.each_slice(3) do |rows|
37
+ rows.transpose.each_slice(3) do |square|
38
+ @solver.assert Z3.Distinct(*square.flatten)
39
+ end
40
+ end
41
+
42
+ 9.times do |x|
43
+ assert_sandwich "c#{x+1}", @col_counts[x], col_vars(x)
44
+ end
45
+
46
+ 9.times do |y|
47
+ assert_sandwich "s#{y+1}", @row_counts[y], row_vars(y)
48
+ end
49
+
50
+ # TODO: SANDWICHES
51
+
52
+ if @solver.satisfiable?
53
+ @model = @solver.model
54
+ print_answer!
55
+ else
56
+ puts "failed to solve"
57
+ end
58
+ end
59
+
60
+ def assert_sandwich(name, count, vars)
61
+ ss = Z3.Int("#{name}-ss")
62
+ se = Z3.Int("#{name}-se")
63
+ @solver.assert ss >= 0
64
+ @solver.assert ss <= 8
65
+ @solver.assert se >= 0
66
+ @solver.assert se <= 8
67
+ @solver.assert ss < se
68
+ 9.times do |i|
69
+ @solver.assert ((vars[i] == 1) | (vars[i] == 9)) == ((ss == i) | (se == i))
70
+ end
71
+ e = 9.times.map{|i|
72
+ Z3.And(i > ss, i < se).ite(vars[i], 0)
73
+ }
74
+ @solver.assert Z3.Add(*e) == count
75
+ end
76
+
77
+ def row_vars(y)
78
+ @cells[y]
79
+ end
80
+
81
+ def col_vars(x)
82
+ @cells.map{|line| line[x]}
83
+ end
84
+
85
+ def cell_var(cell, x, y)
86
+ v = Z3.Int("cell[#{x+1},#{y+1}]")
87
+ @solver.assert v >= 1
88
+ @solver.assert v <= 9
89
+ @solver.assert v == cell if cell != nil
90
+ v
91
+ end
92
+
93
+ def print_answer!
94
+ @cells.each do |row|
95
+ puts row.map{|v| @model[v]}.join(" ")
96
+ end
97
+ end
98
+ end
99
+
100
+ path = ARGV[0] || Pathname(__dir__) + "sandwich_sudoku-1.txt"
101
+ SandwichSudokuSolver.new(path).call
@@ -0,0 +1,10 @@
1
+ 19 7 15 19 4 0 6 9 35
2
+ 5 . . . . . . . . 1
3
+ 13 . . . . . . . . .
4
+ 20 . . . . . . . . .
5
+ 9 . . . . . . . . .
6
+ 12 . . . . 1 . . . .
7
+ 0 . . . . . . . . .
8
+ 4 . . . . . . . . .
9
+ 14 . . . . . . . . .
10
+ 5 . . . . . . . . .
data/examples/selfref CHANGED
@@ -30,7 +30,7 @@ class SelfRefPuzzleSolver
30
30
  Z3.Or(*ary.map{|i| cons_answers == i})
31
31
  end
32
32
 
33
- def solve!
33
+ def call
34
34
  @a_answers = Z3.Add(*(1..20).map{|i| @a[i][1]})
35
35
  @b_answers = Z3.Add(*(1..20).map{|i| @a[i][2]})
36
36
  @c_answers = Z3.Add(*(1..20).map{|i| @a[i][3]})
@@ -205,7 +205,7 @@ class SelfRefPuzzleSolver
205
205
  end
206
206
 
207
207
 
208
- SelfRefPuzzleSolver.new.solve!
208
+ SelfRefPuzzleSolver.new.call
209
209
 
210
210
  __END__
211
211
  http://faculty.uml.edu/jpropp/srat-Q.txt
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pathname"
4
+ require_relative "../lib/z3"
5
+
6
+ class SkyscrapersSolver
7
+ def initialize(path)
8
+ parse_data(Pathname(path).read)
9
+ @solver = Z3::Solver.new
10
+ end
11
+
12
+ def call
13
+ setup_grid_vars
14
+ setup_constraints
15
+
16
+ if @solver.satisfiable?
17
+ @model = @solver.model
18
+ print_answer
19
+ else
20
+ puts "No solution"
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def setup_grid_vars
27
+ @gridvars = @size.times.map do |y|
28
+ @size.times.map do |x|
29
+ v = Z3.Int("g#{x},#{y}")
30
+ if @grid[y][x]
31
+ @solver.assert v == @grid[y][x]
32
+ else
33
+ @solver.assert v >= 1
34
+ @solver.assert v <= @size
35
+ end
36
+ v
37
+ end
38
+ end
39
+ [*@gridvars, *@gridvars.transpose].each do |row|
40
+ @solver.assert Z3.Distinct(*row)
41
+ end
42
+ end
43
+
44
+ def setup_constraints
45
+ @size.times do |i|
46
+ setup_visibility "L#{i}", @left[i], @gridvars[i]
47
+ setup_visibility "R#{i}", @right[i], @gridvars[i].reverse
48
+ setup_visibility "T#{i}", @top[i], @gridvars.map{|row| row[i] }
49
+ setup_visibility "B#{i}", @bottom[i], @gridvars.map{|row| row[i] }.reverse
50
+ end
51
+ end
52
+
53
+ def setup_visibility(label, expected, vars)
54
+ return unless expected
55
+ # Variables between cells:
56
+ # - count seen
57
+ # - max seen
58
+
59
+ count_vars = (0..@size).map{|i| Z3.Int("#{label}-c#{i}") }
60
+ max_vars = (0..@size).map{|i| Z3.Int("#{label}-m#{i}") }
61
+
62
+ @solver.assert count_vars.first == expected
63
+ @solver.assert max_vars.first == 0
64
+
65
+ @size.times do |i|
66
+ count_near = count_vars[i]
67
+ count_far = count_vars[i+1]
68
+ max_near = max_vars[i]
69
+ max_far = max_vars[i+1]
70
+ current = vars[i]
71
+ visible = Z3.Bool("#{label}-v#{i}")
72
+ @solver.assert visible == (current > max_near)
73
+ @solver.assert count_near == Z3.IfThenElse(visible, count_far+1, count_far)
74
+ @solver.assert max_far == Z3.IfThenElse(visible, current, max_near)
75
+ end
76
+
77
+ @solver.assert count_vars.last == 0
78
+ # This is redundant:
79
+ @solver.assert max_vars.last == @size
80
+ end
81
+
82
+ def print_answer
83
+ @size.times do |y|
84
+ @size.times do |x|
85
+ v = @model[@gridvars[y][x]]
86
+ print "#{v} "
87
+ end
88
+ print "\n"
89
+ end
90
+ end
91
+
92
+ def parse_data(data)
93
+ data = data.lines.map do |line|
94
+ line.split.map do |x|
95
+ if x =~ /\d+/
96
+ x.to_i
97
+ elsif x == "." or x == "-"
98
+ nil
99
+ else
100
+ raise "Unrecognized symbol #{x.inspect} in input"
101
+ end
102
+ end
103
+ end
104
+
105
+ @top = data.shift
106
+ @bottom = data.pop
107
+ @left = data.map(&:shift)
108
+ @right = data.map(&:pop)
109
+ @size = @top.size
110
+ raise "Grid must be square" unless [@top.size, @bottom.size, @left.size, @right.size, *data.map(&:size)].uniq.size == 1
111
+ @grid = data
112
+ end
113
+
114
+ end
115
+
116
+ path = ARGV[0] || Pathname(__dir__) + "skyscrapers-1.txt"
117
+ SkyscrapersSolver.new(path).call
118
+
@@ -0,0 +1,6 @@
1
+ 2 - - -
2
+ - . . 1 . -
3
+ - . . . . 3
4
+ - . . . 2 -
5
+ - . . . . -
6
+ 3 - - 1
@@ -0,0 +1,11 @@
1
+ 3 - 4 - - 3 3 3 3
2
+ - . . . . . . . . 6 -
3
+ - . 1 . . . . . . . -
4
+ - 1 6 . . . . . . . -
5
+ - . . . 3 . . . . . 3
6
+ 4 . . . 7 . . . . . 3
7
+ 3 . . . . . 5 . . . 1
8
+ 4 . . . 4 . . . 6 5 3
9
+ - . . 4 . . . . . . 3
10
+ - . . . . . 2 6 . . -
11
+ - - - 3 4 2 4 4 -
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pathname"
4
+ require_relative "../lib/z3"
5
+
6
+ class StarBattle
7
+ def initialize(path)
8
+ @data = Pathname(path).readlines.map(&:chomp).map(&:split)
9
+ @size = @data.size
10
+ raise unless @data.all?{|row| row.size == @size}
11
+ raise unless containers.size == @size
12
+ @solver = Z3::Solver.new
13
+ end
14
+
15
+ def call
16
+ # Each row contains 2 stars
17
+ @size.times do |y|
18
+ sum = Z3.Add(*@size.times.map{|x| cell_var(x, y).ite(1, 0) })
19
+ @solver.assert sum == 2
20
+ end
21
+
22
+ # Each column contains 2 stars
23
+ @size.times do |x|
24
+ sum = Z3.Add(*@size.times.map{|y| cell_var(x, y).ite(1, 0) })
25
+ @solver.assert sum == 2
26
+ end
27
+
28
+ # Each container contains 2 stars
29
+ coords.group_by{|x,y| container_at(x,y) }.each do |name, cells|
30
+ sum = Z3.Add(*cells.map{|x,y| cell_var(x, y).ite(1, 0) })
31
+ @solver.assert sum == 2
32
+ end
33
+
34
+ # Can't be adjacent
35
+ coords.each do |x, y|
36
+ @solver.assert cell_var(x, y).implies !cell_var(x+1, y)
37
+ @solver.assert cell_var(x, y).implies !cell_var(x-1, y+1)
38
+ @solver.assert cell_var(x, y).implies !cell_var(x, y+1)
39
+ @solver.assert cell_var(x, y).implies !cell_var(x+1, y+1)
40
+ end
41
+
42
+ if @solver.satisfiable?
43
+ @model = @solver.model
44
+ print_answer
45
+ else
46
+ puts "failed to solve"
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def coords
53
+ @size.times.flat_map do |y|
54
+ @size.times.map do |x|
55
+ [x,y]
56
+ end
57
+ end
58
+ end
59
+
60
+ def containers
61
+ @containers ||= @data.flatten.uniq.sort
62
+ end
63
+
64
+ def cell_var(x, y)
65
+ return nil unless (0...@size).include?(x)
66
+ return nil unless (0...@size).include?(y)
67
+ Z3.Bool("c#{x},#{y}")
68
+ end
69
+
70
+ def container_at(x, y)
71
+ return nil unless (0...@size).include?(x)
72
+ return nil unless (0...@size).include?(y)
73
+ @data[y][x]
74
+ end
75
+
76
+ def print_corner(x, y)
77
+ if [
78
+ container_at(x, y),
79
+ container_at(x, y-1),
80
+ container_at(x-1, y),
81
+ container_at(x-1, y-1),
82
+ ].uniq.size == 1
83
+ print " "
84
+ else
85
+ print "+"
86
+ end
87
+ end
88
+
89
+ def print_vertical(x, y)
90
+ if x == 0 or container_at(x, y) != container_at(x-1, y)
91
+ print "|"
92
+ else
93
+ print " "
94
+ end
95
+ end
96
+
97
+ def print_horizontal(x, y)
98
+ if y == 0 or container_at(x, y) != container_at(x, y-1)
99
+ print "-"
100
+ else
101
+ print " "
102
+ end
103
+ end
104
+
105
+ def print_cell(x, y)
106
+ if @model[cell_var(x, y)].to_b
107
+ print "*"
108
+ else
109
+ print " "
110
+ end
111
+ end
112
+
113
+ def print_answer
114
+ (0..@size).each do |y|
115
+ (0..@size).each do |x|
116
+ print_corner x, y
117
+ next if x == @size
118
+ print_horizontal x, y
119
+ end
120
+ print "\n"
121
+
122
+ next if y == @size
123
+ (0..@size).each do |x|
124
+ print_vertical x, y
125
+ next if x == @size
126
+ print_cell x, y
127
+ end
128
+ print "\n"
129
+ end
130
+ end
131
+ end
132
+
133
+ path = ARGV[0] || Pathname(__dir__) + "star_battle-1.txt"
134
+ StarBattle.new(path).call
@@ -0,0 +1,10 @@
1
+ a a a a a a a a a b
2
+ a c a d a a a a a b
3
+ a c a d d d d d b b
4
+ c c c d j j d b b b
5
+ c j j d d j d h e e
6
+ j j j j j j d h e e
7
+ j j j i i i i h h e
8
+ j g g g i i i h e e
9
+ f g g f f h i h e e
10
+ f f f f f h h h h h
data/examples/stitches ADDED
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pathname"
4
+ require_relative "../lib/z3"
5
+ require "paint"
6
+
7
+ class StitchesSolver
8
+ def initialize(path)
9
+ data = Pathname(path).read
10
+ data = data.strip.split("\n").map do |line|
11
+ line.split
12
+ end
13
+ @col_counts = data.shift.map(&:to_i)
14
+ @row_counts = data.map(&:shift).map(&:to_i)
15
+ @xsize = @col_counts.size
16
+ @ysize = @row_counts.size
17
+ @data = data
18
+ raise unless data.size == @ysize
19
+ raise unless data.all?{|r| r.size == @ysize}
20
+ raise unless @col_counts.sum == @row_counts.sum
21
+ @solver = Z3::Solver.new
22
+ @groups = @data.flatten.uniq
23
+ end
24
+
25
+ def call
26
+ assign_colors!
27
+ setup_cell_vars!
28
+ setup_stitch_vars!
29
+
30
+ setup_cell_assertions!
31
+ setup_stitch_assertions!
32
+ setup_cell_stitch_assertions!
33
+
34
+ if @solver.satisfiable?
35
+ @model = @solver.model
36
+ print_answer!
37
+ else
38
+ puts "failed to solve"
39
+ end
40
+ end
41
+
42
+ def assign_colors!
43
+ @fg = @groups.to_h{|g| [g, [rand(256),rand(256),rand(256)]] }
44
+ @bg = @groups.to_h{|g| [g, [rand(256),rand(256),rand(256)]] }
45
+ end
46
+
47
+ def setup_cell_vars!
48
+ @cell_vars = {}
49
+ @xsize.times do |x|
50
+ @ysize.times do |y|
51
+ v = Z3.Bool("c#{x},#{y}")
52
+ @cell_vars[[x,y]] = v
53
+ end
54
+ end
55
+ end
56
+
57
+ def row_of_cell_vars(y)
58
+ @xsize.times.map{|x| @cell_vars[[x,y]].ite(1,0) }
59
+ end
60
+
61
+ def col_of_cell_vars(x)
62
+ @ysize.times.map{|y| @cell_vars[[x,y]].ite(1,0) }
63
+ end
64
+
65
+ def setup_cell_assertions!
66
+ @xsize.times do |x|
67
+ @solver.assert Z3.Add(*col_of_cell_vars(x)) == @col_counts[x]
68
+ end
69
+
70
+ @ysize.times do |y|
71
+ @solver.assert Z3.Add(*row_of_cell_vars(y)) == @row_counts[y]
72
+ end
73
+ end
74
+
75
+ def setup_stitch_vars!
76
+ @hstitch = {}
77
+ @vstitch = {}
78
+ @stitch_groups = {}
79
+
80
+ (@xsize-1).times do |x|
81
+ @ysize.times do |y|
82
+ v = Z3.Bool("h#{x},#{y}")
83
+ g1 = @data[y][x]
84
+ g2 = @data[y][x+1]
85
+ pair = [g1,g2].sort.uniq
86
+ next if pair.size == 1
87
+ @stitch_groups[pair] ||= []
88
+ @stitch_groups[pair] << v
89
+ @hstitch[[x,y]] = v
90
+ end
91
+ end
92
+
93
+ @xsize.times do |x|
94
+ (@ysize-1).times do |y|
95
+ v = Z3.Bool("v#{x},#{y}")
96
+ g1 = @data[y][x]
97
+ g2 = @data[y+1][x]
98
+ pair = [g1,g2].sort.uniq
99
+ next if pair.size == 1
100
+ @stitch_groups[pair] ||= []
101
+ @stitch_groups[pair] << v
102
+ @vstitch[[x,y]] = v
103
+ end
104
+ end
105
+
106
+ group_count = (2 * @stitch_groups.keys.size)
107
+ raise unless @col_counts.sum % group_count == 0
108
+ @stitch_count = @col_counts.sum / group_count
109
+ end
110
+
111
+ def setup_stitch_assertions!
112
+ @stitch_groups.each do |g, vars|
113
+ @solver.assert Z3.Add(*vars.map{|v| v.ite(1,0)}) == @stitch_count
114
+ end
115
+ end
116
+
117
+ def setup_cell_stitch_assertions!
118
+ @xsize.times do |x|
119
+ @ysize.times do |y|
120
+ vars = [
121
+ @hstitch[[x,y]],
122
+ @hstitch[[x-1,y]],
123
+ @vstitch[[x,y]],
124
+ @vstitch[[x,y-1]],
125
+ ].compact.map{|v| v.ite(1,0)}
126
+ if vars.empty?
127
+ @solver.assert !@cell_vars[[x,y]]
128
+ else
129
+ @solver.assert @cell_vars[[x,y]].ite(1,0) == Z3.Add(*vars)
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ def group_at(x,y)
136
+ return nil if x < 0 or y < 0
137
+ return nil if x >= @xsize or y >= @ysize
138
+ @data[y][x]
139
+ end
140
+
141
+ def paint_for(s,x1,y1,x2=x1,y2=y1)
142
+ g1 = group_at(x1,y1)
143
+ g2 = group_at(x2,y2)
144
+ if g1 == g2
145
+ Paint[s, @fg[g1], @bg[g1]]
146
+ else
147
+ s
148
+ end
149
+ end
150
+
151
+ def print_answer!
152
+ @ysize.times do |y|
153
+ @xsize.times do |x|
154
+ s = @model[@cell_vars[[x,y]]].to_b ? "*" : " "
155
+ print paint_for(s, x, y)
156
+ if @hstitch[[x,y]]
157
+ hs = @model[@hstitch[[x,y]]].to_b ? "-" : " "
158
+ else
159
+ hs = " "
160
+ end
161
+ print paint_for(hs, x, y, x+1, y)
162
+ end
163
+ print "\n"
164
+ next if y == @ysize - 1
165
+ @xsize.times do |x|
166
+ if @vstitch[[x,y]]
167
+ vs = @model[@vstitch[[x,y]]].to_b ? "|" : " "
168
+ else
169
+ vs = " "
170
+ end
171
+ print paint_for(vs, x, y, x, y+1)
172
+ print paint_for(" ", x, y, x+1, y+1)
173
+ end
174
+ print "\n"
175
+ end
176
+ end
177
+ end
178
+
179
+ path = ARGV[0] || Pathname(__dir__) + "stitches-1.txt"
180
+ StitchesSolver.new(path).call
@@ -0,0 +1,11 @@
1
+ 6 8 6 9 2 5 4 8 2 2
2
+ 3 a a a a a a a b b b
3
+ 6 a a c c c a a b c b
4
+ 4 a f f c c b b b c b
5
+ 6 f f f f c c c c c b
6
+ 8 h h h f c h c b b b
7
+ 4 g h h h h h c c b b
8
+ 6 g g e e e h h h e b
9
+ 8 g g g g e h h e e e
10
+ 5 d d e e e e e e e e
11
+ 2 d d d d e e e e e e
data/examples/sudoku CHANGED
@@ -13,7 +13,7 @@ class SudokuSolver
13
13
  @solver = Z3::Solver.new
14
14
  end
15
15
 
16
- def solve!
16
+ def call
17
17
  @cells = (0..8).map do |j|
18
18
  (0..8).map do |i|
19
19
  cell_var(@data[j][i], i, j)
@@ -58,4 +58,4 @@ class SudokuSolver
58
58
  end
59
59
 
60
60
  path = ARGV[0] || Pathname(__dir__) + "sudoku-1.txt"
61
- SudokuSolver.new(path).solve!
61
+ SudokuSolver.new(path).call