7 Sudoku - A sudoku solver
11 This program implements scanning and blocked rows or columns invalidation.
12 It does not consider all effects of multiple number blocking, where a
13 combination of invalid numbers blocks a row or column. In such cases a
14 simple backtracking algorithm continues to solve the sudoku.
18 parrot -Ot sudoku.pir [--options] [file]
20 If no file is given a builtin game is run.
28 Print version information and exit.
32 Print help hint and exit.
36 Print debug output and game progress to stdout.
40 Print additionally invalid state of given number(s).
44 Print additionally fields with uniqe pairs of numbers.
48 Run builtin game. If no name is given a list of builtins is printed.
52 Use ncurses for display and single step through progress. Press any key
59 The game state is held in multiple views which share one Integer PMC
60 per common position. Thus updating a row sets also the column or square
61 information. These three views are list of lists.
65 Game files may contain comments (hash sign in the first column)
66 digits, and dots for empty fields. E.g:
69 # der standard 020 - leicht
82 =head2 Parrot features used
88 The solver is an object as well as the display.
92 For deep copying the game state for backtracking.
94 =item Multi Method Dispatch
96 The display classes define multiple methods with the name I<print>,
97 which take different types and amounts of arguments.
101 The program uses Getopt/Obj and the ncurses library.
103 =item Exception handling
105 To turn off ncurses just in case.
109 =head2 Variable indices
111 Column, rows, and sqares have zero-based indices. Squares are
112 numbered from top left to bottom right.
114 =head2 Sudoku Class attributes
118 =item I<rows, cols, sqrs>
120 LoL of 0 = free, 1..9 = number
122 =item I<i_rows, i_cols, i_sqrs>
124 LoL of a bitmask of invalid numbers per field. Bits are starting at bit
129 Hash referencing these 6 items - used for backtracking.
137 Holds an instance of the display class (I<Dummy>, I<NCurses>) to use.
151 .const string VERSION="0.2.3"
156 .local string raw_given
158 opt = parse_options(argv)
161 if argc < 0 goto get_default
162 $I0 = defined opt["builtin"]
163 if $I0 goto get_default
165 raw_given = read_given($S0)
168 raw_given = builtin_game(opt)
170 run_game(raw_given, opt)
173 # create game class, object, display, and run
175 .param string raw_given
179 ar = verify_input(raw_given)
180 .local pmc cl, self, disp
181 cl = newclass "Sudoku"
182 addattribute cl, "orig"
183 addattribute cl, "all"
184 addattribute cl, "cols"
185 addattribute cl, "rows"
186 addattribute cl, "sqrs"
187 addattribute cl, "i_cols"
188 addattribute cl, "i_rows"
189 addattribute cl, "i_sqrs"
190 addattribute cl, "opt"
191 addattribute cl, "disp"
193 setattribute self, "opt", opt
194 disp = self."new_display"()
199 $I0 = self."verify"()
204 disp."print"("init ok\n")
207 if $I0 == 0 goto nc_stop
210 disp."print"("solved\n")
217 .local pmc tries, all
218 tries = new 'ResizablePMCArray'
219 all = getattribute self, "all"
221 $I0 = self."back_track"(tries)
223 disp."print"("failed\n")
226 printerr "inconsistent start\n"
231 # read game from file
232 # - ignore comment lines w/ hash sign in first col
233 # - valid chars are dots (empty) and digits 1..9
235 .param string file_name
237 .local string line, result, c
240 io = open file_name, "<"
247 if c == '#' goto loop
252 if c != '.' goto no_dot
255 if c < '1' goto no_num
256 if c > '9' goto no_num
267 printerr "' failed\n"
271 # get commandline options
275 load_bytecode "Getopt/Obj.pbc"
280 # Specification of command line arguments.
281 # --version, --debug, --inv=nnn, --builtin=name, --nc, --help
283 getopts = new "Getopt::Obj"
284 push getopts, "version"
285 push getopts, "debug"
286 push getopts, "pairs"
287 push getopts, "inv=s"
288 push getopts, "builtin:s" # optional
293 opt = getopts."get_options"(argv)
295 $I0 = defined opt['version']
296 unless $I0 goto n_ver
303 $I0 = defined opt['help']
304 unless $I0 goto n_help
307 print " [options...] [file]\n"
308 print "see\n\tperldoc -F "
314 $I0 = defined opt['debug']
315 unless $I0 goto n_deb
316 print "debugging on\n"
321 .include "iterator.pasm"
323 # return one of the builtin games
327 .local string raw_given, name
331 $I0 = exists opt["builtin"]
336 name = opt["builtin"]
337 if name == "1" goto list_names
338 if name goto sel_name
341 new it, 'Iterator', b
342 it = .ITERATE_FROM_START
356 printerr "no such builtin: '"
367 .local string raw_given
370 # sudokusan malicious 26.6
371 raw_given = "..9...8.."
372 raw_given .= "....85..."
373 raw_given .= "4..23...1"
374 raw_given .= ".4....9.."
375 raw_given .= ".75...34."
376 raw_given .= "..2....8."
377 raw_given .= "1...59..4"
378 raw_given .= "...17...."
379 raw_given .= "..3...6.."
380 b["san_m0626"] = raw_given
382 # sudokusan atrocious 20.6
383 raw_given = "..2.1..5."
384 raw_given .= "95...736."
385 raw_given .= ".3......8"
386 raw_given .= ".8.6.1..."
387 raw_given .= "5...2...3"
388 raw_given .= "...7.5.1."
389 raw_given .= "8......3."
390 raw_given .= ".451...29"
391 raw_given .= ".1..6.4.."
392 b["san_a0620"] = raw_given
394 # sudokusan atrocious 24.6
395 raw_given = ".83......"
396 raw_given .= "....4...3"
397 raw_given .= "..79..8.6"
398 raw_given .= "....2.3.."
399 raw_given .= ".6.8.3.1."
400 raw_given .= "..5.6...."
401 raw_given .= "4.6..97.."
402 raw_given .= "3...8...."
403 raw_given .= "......12."
404 b["san_a0624"] = raw_given
406 # sudokusan atrocious 26.6
407 raw_given = "5.93....7"
408 raw_given .= "....9...."
409 raw_given .= "....64..1"
410 raw_given .= "..6.....5"
411 raw_given .= ".58...27."
412 raw_given .= "2.....4.."
413 raw_given .= "7..51...."
414 raw_given .= "....4...."
415 raw_given .= "8....61.9"
416 b["san_a0626"] = raw_given
418 # sudoku-san 4th aug 2006 - atrocious - Y-WING
419 raw_given = ".....1..."
420 raw_given .= "6..7....5"
421 raw_given .= ".82..49.."
422 raw_given .= ".734...8."
423 raw_given .= "........."
424 raw_given .= ".5...736."
425 raw_given .= "..16..23."
426 raw_given .= "9....5..1"
427 raw_given .= "...8....."
428 b["san_a0804"] = raw_given
430 # wikipedia - (one of ) the smallest (17 clues) known sudoku
431 raw_given = "1........"
432 raw_given .= "..274...."
433 raw_given .= "...5....4"
434 raw_given .= ".3......."
435 raw_given .= "75......."
436 raw_given .= ".....96.."
437 raw_given .= ".4...6..."
438 raw_given .= ".......71"
439 raw_given .= ".....1.3."
440 b["wikipedia"] = raw_given
442 # derstandard 019 schwer
443 raw_given = "....8..5."
444 raw_given .= ".123....6"
445 raw_given .= ".456....."
446 raw_given .= ".789....."
447 raw_given .= "5.......4"
448 raw_given .= ".....123."
449 raw_given .= ".....456."
450 raw_given .= "3....789."
451 raw_given .= ".5..3...."
452 b["std019"] = raw_given
454 # derstandard 018 mittel
455 raw_given = "....247.."
456 raw_given .= ".1.....5."
457 raw_given .= "..8.....3"
458 raw_given .= "..25....7"
459 raw_given .= ".4..3..8."
460 raw_given .= "6....91.."
461 raw_given .= "7.....9.."
462 raw_given .= ".3.....6."
463 raw_given .= "..581...."
464 b["std018"] = raw_given
466 # "unsolvable" 3 - Y-Wing
467 raw_given = "...8....6"
468 raw_given .= "..162.43."
469 raw_given .= "4...71..2"
470 raw_given .= "..72...8."
471 raw_given .= "....1...."
472 raw_given .= ".1...62.."
473 raw_given .= "1..73...4"
474 raw_given .= ".26.481.."
475 raw_given .= "3....5..."
476 b["uns3"] = raw_given
489 c >>= 1 # bits start at 1
499 # count one bits (3 max - zero based)
509 unless b goto not_set
517 # make sure the game is valid
522 if i != 81 goto len_err
524 ar = new 'FixedIntegerArray'
529 if $I0 != 0x2e goto not_dot
533 if $I0 < 0x30 goto err
534 if $I0 > 0x39 goto err
542 printerr "ill char: '"
549 printerr "length != 81 found : "
559 .namespace ["Sudoku"]
561 # return true if we single-step
564 opt = getattribute self, "opt"
565 $I0 = defined opt['debug']
566 unless $I0 goto check_nc
569 $I0 = defined opt['nc']
573 # return true if debugging is on
576 opt = getattribute self, "opt"
577 $I0 = defined opt['debug']
582 .sub create_1 :method
584 .local pmc rcss, rcs, all
586 rcss = new 'FixedPMCArray'
588 setattribute self, what, rcss
589 all = getattribute self, "all"
596 rcs = new 'FixedPMCArray'
606 .local int x, y, p, c
608 .local pmc cols, rows, sqrs, e, col, row, sqr, all
609 .local pmc i_cols, i_rows, i_sqrs, i_col, i_row, i_sqr, inv
610 setattribute self, "orig", ar
612 setattribute self, "all", all
613 self.create_1("rows")
614 self.create_1("cols")
615 self.create_1("sqrs")
616 self.create_1("i_rows")
617 self.create_1("i_cols")
618 self.create_1("i_sqrs")
620 rows = getattribute self, "rows"
621 cols = getattribute self, "cols"
622 sqrs = getattribute self, "sqrs"
623 i_rows = getattribute self, "i_rows"
624 i_cols = getattribute self, "i_cols"
625 i_sqrs = getattribute self, "i_sqrs"
636 # the entries 'e' and 'inv' are common to all 3 views of the sudoku
654 $I2 = square_of(x, y)
671 # TODO disp 2nd in different color, use curses or shell escapes
673 .local pmc ar, rows, row, opt, disp
675 .local int i, x, y, c1, c2, r, deb_pairs
677 deb_n = "" # print inv for that
679 opt = getattribute self, "opt"
680 disp = getattribute self, "disp"
681 $I0 = defined opt["inv"]
682 unless $I0 goto no_deb
685 deb_pairs = defined opt["pairs"]
690 # orig is a linear array 0..80
691 ar = getattribute self, "orig"
692 rows = getattribute self, "rows"
696 disp."print"(r,0,"+---------+---------+---------+\n")
706 if c1 != c2 goto intern_err
732 unless deb_n goto not_deb_n
733 self."deb_inv"(y, deb_n)
735 unless deb_pairs goto not_deb_pairs
743 disp."print"(r,0,"+---------+---------+---------+\n")
748 printerr "diff between ar and try\n"
752 # print invalid for given row and number(s)
758 .local int b, x, c, i, len, n
765 invs = getattribute self, "i_rows"
785 if i < len goto lp_inv
788 # print pairs for given row
789 .sub deb_pairs :method
795 invs = getattribute self, "i_rows"
803 .local int el, bits, i, b
806 if bits == 2 goto isa_pair
812 el >>= 1 # bits start at 1
820 if i <= 9 goto bit_loop
837 rcss = getattribute self, "rows"
838 r = self."verify_1"(rcss)
843 rcss = getattribute self, "cols"
844 r = self."verify_1"(rcss)
849 rcss = getattribute self, "sqrs"
850 r = self."verify_1"(rcss)
860 # verify rows, cols, or sqrs
861 .sub verify_1 :method
864 .local int x, y, result
865 .local pmc one, e, seen, s
866 result = 2 # finished
876 unless $I0 goto not_seen
887 if $I0 == 9 goto done
890 $I0 = check_seen(seen)
891 unless $I0 goto ret_0
903 new it, 'Iterator', seen
904 it = .ITERATE_FROM_START
917 # create invalid bits
918 .sub create_inv :method
919 .local pmc rcss, i_rcss
920 rcss = getattribute self, "rows"
921 i_rcss = getattribute self, "i_rows"
922 self."create_inv_1"(rcss, i_rcss, "row")
923 rcss = getattribute self, "cols"
924 i_rcss = getattribute self, "i_cols"
925 self."create_inv_1"(rcss, i_rcss, "col")
926 rcss = getattribute self, "sqrs"
927 i_rcss = getattribute self, "i_sqrs"
928 self."create_inv_1"(rcss, i_rcss, "sqr")
931 # create row, cols, or sqrs of invalid numbers
932 # one bit per invalid
934 .sub create_inv_1 :method
939 .local int x, y, n, i, c
957 $I0 = self."contains"(ar, n)
958 unless $I0 goto nxt_n
969 self."create_inv_n"(ar, inv, y, what)
974 # if inv contains 2 identical entries and exactly 2 nums are
975 # allowed these 2 positions are invalid for all other digits
976 .sub create_inv_n :method
982 .local int x, x1, x2, n, m, msk
983 .local pmc d, e1, e2, empty_2, digs
985 # transpose into a digit-base array with positions as bits
986 # this should simplify the test for 2 empty squares with
988 digs = new 'FixedPMCArray'
996 # don't bother looking further if n is already set
998 $I0 = self."contains"(ar, $I1)
1004 unless $I0 goto nxt_x
1016 empty_2 = new 'ResizablePMCArray'
1019 if n != 2 goto nxt_x1
1024 if e1 != e2 goto nxt_x2
1026 if m > 2 goto nxt_x1
1033 $I0 = elements empty_2
1034 unless $I0 goto nxt_x1
1035 if m != 2 goto nxt_x1
1041 $I0 = elements empty_2
1044 .local int d1, d2, pos_msk, changed
1045 pos_msk = empty_2[0] # positions 1 based
1046 d1 = empty_2[1] # 0 based
1047 d2 = empty_2[2] # 0 based
1057 if n == d1 goto nxt_n3
1058 if n == d2 goto nxt_n3
1059 # invalidate all but d1, d2 at the 2 positions
1073 $I0 = self."debug"()
1076 unless changed goto ret
1077 # reuse array for debug reports
1079 unshift empty_2, what
1080 $S0 = sprintf "*** found inv_2 %s %d: %#b %d %d\n", empty_2
1085 # return 1 if row/col/sqr contains num n
1086 .sub contains :method
1094 if c == n goto ret_1
1102 # main solver method
1109 #self."sanity_check"()
1116 unless $I0 goto loop
1120 #self."sanity_check"()
1121 $I0 = self."verify"()
1122 # if yet unfished, try advanced methods before back_tracking starts
1123 unless $I0 == 1 goto no_adv
1124 r = self."adv_scan"()
1125 # if changes, start over with "normal" stuff
1134 # scan for forced numbers
1140 .local int any, y, x, m
1142 .local pmc rcss, i_rcss
1143 rcss = getattribute self, "rows"
1144 i_rcss = getattribute self, "i_rows"
1145 $I0 = self."scan_1"(rcss, i_rcss, "rows")
1146 if $I0 == -1 goto err
1148 $I0 = self."scan_dbl"(rcss, i_rcss, "rows")
1151 rcss = getattribute self, "cols"
1152 i_rcss = getattribute self, "i_cols"
1153 $I0 = self."scan_1"(rcss, i_rcss, "cols")
1154 if $I0 == -1 goto err
1156 $I0 = self."scan_dbl"(rcss, i_rcss, "cols")
1159 rcss = getattribute self, "sqrs"
1160 i_rcss = getattribute self, "i_sqrs"
1161 $I0 = self."scan_1"(rcss, i_rcss, "sqrs")
1162 if $I0 == -1 goto err
1168 $I0 = self."scan_blocked"(rcss, i_rcss, "sqrs")
1170 (y, x, m) = self."best_pos"()
1171 if m != 1 goto not_uniq
1172 self."set_uniq"(y, x)
1180 # scan for advanced methods
1185 .sub adv_scan :method
1186 $I0 = self."y_wing"()
1187 # TODO try more stuff
1191 .sub "y_wing" :method
1192 # scan for pairs all over
1193 .local int x, y, bits, el, res
1194 .local pmc i_rows, i_row
1197 i_rows = getattribute self, "i_rows"
1204 if bits != 2 goto nxt_x
1205 $I0 = self."check_y_wing"(x, y, el)
1209 if x < 9 goto loop_x
1211 if y < 9 goto loop_y
1217 .local int i, b, A, B
1219 i = 1 # A, B are 1-based
1221 el >>= 1 # bits start at 1
1233 printerr "failed to fined pair"
1237 # get the square # of coors (x,y) TODO reuse
1248 # given the square # and idx inside, return coors (x,y) TODO reuse
1264 # look for another pair AC (A,C != B)
1265 # return C and the position in i_rcs
1266 .sub "y_wing-pair" :method
1270 .local int x, el, bits, p1, p2
1275 if bits != 2 goto next
1276 (p1, p2) = pair_vals(el)
1277 if p1 == not_B goto next
1278 if p2 == not_B goto next
1279 if p1 != A goto check_p2
1282 if p2 != A goto next
1290 # look for another pair BC
1291 # return 0/1 and the position in i_rcs
1292 .sub "y_wing-pair_BC" :method
1296 .local int x, el, bits, p1, p2
1301 if bits != 2 goto next
1302 (p1, p2) = pair_vals(el)
1307 if p2 != C goto next
1310 if p1 != C goto next
1318 # invalidate C from the given [start,end] range#
1319 # return 1 if something changed
1320 .sub "y_wing_inv" :method
1326 .local int changed, b
1339 if start <= end goto loop
1344 # and invalidate C if found
1345 .sub "find_C_y_wing_1" :method
1350 # check same row, col, or sqr for a pair with A and not B
1351 .local pmc i_rcss, i_rcs
1352 i_rcss = getattribute self, "i_sqrs"
1353 .local int sq, changed
1355 sq = square_of(x, y) # TODO reuse this func
1358 (C, c) = self."y_wing-pair"(i_rcs, A, B)
1359 unless C goto check_row # TODO row, col
1360 # convert the square coordinate to (x, y)
1361 .local int cx, cy, bx, by, has_bc
1362 (cx, cy) = square_to_xy(sq, c) # AC
1363 if x == cx goto try_row
1364 # check col and row at AB for a BC pair
1365 i_rcss = getattribute self, "i_cols"
1367 (has_bc, c) = self."y_wing-pair_BC"(i_rcs, B, C)
1368 unless has_bc goto try_row
1371 # but B have to be in a different square too
1372 $I0 = square_of(bx, by)
1373 if sq == $I0 goto try_row
1374 .local int start, end
1375 # invalidate col x in sqr(x,y)
1376 sq = square_of(x, y)
1377 ($I0, start) = square_to_xy(sq, 0)
1379 changed = self."y_wing_inv"(i_rcs, C, start, end)
1380 # invalidate col x at BC
1382 sq = square_of(bx, by)
1383 ($I0, start) = square_to_xy(sq, 0)
1385 $I0 = self."y_wing_inv"(i_rcs, C, start, end)
1389 if y == cy goto nope
1390 i_rcss = getattribute self, "i_rows"
1392 (has_bc, c) = self."y_wing-pair_BC"(i_rcs, B, C)
1393 unless has_bc goto nope
1396 .local int start, end
1397 # TODO invalidate row y too
1399 sq = square_of(bx, by)
1400 ($I0, start) = square_to_xy(sq, 0)
1402 changed = self."y_wing_inv"(i_rcs, C, start, end)
1404 $I0 = self."debug"()
1407 if changed goto chg_ok
1433 i_rcss = getattribute self, "i_rows"
1435 # XXX TODO check that A is in a forced pair
1436 (C, c) = self."y_wing-pair"(i_rcs, A, B)
1439 unless C goto check_col
1440 i_rcss = getattribute self, "i_cols"
1442 # XXX TODO check that B is in a forced pair
1443 (has_bc, by) = self."y_wing-pair_BC"(i_rcs, B, C)
1445 unless has_bc goto check_col
1447 changed = self."y_wing_inv"(i_rcs, C, by, by)
1448 if changed goto show_debug
1457 # find C for A, or B
1458 .sub "find_C_y_wing" :method
1465 changed = self."find_C_y_wing_1"(x, y, A, B)
1466 unless changed goto not_A
1469 .return self."find_C_y_wing_1"(x, y, B, A)
1472 # check, if we find another pair with 1 digit in common with el
1473 .sub "check_y_wing" :method
1477 # ok - we have a pair at col/row (x,y) with inv_bits el
1478 # assume, we are at the corner of the Y
1479 # let one number be A, the other B
1482 (A, B) = pair_vals(el)
1483 # now find another pair in
1484 # - another *this* row, col, or square
1485 # - AC or BC giving another unique element C
1486 .return self."find_C_y_wing"(x, y, A, B)
1489 # the quare y has a uniq digit at x - set it
1490 .sub set_uniq :method
1493 .local pmc sqrs, sqr, e
1495 sqrs = getattribute self, "i_sqrs"
1503 sqrs = getattribute self, "sqrs"
1507 $I0 = self."debug"()
1523 # check inv of rows,cols,sqrs for forced numbers
1533 .local int x, y, c, m, n, b, xx, any
1534 .local pmc one, inv, e
1555 if b != 1 goto nxt_n
1559 if $P0 != n goto err
1563 $I0 = self."debug"()
1564 unless $I0 goto nxt_n
1583 # check double invs of rows,cols for forced rows/cols
1587 # this implements half of TODO item 1 (digit '7' ...)
1588 # scan_dbl finds both occurencies of the blocked '7' but needs more testing still
1590 .sub scan_dbl :method
1595 .local pmc inv, bits
1596 .local int n, y, x, sx, sy, el, retval
1602 # when scanning cols, sx is horizontal
1603 # need 3 cols at a time
1605 bits = new 'FixedIntegerArray'
1619 # if n is allowed, set a bit in bits
1637 if $I4 == 0 goto no_check
1639 if $I3 < 3 goto lp_c
1640 #$S1 = sprintf "bits %x %x %x\n", bits
1642 $I10 = self."check_dbl"(i_rcss, bits, sx, n, what)
1653 # check if this is validly dbl blocking
1654 .sub check_dbl :method
1660 # we must have 2 masks with the same 2 bits set and another one
1661 # where the clear one is also set e.g. 3 7 3
1662 .local int m0, m1, m2, b
1667 if m0 != m1 goto m02
1672 if $I0 != 3 goto m02
1673 .return self.inv_dbl(i_rcss, n, m0, sx, 2, what)
1675 if m0 != m2 goto m12
1680 if $I0 != 3 goto m12
1681 .return self.inv_dbl(i_rcss, n, m0, sx, 1, what)
1683 if m1 != m2 goto ret
1688 if $I0 != 3 goto ret
1689 .return self.inv_dbl(i_rcss, n, m1, sx, 0, what)
1694 # invalidate results found from check_dbl
1695 .sub inv_dbl :method
1710 unless $I0 goto not_set
1725 $I0 = self."debug"()
1742 # check for blocked rows or colums
1746 .sub scan_blocked :method
1751 .local int x, y, c, m, n, b, sh, any
1752 .local pmc one, inv, e
1753 .local int sr, sc, cbl, rbl, nulb, nulc
1763 rbl = 7 # blocked is reset per square row/col
1765 nulb = 0 # empty are set
1768 sc = x % 3 # square col and row
1791 # need to have 2 blocked and one not-filled
1792 if $I0 != sh goto nbr
1793 if nulb != sh goto nbr
1794 $I0 = self."inv_row"(y, b, n)
1799 if $I0 != sh goto nbc
1800 if nulc != sh goto nbc
1801 $I0 = self."inv_col"(y, b, n)
1805 if b < 3 goto loop_br
1814 # set rest of row invalid due to blocked square
1815 # skip the square itself
1816 .sub "inv_row" :method
1822 rows = getattribute self, "i_rows"
1823 r = y / 3 # row of square y
1826 sx = y % 3 # skip / 3
1827 $I0 = self."inv_rc"(rows, r, sx, n, "row")
1831 # set rest of col invalid due to blocked square
1832 .sub "inv_col" :method
1838 cols = getattribute self, "i_cols"
1839 r = y % 3 # col of square y
1843 $I0 = self."inv_rc"(cols, r, sx, n, "col")
1847 # set rest of row/col invalid due to blocked square
1848 .sub "inv_rc" :method
1856 .local int r, x, bb, any
1864 $I0 = x / 3 # skip this square
1865 if $I0 == sx goto nxt
1876 $I0 = self."debug"()
1878 print "found blocked "
1888 # check that pmcs in rows, cols and sqrs are the same
1889 .sub sanity_check :method
1890 .local pmc rows, cols
1891 rows = getattribute self, "rows"
1892 cols = getattribute self, "cols"
1893 self."sanity_check_rc"(rows, cols)
1894 rows = getattribute self, "i_rows"
1895 cols = getattribute self, "i_cols"
1896 self."sanity_check_rc"(rows, cols)
1899 .sub sanity_check_rc :method
1904 .local pmc row, col, e1, e2
1915 printerr "pmc borken rc y="
1928 # backtrack progress
1929 .sub progress :method
1935 print "back_tracking "
1949 .sub back_track :method
1952 .local pmc tos, all, sqrs, sqr, e
1953 .local int r, size, x, y, n, m
1956 size = elements tries
1960 (y, x, m) = self."best_pos"()
1961 if y >= 0 goto start
1962 # this shouldn't happen
1963 self."progress"(size, y, x, -1)
1966 n = 1 # try all numbers at best pos
1968 all = thaw state # restore state
1969 self.set_attrs(all) # set attributes to this state
1970 sqrs = getattribute self, "sqrs"
1976 self."progress"(size, y, x, n, m)
1985 r = self."back_track"(tries)
1991 $I0 = self."debug"()
2002 # return the square coors of the minimum freedom
2003 # used for backtracking
2004 # if m == 1 this is a forced uniq position
2005 .sub best_pos :method
2006 .local pmc sqrs, sqr
2007 .local int x, y, n, c, mx, my, mb
2009 sqrs = getattribute self, "i_sqrs"
2020 unless n goto no_min
2021 if n >= mb goto no_min
2030 .return (my, mx, mb)
2033 .sub set_attrs :method
2038 setattribute self, "rows", e
2040 setattribute self, "cols", e
2042 setattribute self, "sqrs", e
2044 setattribute self, "i_rows", e
2046 setattribute self, "i_cols", e
2048 setattribute self, "i_sqrs", e
2052 .sub new_display :method
2053 .local pmc stdscr, opt, cl, p, s, it, f, gl
2054 opt = getattribute self, "opt"
2055 $I0 = defined opt["nc"]
2058 cl = newclass "NCurses"
2059 addattribute cl, "win"
2061 setattribute p, "win", stdscr
2063 setattribute self, "disp", p
2066 cl = newclass "Dummy"
2068 setattribute self, "disp", p
2072 .sub end_display :method
2074 opt = getattribute self, "opt"
2075 $I0 = defined opt["nc"]
2081 .namespace ["Dummy"]
2083 .sub "print" :multi(_, int, int, string) :method
2090 .sub "print" :multi(_, string) :method
2095 .sub "print" :multi(_, int) :method
2103 .namespace ["NCurses"]
2105 # TODO remember last position, parse newlines to increment row
2106 # this should better be all in a new library
2108 .sub "print" :multi(_, int, int, string) :method
2114 win = getattribute self, "win"
2115 f = global "ncurses::mvwaddstr"
2119 .sub "print" :multi(_, string) :method
2123 win = getattribute self, "win"
2124 f = global "ncurses::waddstr"
2128 .sub "print" :multi(_, int) :method
2134 win = getattribute self, "win"
2135 f = global "ncurses::waddstr"
2142 f = global "ncurses::getch"
2151 load_bytecode "library/ncurses.pasm"
2152 stdscr = _init_curses()
2157 .local pmc endwin, curs_set
2158 curs_set = global "ncurses::curs_set"
2160 endwin = global "ncurses::endwin"
2166 .local pmc START_COLOR
2167 .local pmc INIT_PAIR
2168 .local pmc COLOR_PAIR
2174 INITSCR = global "ncurses::initscr"
2175 START_COLOR = global "ncurses::start_color"
2176 INIT_PAIR = global "ncurses::init_pair"
2177 COLOR_PAIR = global "ncurses::COLOR_PAIR"
2178 WATTRON = global "ncurses::wattron"
2179 CURS_SET = global "ncurses::curs_set"
2180 NODELAY = global "ncurses::nodelay"
2181 KEYPAD = global "ncurses::keypad"
2184 # Color pair 1, dark green fg, black background
2187 # We pass what's returned from COLOR_PAIR straight on
2189 CURS_SET(0) # turn off cursor
2190 ## NODELAY(STDSCR, 1) # set nodelay mode
2191 ## KEYPAD(STDSCR, 1) # set keypad mode
2195 =head1 Advanced checks
2197 =head2 Double blocked rows/columns
2201 # daily sudoku 16-nov-2005 very hard
2212 It got solved until here, then backtracking began (and succeeded).
2214 +---------+---------+---------+
2215 | 4 5 6 | 8 3 . | 9 . . | 777 77. 7..
2216 | . 3 9 | 4 . . | 8 . . | .77 7.. 7..
2217 | . . 8 | . . 9 | 6 4 3 | ..7 ..7 777
2218 +---------+---------+---------+
2219 | . 6 . | . . 8 | 4 . . | .7. ..7 7..
2220 | 5 . . | . . . | . . 8 | 7.. ... 7.7
2221 | 8 . 1 | 9 . . | . 2 6 | 7.7 7.. 777 <<<<<<<<<<
2222 +---------+---------+---------+
2223 | . 8 2 | 6 . . | . . . | .77 7.. 777
2224 | . . . | 2 8 5 | 7 6 . | 777 777 777
2225 | 6 . 5 | . 9 . | 2 8 . | 7.7 .7. 777
2226 +---------+---------+---------+
2228 Have a look at the marked row 5. '3' and '5' can't be in col 1.
2229 So '3' and '5' have to be at the right side of the row.
2231 Now take a look at the '7' - invalid positions are shown above already
2232 (dumped with the --inv=7 option).
2234 In both squares 0 and 6 the '7' can only be in columns 0 or 1. This
2235 implies that a '7' has to be in col 2, row 3 or 4. Looking at
2236 square 5, the '7' is also in row 3 or 4. Therefore the '7' in the
2237 middle square (4) has to be too in row 5.
2239 Voila we have 3 numbers (3,5,7) which are somewhere on the right
2240 side of row 5 and we get a unique number in row 5, col 1 - the '4'.
2244 One part (the '7') is implemented in C<scan_dbl>, which
2245 boils down this case to the other one below.
2247 =head2 Blocking due to multiple others
2251 # daily sudoku 16-nov-2005 very hard
2262 Earlier sudoku.pir started backtracking at:
2264 +---------+---------+---------+
2265 | . . 1 | 3 8 5 | . . . |
2266 | 6 8 7 | . 1 . | . 9 . |
2267 | 2 3 5 | 6 9 7 | . . 1 |
2268 +---------+---------+---------+
2269 | 1 . . | 9 7 3 | . 5 . |
2270 | . 7 6 | 5 . 8 | 1 3 . |
2271 | . 5 . | . 6 1 | . . . |
2272 +---------+---------+---------+
2273 | 7 1 . | 8 . . | . . 4 |
2274 | . . . | 7 . . | . 1 8 |
2275 | . . . | 1 . 9 | 7 . . |
2276 +---------+---------+---------+
2278 In columns 7 the digits (9,5,3) are blocking this column in square 8
2279 so that the digits (2,6) have to be in column 7 too. Which implies
2280 that in square 2 we have a unique '7' at row 0, col 7:
2282 +---------+---------+---------+
2283 | . . 1 | 3 8 5 | x 7 y | (x,y) = (2|6)
2284 | 6 8 7 | . 1 . | . 9 . |
2285 | 2 3 5 | 6 9 7 | . . 1 |
2286 +---------+---------+---------+
2287 | 1 . . | 9 7 3 | . 5 . |
2288 | . 7 6 | 5 . 8 | 1 3 . |
2289 | . 5 . | . 6 1 | . . . |
2290 +---------+---------+---------+
2291 | 7 1 . | 8 . . | a . 4 | (a,b,c) = (3|5|9)
2292 | . . . | 7 . . | b 1 8 |
2293 | . . . | 1 . 9 | 7 . c |
2294 +---------+---------+---------+
2296 Now the tests in "create_inv_n" invalidate illegal positions
2297 due to multiple-blocking and other tests are likely to proceed.
2301 (This is partially still TODO)
2305 # "unsolvable" 3 - Y-Wing
2316 It started backtracking at:
2318 +---------+---------+---------+
2319 | . 3 . | 8 5 4 | . 1 6 | .. .. 29 .. .. .. 79 .. ..
2320 | . . 1 | 6 2 9 | 4 3 . | .. .. .. .. .. .. .. .. ..
2321 | 4 6 . | 3 7 1 | . . 2 | .. .. .. .. .. .. .. 59 ..
2322 +---------+---------+---------+
2323 | . 4 7 | 2 9 3 | . 8 1 | 56 .. .. .. .. .. 56 .. ..
2324 | . . . | . 1 7 | 3 . . | .. .. .. 45 .. .. .. .. 59
2325 | . 1 3 | . 8 6 | 2 . . | 59 .. .. 45 .. .. .. .. ..
2326 +---------+---------+---------+
2327 | 1 . . | 7 3 2 | . . 4 | .. .. .. .. .. .. .. .. ..
2328 | . 2 6 | 9 4 8 | 1 . 3 | 57 .. .. .. .. .. .. 57 ..
2329 | 3 . 4 | 1 6 5 | . 2 . | .. .. .. .. .. .. .. .. ..
2330 +---------+---------+---------+
2332 The numbers on the right side are showing squares with unique pairs.
2333 Having a look at the columns 7 and 8, we see these pairs (79, 59, and 57)
2335 Let's label these numbers as A, B, and C:
2337 +---------+---------+---------+
2338 | . 3 . | 8 5 4 | AC 1 6 |
2339 | . . 1 | 6 2 9 | 4 3 . |
2340 | 4 6 . | 3 7 1 | . AB 2 |
2341 +---------+---------+---------+
2342 | . 4 7 | 2 9 3 | . 8 1 |
2343 | . . . | . 1 7 | 3 . . |
2344 | . 1 3 | . 8 6 | 2 . . |
2345 +---------+---------+---------+
2346 | 1 . . | 7 3 2 | X . 4 |
2347 | . 2 6 | 9 4 8 | 1 BC 3 |
2348 | 3 . 4 | 1 6 5 | X 2 . |
2349 +---------+---------+---------+
2351 When we now try to fill row 2, column 7 with A or B, we see that at
2352 positions X, a C can't be valid. Either it's blocked via the column
2353 or directly via the last square. Thus we can eliminate digit 7 from
2358 # daily sudoku wed 28-dec-2005, very hard
2370 This one starts backtracking early. The '6' is an 'X-Wing' like
2371 configuration (col 1 and row 2 with a common corner have just 2
2372 possible positions, just one is valid, when you try both).
2373 The same happens a bit later with '9'.
2375 +---------+---------+---------+
2376 | . 7 . | 5 2 . | 6 3 . | 666 666 666
2377 | . 5 . | . . . | . 7 . | .66 6.. 666
2378 | 9 . . | . . 8 | . . 2 | 6.6 6.6 666
2379 +---------+---------+---------+
2380 | . 1 7 | . . 4 | . . . | .66 6.6 666
2381 | . 9 . | . . . | . 6 . | 666 666 666
2382 | . . . | 8 . . | 3 1 . | ..6 6.. 666
2383 +---------+---------+---------+
2384 | 1 . 9 | 6 . . | . . 5 | 666 666 666
2385 | . 4 . | . . . | . 9 6 | 666 666 666
2386 | . 8 6 | . 9 5 | . . 3 | 666 666 666
2387 +---------+---------+---------+
2389 Here is starts backtracking. A possible improvement would be:
2391 - detect such digit configuration
2392 - only backtrack try this digit ('6') above
2394 =head2 TODO deadly square
2398 =head2 TODO Generalization
2400 A Sudoku has 2 dimensions and 3 connected views (row, column, and
2401 square). There are 1-dim tests, which work for all views. 2-dim tests
2402 are a bit more tricky to generalize and not yet done properly.
2404 Basically: as only 2 views are independant, all these tests can
2405 work on 2 of 3 views:
2411 Now the problem is, how to generalize the possible other direction.
2412 Let's call it the 'neighbour'. A neighbour is always 'towards' the
2413 second view. A row has 9 column neighbours and 3
2414 square neighbours. A square has 3 row and 3 column neighbours.
2415 (Maybe neighbour is a bad term as it does contain itself).
2417 C<scan_dbl> can now easily be reviewed and generalized:
2419 For all neighbours (n): If in the view (v0) a digit is valid in only
2420 one of (n)'s views: giving (v1), this digit is invalid in the
2421 complement of the intersection (v0 & v1).
2423 NB: it seems to be simpler to just hack the code as to utter the
2424 idea in $human_lang.
2426 This is trivial if these views are (row, column) as the intersection
2427 is just one point, but it is the generalization of the 'inv_1' code.
2429 Another example of a 2-dim test is of course Y-Wing.
2437 # vim: expandtab shiftwidth=4 ft=pir: