binary_puzzle_solver 0.0.4 → 0.0.5
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.
- data/bin/binary-puzzle-solve +2 -0
- data/lib/binary_puzzle_solver/base.rb +219 -53
- data/lib/binary_puzzle_solver/version.rb +1 -1
- data/test/data/boards/binarypuzzle.com/numbered/10x10/easy/1/final.binpuz-board.txt +10 -0
- data/test/data/boards/binarypuzzle.com/numbered/10x10/easy/1/initial.binpuz-board.txt +10 -0
- data/test/data/boards/binarypuzzle.com/numbered/10x10/hard/1/final.binpuz-board.txt +10 -0
- data/test/data/boards/binarypuzzle.com/numbered/10x10/hard/1/initial.binpuz-board.txt +10 -0
- data/test/data/boards/binarypuzzle.com/numbered/10x10/hard/2/final.binpuz-board.txt +10 -0
- data/test/data/boards/binarypuzzle.com/numbered/10x10/hard/2/initial.binpuz-board.txt +10 -0
- data/test/data/boards/binarypuzzle.com/numbered/12x12/hard/1/final.binpuz-board.txt +12 -0
- data/test/data/boards/binarypuzzle.com/numbered/12x12/hard/1/initial.binpuz-board.txt +12 -0
- data/test/data/boards/binarypuzzle.com/numbered/12x12/very_hard/1/initial.binpuz-board.txt +12 -0
- data/test/data/boards/binarypuzzle.com/numbered/12x12/very_hard/1/intermediate_1.binpuz-board.txt +12 -0
- data/test/data/boards/binarypuzzle.com/numbered/14x14/very_hard/1/final.binpuz-board.txt +14 -0
- data/test/data/boards/binarypuzzle.com/numbered/14x14/very_hard/1/initial.binpuz-board.txt +14 -0
- data/test/data/boards/binarypuzzle.com/numbered/14x14/very_hard/1/intermediate_1.binpuz-board.txt +14 -0
- data/test/data/boards/binarypuzzle.com/numbered/14x14/very_hard/1/intermediate_2.binpuz-board.txt +14 -0
- data/test/data/boards/binarypuzzle.com/numbered/14x14/very_hard/2/final.binpuz-board.txt +14 -0
- data/test/data/boards/binarypuzzle.com/numbered/14x14/very_hard/2/initial.binpuz-board.txt +14 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/easy/1/initial.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/easy/1/intermediate_1.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/easy/2/initial.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/easy/2/intermediate_1.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/hard/1/final.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/hard/1/initial.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/hard/1/intermediate.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/hard/2/initial.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/hard/2/intermediate.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/hard/2/intermediate_2.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/hard/3/initial.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/hard/3/intermediate.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/medium/1/after_cells_of_one_value_in_row_were_all_found.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/medium/1/after_easy_moves.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/medium/1/final.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/medium/1/initial.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/very_hard/1/final.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/very_hard/1/initial.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/very_hard/1/intermediate_1.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/very_hard/2/final.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/6x6/very_hard/2/initial.binpuz-board.txt +6 -0
- data/test/data/boards/binarypuzzle.com/numbered/8x8/easy/1/final.binpuz-board.txt +8 -0
- data/test/data/boards/binarypuzzle.com/numbered/8x8/easy/1/initial.binpuz-board.txt +8 -0
- data/test/data/boards/binarypuzzle.com/numbered/8x8/medium/1/final.binpuz-board.txt +8 -0
- data/test/data/boards/binarypuzzle.com/numbered/8x8/medium/1/initial.binpuz-board.txt +8 -0
- data/test/deduction.rb +190 -388
- metadata +84 -2
data/bin/binary-puzzle-solve
CHANGED
@@ -18,6 +18,8 @@ board.try_to_solve_using(
|
|
18
18
|
:check_exceeded_numbers_while_accounting_for_two_unknown_gaps,
|
19
19
|
:check_try_placing_last_of_certain_digit_in_row,
|
20
20
|
:check_try_placing_last_of_certain_digit_in_row_to_avoid_dups,
|
21
|
+
:check_remaining_gap_of_three_with_implicits,
|
22
|
+
:check_exceeded_digits_taking_large_gaps_into_account,
|
21
23
|
]
|
22
24
|
);
|
23
25
|
|
@@ -166,7 +166,19 @@ module Binary_Puzzle_Solver
|
|
166
166
|
end
|
167
167
|
end
|
168
168
|
|
169
|
-
class
|
169
|
+
class BaseClass
|
170
|
+
def opposite_value(val)
|
171
|
+
if (val == Cell::ZERO)
|
172
|
+
return Cell::ONE
|
173
|
+
elsif (val == Cell::ONE)
|
174
|
+
return Cell::ZERO
|
175
|
+
else
|
176
|
+
raise RuntimeError, "'#{val}' must be zero or one."
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
class Board < BaseClass
|
170
182
|
|
171
183
|
attr_reader :iters_quota, :num_iters_done
|
172
184
|
|
@@ -324,18 +336,34 @@ module Binary_Puzzle_Solver
|
|
324
336
|
return @row_summaries[dim][idx]
|
325
337
|
end
|
326
338
|
|
327
|
-
def
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
339
|
+
def _validate_method_list (methods_list)
|
340
|
+
valid_methods_arr = [
|
341
|
+
:check_and_handle_sequences_in_row,
|
342
|
+
:check_and_handle_known_unknown_sameknown_in_row,
|
343
|
+
:check_and_handle_cells_of_one_value_in_row_were_all_found,
|
344
|
+
:check_exceeded_numbers_while_accounting_for_two_unknown_gaps,
|
345
|
+
:check_try_placing_last_of_certain_digit_in_row,
|
346
|
+
:check_try_placing_last_of_certain_digit_in_row_to_avoid_dups,
|
347
|
+
:check_remaining_gap_of_three_with_implicits,
|
348
|
+
:check_exceeded_digits_taking_large_gaps_into_account,
|
349
|
+
]
|
350
|
+
|
351
|
+
valid_mathods_hash = valid_methods_arr.inject({}) {
|
352
|
+
|h,v| h[v] = true; h
|
353
|
+
}
|
354
|
+
|
355
|
+
methods_list.each do |m|
|
356
|
+
if not valid_mathods_hash.has_key?(m) then
|
357
|
+
raise RuntimeError, "Method #{m} is not valid to be used."
|
358
|
+
end
|
334
359
|
end
|
335
360
|
end
|
336
361
|
|
362
|
+
|
337
363
|
def try_to_solve_using (params)
|
338
364
|
methods_list = params[:methods]
|
365
|
+
_validate_method_list(methods_list)
|
366
|
+
|
339
367
|
views = list_views()
|
340
368
|
|
341
369
|
catch :out_of_iters do
|
@@ -620,53 +648,11 @@ module Binary_Puzzle_Solver
|
|
620
648
|
|
621
649
|
row = get_row_handle(row_idx)
|
622
650
|
|
623
|
-
gaps =
|
624
|
-
|
625
|
-
next_gap = []
|
626
|
-
|
627
|
-
add_gap = lambda {
|
628
|
-
l = next_gap.length
|
629
|
-
if (l > 0)
|
630
|
-
if not gaps[l]
|
631
|
-
gaps[l] = []
|
632
|
-
end
|
633
|
-
gaps[l] << next_gap
|
634
|
-
next_gap = []
|
635
|
-
end
|
636
|
-
}
|
637
|
-
|
638
|
-
row.iter_of_handles().each do |cell_h|
|
639
|
-
if (cell_h.get_state == Cell::UNKNOWN)
|
640
|
-
next_gap << cell_h.x
|
641
|
-
else
|
642
|
-
add_gap.call()
|
643
|
-
end
|
644
|
-
end
|
645
|
-
|
646
|
-
add_gap.call()
|
651
|
+
gaps = row.calc_gaps()
|
647
652
|
|
648
653
|
if (gaps.has_key?(2)) then
|
649
|
-
implicit_counts = {Cell::ZERO => 0, Cell::ONE => 0,}
|
650
|
-
gaps[2].each do |gap|
|
651
|
-
x_s = []
|
652
|
-
if (gap[0] > 0)
|
653
|
-
x_s << gap[0]-1
|
654
|
-
end
|
655
|
-
if (gap[-1] < row.max_idx)
|
656
|
-
x_s << gap[-1]+1
|
657
|
-
end
|
658
654
|
|
659
|
-
|
660
|
-
x_s.each do |x|
|
661
|
-
bordering_values[row.get_state(x)] += 1
|
662
|
-
end
|
663
|
-
|
664
|
-
for v in [Cell::ZERO, Cell::ONE] do
|
665
|
-
if bordering_values[opposite_value(v)] > 0
|
666
|
-
implicit_counts[v] += 1
|
667
|
-
end
|
668
|
-
end
|
669
|
-
end
|
655
|
+
implicit_counts = row.calc_gaps_implicit_counts(gaps)
|
670
656
|
|
671
657
|
summ = row.get_summary()
|
672
658
|
|
@@ -788,6 +774,131 @@ module Binary_Puzzle_Solver
|
|
788
774
|
return
|
789
775
|
end
|
790
776
|
|
777
|
+
def check_remaining_gap_of_three_with_implicits(params)
|
778
|
+
row_idx = params[:idx]
|
779
|
+
|
780
|
+
row = get_row_handle(row_idx)
|
781
|
+
|
782
|
+
gaps = row.calc_gaps()
|
783
|
+
|
784
|
+
if (gaps.has_key?(2) and gaps.has_key?(3)) then
|
785
|
+
|
786
|
+
implicit_counts = row.calc_gaps_implicit_counts(gaps)
|
787
|
+
|
788
|
+
summ = row.get_summary()
|
789
|
+
|
790
|
+
v = [Cell::ZERO, Cell::ONE].find {
|
791
|
+
|v| summ.get_count(v) + implicit_counts[v] \
|
792
|
+
== summ.half_limit() - 1
|
793
|
+
}
|
794
|
+
|
795
|
+
if v then
|
796
|
+
opposite_val = opposite_value(v)
|
797
|
+
gap_3 = gaps[3][0]
|
798
|
+
|
799
|
+
|
800
|
+
# Copied - refactor
|
801
|
+
for idx in [0,-1] do
|
802
|
+
opposite_idx = (idx == 0) ? -1 : 0
|
803
|
+
edge_offset = (idx == 0) ? (-1) : 1
|
804
|
+
if (gap_3[idx] > 0 and gap_3[idx] < row.max_idx()) then
|
805
|
+
if (row.get_state(gap_3[idx]+edge_offset) \
|
806
|
+
== opposite_val) then
|
807
|
+
perform_and_append_move(
|
808
|
+
:coord => row.get_coord(gap_3[opposite_idx]),
|
809
|
+
:val => opposite_val,
|
810
|
+
:reason => \
|
811
|
+
"Gap of 3 with 2 of the value remaining must not form 3 of a kind (with implicits)",
|
812
|
+
:dir => row.col_dim()
|
813
|
+
)
|
814
|
+
end
|
815
|
+
end
|
816
|
+
end
|
817
|
+
end
|
818
|
+
end
|
819
|
+
|
820
|
+
return
|
821
|
+
end
|
822
|
+
|
823
|
+
def check_exceeded_digits_taking_large_gaps_into_account(params)
|
824
|
+
row_idx = params[:idx]
|
825
|
+
|
826
|
+
row = get_row_handle(row_idx)
|
827
|
+
|
828
|
+
gaps = row.calc_gaps()
|
829
|
+
summ = row.get_summary()
|
830
|
+
|
831
|
+
values_sorted = [Cell::ZERO, Cell::ONE].sort { |a,b|
|
832
|
+
summ.get_count(a) <=> summ.get_count(b) }
|
833
|
+
|
834
|
+
v = values_sorted[-1]
|
835
|
+
|
836
|
+
to_add = 0
|
837
|
+
(3 .. row.max_idx()).each do |len|
|
838
|
+
if gaps.has_key?(len) then
|
839
|
+
to_add += gaps[len].length
|
840
|
+
end
|
841
|
+
end
|
842
|
+
|
843
|
+
if summ.get_count(v) + to_add == summ.half_limit() then
|
844
|
+
opposite_val = opposite_value(v)
|
845
|
+
(3 .. row.max_idx()).each do |len|
|
846
|
+
if gaps.has_key?(len) then
|
847
|
+
if (len == 3) then
|
848
|
+
gaps[len].each do |gap_3|
|
849
|
+
# Copied - refactor
|
850
|
+
for idx in [0,-1] do
|
851
|
+
opposite_idx = (idx == 0) ? -1 : 0
|
852
|
+
edge_offset = (idx == 0) ? (-1) : 1
|
853
|
+
if (gap_3[idx] > 0 and gap_3[idx] < row.max_idx()) then
|
854
|
+
if (row.get_state(gap_3[idx]+edge_offset) \
|
855
|
+
== opposite_val) then
|
856
|
+
perform_and_append_move(
|
857
|
+
:coord => row.get_coord(gap_3[opposite_idx]),
|
858
|
+
:val => opposite_val,
|
859
|
+
:reason => \
|
860
|
+
"With gaps of 3, exceeded value cannot be at the opposite edge of the opposite value.",
|
861
|
+
:dir => row.col_dim()
|
862
|
+
)
|
863
|
+
end
|
864
|
+
end
|
865
|
+
end
|
866
|
+
end
|
867
|
+
elsif (len == 4) then
|
868
|
+
gaps[len].each do |gap|
|
869
|
+
[0, -1].each do |i|
|
870
|
+
perform_and_append_move(
|
871
|
+
:coord => row.get_coord(gap[i]),
|
872
|
+
:val => opposite_val,
|
873
|
+
:reason => \
|
874
|
+
"With gaps of 4, exceeded value cannot be at edges.",
|
875
|
+
:dir => row.col_dim()
|
876
|
+
)
|
877
|
+
end
|
878
|
+
end
|
879
|
+
elsif (len == 5) then
|
880
|
+
gaps[len].each do |gap|
|
881
|
+
o = opposite_val
|
882
|
+
gap.zip([o,o,v,o,o]) do |pos_v|
|
883
|
+
perform_and_append_move(
|
884
|
+
:coord => row.get_coord(gap[pos_v[0]]),
|
885
|
+
:val => pos_v[1],
|
886
|
+
:reason => \
|
887
|
+
"With gaps of 5, exceeded value pattern must be 00100.",
|
888
|
+
:dir => row.col_dim()
|
889
|
+
)
|
890
|
+
end
|
891
|
+
end
|
892
|
+
else # len >= 5
|
893
|
+
raise GameIntegrityException, "Too large a gap."
|
894
|
+
end
|
895
|
+
end
|
896
|
+
end
|
897
|
+
end
|
898
|
+
|
899
|
+
return
|
900
|
+
end
|
901
|
+
|
791
902
|
def validate_rows()
|
792
903
|
is_final = true
|
793
904
|
|
@@ -803,7 +914,7 @@ module Binary_Puzzle_Solver
|
|
803
914
|
private :_do_values_have_a_three_in_a_row, :_generic_check_try_placing_last_digit, :_are_values_duplicates
|
804
915
|
end
|
805
916
|
|
806
|
-
class RowHandle
|
917
|
+
class RowHandle < BaseClass
|
807
918
|
attr_reader :view, :idx
|
808
919
|
def initialize (init_view, init_idx)
|
809
920
|
@view = init_view
|
@@ -919,6 +1030,61 @@ module Binary_Puzzle_Solver
|
|
919
1030
|
|
920
1031
|
return { :is_final => check_for_duplicated(), };
|
921
1032
|
end
|
1033
|
+
|
1034
|
+
def calc_gaps
|
1035
|
+
gaps = {}
|
1036
|
+
|
1037
|
+
next_gap = []
|
1038
|
+
|
1039
|
+
add_gap = lambda {
|
1040
|
+
l = next_gap.length
|
1041
|
+
if (l > 0)
|
1042
|
+
if not gaps[l]
|
1043
|
+
gaps[l] = []
|
1044
|
+
end
|
1045
|
+
gaps[l] << next_gap
|
1046
|
+
next_gap = []
|
1047
|
+
end
|
1048
|
+
}
|
1049
|
+
|
1050
|
+
iter_of_handles().each do |cell_h|
|
1051
|
+
if (cell_h.get_state == Cell::UNKNOWN)
|
1052
|
+
next_gap << cell_h.x
|
1053
|
+
else
|
1054
|
+
add_gap.call()
|
1055
|
+
end
|
1056
|
+
end
|
1057
|
+
|
1058
|
+
add_gap.call()
|
1059
|
+
|
1060
|
+
return gaps
|
1061
|
+
end
|
1062
|
+
|
1063
|
+
def calc_gaps_implicit_counts(gaps)
|
1064
|
+
implicit_counts = {Cell::ZERO => 0, Cell::ONE => 0,}
|
1065
|
+
gaps[2].each do |gap|
|
1066
|
+
x_s = []
|
1067
|
+
if (gap[0] > 0)
|
1068
|
+
x_s << gap[0]-1
|
1069
|
+
end
|
1070
|
+
if (gap[-1] < max_idx())
|
1071
|
+
x_s << gap[-1]+1
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
bordering_values = {Cell::ZERO => 0, Cell::ONE => 0,}
|
1075
|
+
x_s.each do |x|
|
1076
|
+
bordering_values[get_state(x)] += 1
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
for v in [Cell::ZERO, Cell::ONE] do
|
1080
|
+
if bordering_values[opposite_value(v)] > 0
|
1081
|
+
implicit_counts[v] += 1
|
1082
|
+
end
|
1083
|
+
end
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
return implicit_counts
|
1087
|
+
end
|
922
1088
|
end
|
923
1089
|
|
924
1090
|
class CellHandle
|