tagged release 0.6.4
[parrot.git] / examples / pir / sudoku.pir
blob8639804cebe7b85e5ddd760a8186c87f4e3bfbce
1 # $Id$
3 =pod
5 =head1 TITLE
7 Sudoku - A sudoku solver
9 =head1 OVERVIEW
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.
16 =head1 SYNOPSIS
18   parrot -Ot sudoku.pir [--options] [file]
20 If no file is given a builtin game is run.
22 Valid options are:
24 =over 4
26 =item --version
28 Print version information and exit.
30 =item --help
32 Print help hint and exit.
34 =item --debug
36 Print debug output and game progress to stdout.
38 =item --inv=n..
40 Print additionally invalid state of given number(s).
42 =item --pairs
44 Print additionally fields with uniqe pairs of numbers.
46 =item --builtin=name
48 Run builtin game. If no name is given a list of builtins is printed.
50 =item --nc
52 Use ncurses for display and single step through progress. Press any key
53 for next display.
55 =back
57 =head1 DESCRIPTION
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.
63 =head1 GAME FILES
65 Game files may contain comments (hash sign in the first column)
66 digits, and dots for empty fields. E.g:
68   # std020.sud
69   # der standard 020 - leicht
70   2.1..678.
71   ...2...36
72   8.9.3...5
73   .7...4..2
74   ..6.9.5..
75   9..5...6.
76   5...4.9.7
77   71...3...
78   .987..2.3
80 =head1 PARROT
82 =head2 Parrot features used
84 =over 4
86 =item Parrot OO
88 The solver is an object as well as the display.
90 =item Freeze/thaw
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.
99 =item Libraries
101 The program uses Getopt/Obj and the ncurses library.
103 =item Exception handling
105 To turn off ncurses just in case.
107 =back
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
116 =over 4
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
125 one not zero.
127 =item I<all>
129 Hash referencing these 6 items - used for backtracking.
131 =item I<opt>
133 The option hash.
135 =item I<disp>
137 Holds an instance of the display class (I<Dummy>, I<NCurses>) to use.
139 =back
141 =head1 AUTHOR
143 Leopold Toetsch
145 =head1 COPYRIGHT
147 Same as parrot.
149 =cut
151 .const string VERSION="0.2.3"
153 .sub _main :main
154     .param pmc argv
155     .local int argc
156     .local string raw_given
157     .local pmc opt
158     opt = parse_options(argv)
159     argc = elements argv
160     dec argc
161     if argc < 0 goto get_default
162     $I0 = defined opt["builtin"]
163     if $I0 goto get_default
164     $S0 = argv[argc]
165     raw_given = read_given($S0)
166     goto done_input
167 get_default:
168     raw_given = builtin_game(opt)
169 done_input:
170     run_game(raw_given, opt)
171 .end
173 # create game class, object, display, and run
174 .sub run_game
175     .param string raw_given
176     .param pmc opt
178     .local pmc ar
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"
192     self = new "Sudoku"
193     setattribute self, "opt", opt
194     disp = self."new_display"()
195     ##push_eh nc_stop
197     self."create"(ar)
198     self."display"()
199     $I0 = self."verify"()
200     unless $I0 goto err
201     if $I0 == 1 goto ok
202     if $I0 == 2 goto fin
204     disp."print"("init ok\n")
205     $I0 = self."solve"()
206     if $I0 == 1 goto nok
207     if $I0 == 0 goto nc_stop
208 fin:
209     self."display"()
210     disp."print"("solved\n")
211     disp."wait"()
212 nc_stop:
213     self."end_display"()
214     end
215 nok:
216     # need backtracking
217     .local pmc tries, all
218     tries = new 'ResizablePMCArray'
219     all = getattribute self, "all"
220     push tries, all
221     $I0 = self."back_track"(tries)
222     if $I0 == 2 goto fin
223     disp."print"("failed\n")
224     goto nc_stop
225 err:
226     printerr "inconsistent start\n"
227     self."end_display"()
228     die 3, 100
229 .end
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
234 .sub read_given
235     .param string file_name
236     .local pmc io
237     .local string line, result, c
238     .local int i, len
239     result = ""
240     io = open file_name, "<"
241     $I0 = defined io
242     unless $I0 goto err
243 loop:
244     line = readline io
245     unless io goto done
246     c = line[0]
247     if c == '#' goto loop
248     len = length line
249     i = 0
250 lp2:
251     c = line[i]
252     if c != '.' goto no_dot
253         result .= c
254 no_dot:
255     if c < '1' goto no_num
256     if c > '9' goto no_num
257         result .= c
258 no_num:
259     inc i
260     if i < len goto lp2
261     goto loop
262 done:
263     .return(result)
264 err:
265     printerr "read '"
266     printerr file_name
267     printerr "' failed\n"
268     die 3, 100
269 .end
271 # get commandline options
272 .sub parse_options
273     .param pmc argv
275     load_bytecode "Getopt/Obj.pbc"
277     .local string prog
278     prog = shift argv
280     # Specification of command line arguments.
281     # --version, --debug, --inv=nnn, --builtin=name, --nc, --help
282     .local pmc getopts
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
289     push getopts, "nc"
290     push getopts, "help"
292     .local pmc opt
293     opt = getopts."get_options"(argv)
295     $I0 = defined opt['version']
296     unless $I0 goto n_ver
297         print prog
298         print " "
299         print VERSION
300         print "\n"
301         end
302 n_ver:
303     $I0 = defined opt['help']
304     unless $I0 goto n_help
305     print "usage: "
306     print prog
307     print " [options...] [file]\n"
308     print "see\n\tperldoc -F "
309     print prog
310     print "\nfor more\n"
311     end
313 n_help:
314     $I0 = defined opt['debug']
315     unless $I0 goto n_deb
316         print "debugging on\n"
317 n_deb:
318     .return (opt)
319 .end
321 .include "iterator.pasm"
323 # return one of the builtin games
324 .sub builtin_game
325     .param pmc opt
327     .local string raw_given, name
328     .local pmc b, it
330     b = get_builtins()
331     $I0 = exists opt["builtin"]
332     if $I0 goto some
333     $S0 = b['wikipedia']
334     .return ($S0)
335 some:
336     name = opt["builtin"]
337     if name == "1" goto list_names
338     if name goto sel_name
340 list_names:
341     new it, 'Iterator', b
342     it = .ITERATE_FROM_START
343 loop:
344     unless it goto fin
345     $S0 = shift it
346     print $S0
347     print " "
348     goto loop
349 fin:
350     say ''
351     end
353 sel_name:
354     $I0 = exists b[name]
355     if $I0 goto ok
356         printerr "no such builtin: '"
357         printerr name
358         printerr "'\n"
359         die 3, 100
361     $S0 = b[name]
362     .return ($S0)
363 .end
365 .sub get_builtins
366     .local pmc b
367     .local string raw_given
368     b = new 'Hash'
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
478     .return (b)
479 .end
481 # count zero bits
482 .sub bits0
483     .param int c
484     .local int i, n, b
486     i = 0
487     n = 0
488 loop:
489     c >>= 1        # bits start at 1
490     b = c & 1
491     if b goto is_set
492     inc n
493 is_set:
494     inc i
495     if i < 9 goto loop
496     .return (n)
497 .end
499 # count one bits (3 max - zero based)
500 .sub bits1
501     .param int c
502     .local int i, n, b
504     i = 0
505     n = 0
506 loop:
507     b = c & 1
508     c >>= 1
509     unless b goto not_set
510     inc n
511 not_set:
512     inc i
513     if i < 3 goto loop
514     .return (n)
515 .end
517 # make sure the game is valid
518 .sub verify_input
519     .param string raw
520     .local int i, c
521     i = length raw
522     if i != 81 goto len_err
523     .local pmc ar
524     ar = new 'FixedIntegerArray'
525     ar = 81
526     i = 0
527 loop:
528     $I0 = ord raw, i
529     if $I0 != 0x2e goto not_dot
530         c =  0
531         goto set_it
532 not_dot:
533     if $I0 < 0x30 goto err
534     if $I0 > 0x39 goto err
535     c = $I0 - 0x30
536 set_it:
537     ar[i] = c
538     inc i
539     if i < 81 goto loop
540     .return (ar)
541 err:
542     printerr "ill char: '"
543     $S0 = raw[i]
544     printerr $S0
545     printerr "\n"
546     die 3, 100
548 len_err:
549     printerr "length != 81 found : "
550     printerr i
551     printerr "\n"
552     die 3, 100
553 .end
556 # game methods
559 .namespace ["Sudoku"]
561 # return true if we single-step
562 .sub "step" :method
563     .local pmc opt
564     opt = getattribute self, "opt"
565     $I0 = defined opt['debug']
566     unless $I0 goto check_nc
567     .return ($I0)
568 check_nc:
569     $I0 = defined opt['nc']
570     .return ($I0)
571 .end
573 # return true if debugging is on
574 .sub "debug" :method
575     .local pmc opt
576     opt = getattribute self, "opt"
577     $I0 = defined opt['debug']
578     .return ($I0)
579 .end
581 # create 9x9 LoL
582 .sub create_1 :method
583     .param string what
584     .local pmc rcss, rcs, all
586     rcss = new 'FixedPMCArray'
587     rcss = 9
588     setattribute self, what, rcss
589     all = getattribute self, "all"
590     all[what] = rcss
592     .local int y
593     # create arrays
594     y = 0
595 ly1:
596     rcs = new 'FixedPMCArray'
597     rcs = 9
598     rcss[y] = rcs
599     inc y
600     if y < 9 goto ly1
601 .end
603 # create all arrays
604 .sub create :method
605     .param pmc ar
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
611     all = new 'Hash'
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"
627     # now fill em
628     y = 0
629 ly2:
630     x = 0
631 lx2:
632     p = y * 9
633     p += x
634     c = ar[p]
636     # the entries 'e' and 'inv' are common to all 3 views of the sudoku
637     e = new 'Integer'
638     e = c
639     inv = new 'Integer'
641     # set row
642     row = rows[y]
643     i_row = i_rows[y]
644     row[x] = e
645     i_row[x] = inv
647     # set col
648     col = cols[x]
649     i_col = i_cols[x]
650     col[y] = e
651     i_col[y] = inv
653     # set square
654     $I2 = square_of(x, y)
655     sqr = sqrs[$I2]
656     i_sqr = i_sqrs[$I2]
657     $I0 = x % 3
658     $I1 = y % 3
659     $I1 *= 3
660     $I2 = $I0 + $I1
661     sqr[$I2] = e
662     i_sqr[$I2] = inv
664     inc x
665     if x < 9 goto lx2
666     inc y
667     if y < 9 goto ly2
668 .end
670 # display
671 # TODO disp 2nd in different color, use curses or shell escapes
672 .sub display :method
673     .local pmc ar, rows, row, opt, disp
674     .local string s, c
675     .local int i, x, y, c1, c2, r, deb_pairs
676     .local string deb_n
677     deb_n = ""  # print inv for that
678     self."create_inv"()
679     opt = getattribute self, "opt"
680     disp = getattribute self, "disp"
681     $I0 = defined opt["inv"]
682     unless $I0 goto no_deb
683         deb_n = opt["inv"]
684 no_deb:
685     deb_pairs = defined opt["pairs"]
686     i = 0
687     y = 0
688     s = ""
689     r = 0
690     # orig is a linear array 0..80
691     ar = getattribute self, "orig"
692     rows = getattribute self, "rows"
693 loop_y:
694     $I0 = y % 3
695     if $I0 goto no_line
696     disp."print"(r,0,"+---------+---------+---------+\n")
697     inc r
698 no_line:
699     row = rows[y]
700     x = 0
701 loop_x:
702     c1 = ar[i]
703     c2 = row[x]
704     if c1 == 0 goto ok
705     if c2 == 0 goto ok
706     if c1 != c2 goto intern_err
708     c = "."
709     if c1 == 0 goto nxt
710         $I0 = c1 + 0x30
711         c = chr $I0
712         goto set
713 nxt:
714     if c2 == 0 goto set
715         $I0 = c2 + 0x30
716         c = chr $I0
717 set:
718     $I0 = i % 3
719     if $I0 goto sp1
720     s .= '| '
721     goto sp2
722 sp1:
723     s .= ' '
724 sp2:
725     s .= c
726     s .= ' '
727     inc i
728     inc x
729     if x < 9 goto loop_x
730     s .= '|'
731     disp."print"(r,0, s)
732     unless deb_n goto not_deb_n
733     self."deb_inv"(y, deb_n)
734 not_deb_n:
735     unless deb_pairs goto not_deb_pairs
736     self."deb_pairs"(y)
737 not_deb_pairs:
738     disp."print"("\n")
739     inc r
740     s = ""
741     inc y
742     if y < 9 goto loop_y
743     disp."print"(r,0,"+---------+---------+---------+\n")
744     inc r
745     disp."wait"()
746     .return()
747 intern_err:
748     printerr "diff between ar and try\n"
749     die 3, 100
750 .end
752 # print invalid for given row and number(s)
753 .sub deb_inv :method
754     .param int y
755     .param string ns
757     .local pmc invs, inv
758     .local int b, x, c, i, len, n
759     i = 0
760     len = length ns
761 lp_inv:
762     $S0 = ns[i]
763     n = $S0
764     print "   "
765     invs = getattribute self, "i_rows"
766     inv = invs[y]
767     x = 0
768 loop:
769     $I0 = x % 3
770     if $I0 goto nosp
771     print " "
772 nosp:
773     c = inv[x]
774     b = 1 << n
775     $I0 = c & b
776     if $I0 goto is_set
777         print "."
778         goto nxt
779 is_set:
780         print n
781 nxt:
782     inc x
783     if x < 9 goto loop
784     inc i
785     if i < len goto lp_inv
786 .end
788 # print pairs for given row
789 .sub deb_pairs :method
790     .param int y
792     .local pmc invs, inv
793     .local int x
794     print "   "
795     invs = getattribute self, "i_rows"
796     inv = invs[y]
797     x = 0
798 loop:
799     $I0 = x % 3
800     if $I0 goto nosp
801     print "   "
802 nosp:
803     .local int el, bits, i, b
804     el = inv[x]
805     bits = bits0(el)
806     if bits == 2 goto isa_pair
807     print ".."
808     goto nxt_x
809 isa_pair:
810     i = 1
811 bit_loop:
812     el >>= 1        # bits start at 1
813     b = el & 1
814     if b goto is_set
815     $I0 = i + 0x30
816     $S0 = chr $I0
817     print $S0
818 is_set:
819     inc i
820     if i <= 9 goto bit_loop
821 nxt_x:
822     inc x
823     print " "
824     if x < 9 goto loop
825 .end
827 # verify numbers
828 # returns:
829 #   0 ... failure
830 #   1 ... ok
831 #   2 ... finished
833 .sub verify :method
834     .local pmc rcss
835     .local int r, done
836     done = 2
837     rcss = getattribute self, "rows"
838     r = self."verify_1"(rcss)
839     unless r goto err
840     if r == 2 goto fin1
841     done = 1
842 fin1:
843     rcss = getattribute self, "cols"
844     r = self."verify_1"(rcss)
845     unless r goto err
846     if r == 2 goto fin2
847     done = 1
848 fin2:
849     rcss = getattribute self, "sqrs"
850     r = self."verify_1"(rcss)
851     unless r goto err
852     if r == 2 goto fin3
853     done = 1
854 fin3:
855     .return (done)
856 err:
857     .return (0)
858 .end
860 # verify rows, cols, or sqrs
861 .sub verify_1 :method
862     .param pmc rcss
864     .local int x, y, result
865     .local pmc one, e, seen, s
866     result = 2         # finished
867     y = 0
868 lpy:
869     one = rcss[y]
870     seen = new 'Hash'
871     x = 0
872 lpx:
873     e = one[x]
874     unless e goto nxtx
875     $I0 = exists seen[e]
876     unless $I0 goto not_seen
877     s = seen[e]
878     inc s
879     goto nxtx
880 not_seen:
881     seen[e] = 1
882 nxtx:
883     inc x
884     if x < 9 goto lpx
886     $I0 = elements seen
887     if $I0 == 9 goto done
888         result = 1
889 done:
890     $I0 = check_seen(seen)
891     unless $I0 goto ret_0
892     inc y
893     if y < 9 goto lpy
894     .return (result)
895 ret_0:
896     .return (0)
897 .end
899 # count seen in one
900 .sub check_seen
901     .param pmc seen
902     .local pmc it
903     new it, 'Iterator', seen
904     it = .ITERATE_FROM_START
905 loop:
906     unless it goto ok
907     $S0 = shift it
908     $I0 = seen[$S0]
909     if $I0 > 1 goto err
910     goto loop
912     .return(1)
913 err:
914     .return(0)
915 .end
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")
929 .end
931 # create row, cols, or sqrs of invalid numbers
932 # one bit per invalid
934 .sub create_inv_1 :method
935     .param pmc ars
936     .param pmc invs
937     .param string what
939     .local int x, y, n, i, c
940     .local pmc ar, inv
942     y = 0
943 lpy:
944     ar = ars[y]
945     inv = invs[y]
946     x = 0
947 lpx:
948     c = ar[x]
949     unless c goto nxt_x
950     $P0 = inv[x]
951     $P0 |= 0b1111111110
952 nxt_x:
953     inc x
954     if x < 9 goto lpx
955     n = 1
956 lpn:
957     $I0 = self."contains"(ar, n)
958     unless $I0 goto nxt_n
959         i = 0
960 fill:
961         $I0 = 1 << n
962         $P1 = inv[i]
963         $P1 |= $I0
964         inc i
965         if i < 9 goto fill
966 nxt_n:
967     inc n
968     if n <= 9 goto lpn
969     self."create_inv_n"(ar, inv, y, what)
970     inc y
971     if y < 9 goto lpy
972 .end
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
977     .param pmc ar
978     .param pmc inv
979     .param int y
980     .param string what
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
987     # same digit
988     digs = new 'FixedPMCArray'
989     digs = 9
990     n = 0
991 lpn:
992     msk = 2 << n
993     d = new 'Integer'
994     digs[n] = d
995     x = 0
996     # don't bother looking further if n is already set
997     $I1 = n + 1
998     $I0 = self."contains"(ar, $I1)
999     if $I0 goto nxt_n
1000 lpx:
1001     e1 = inv[x]
1002     $I0 = e1
1003     $I0 &= msk
1004     unless $I0 goto nxt_x
1005     $I1 = 2 << x
1006     d |= $I1
1007 nxt_x:
1008     inc x
1009     if x < 9 goto lpx
1010 nxt_n:
1011     inc n
1012     if n < 9 goto lpn
1014     x1 = 0
1015 lpx1:
1016     empty_2 = new 'ResizablePMCArray'
1017     e1 = digs[x1]
1018     n = bits0(e1)
1019     if n != 2 goto nxt_x1
1020     m = 1
1021     x2 = x1 + 1
1022 lpx2:
1023     e2 = digs[x2]
1024     if e1 != e2 goto nxt_x2
1025     inc m
1026     if m > 2 goto nxt_x1
1027     push empty_2, e1
1028     push empty_2, x1
1029     push empty_2, x2
1030 nxt_x2:
1031     inc x2
1032     if x2 < 9 goto lpx2
1033     $I0 = elements empty_2
1034     unless $I0 goto nxt_x1
1035     if m != 2 goto nxt_x1
1036     goto done
1037 nxt_x1:
1038     inc x1
1039     if x1 < 8 goto lpx1
1041     $I0 = elements empty_2
1042     unless $I0 goto ret
1043 done:
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
1048     x = 0
1049     changed = 0
1050 lpx3:
1051     $I0 = 2 << x
1052     $I0 &= pos_msk
1053     if $I0 goto nxt_x3
1054        e1 = inv[x]
1055        n = 0
1056     lpn3:
1057        if n == d1 goto nxt_n3
1058        if n == d2 goto nxt_n3
1059        # invalidate all but d1, d2 at the 2 positions
1060        $I0 = 2 << n
1061        $I1 = e1
1062        $I1 &= $I0
1063        if $I1 goto no_c
1064        e1 |= $I0
1065        changed = 1
1066     no_c:
1067     nxt_n3:
1068        inc n
1069        if n < 9 goto lpn3
1070 nxt_x3:
1071     inc x
1072     if x < 9 goto lpx3
1073     $I0 = self."debug"()
1074     unless $I0 goto ret
1076     unless changed goto ret
1077     # reuse array for debug reports
1078     unshift empty_2, y
1079     unshift empty_2, what
1080     $S0 = sprintf "*** found inv_2 %s %d: %#b %d %d\n", empty_2
1081     print $S0
1082 ret:
1083 .end
1085 # return 1 if row/col/sqr contains num n
1086 .sub contains :method
1087     .param pmc ar
1088     .param int n
1090     .local int i, c
1091     i = 0
1093     c = ar[i]
1094     if c == n goto ret_1
1095     inc i
1096     if i < 9 goto lp
1097     .return (0)
1098 ret_1:
1099     .return (1)
1100 .end
1102 # main solver method
1103 # returns
1104 #   0 ... err
1105 #   1 ... incomplete
1106 #   2 ... finito
1107 .sub solve :method
1108     .local int r
1109     #self."sanity_check"()
1110 loop:
1111     self."create_inv"()
1112     r = self."scan"()
1113     if r == -1 goto err
1114     unless r goto done
1115         $I0 = self."step"()
1116         unless $I0 goto loop
1117         self."display"()
1118         goto loop
1119 done:
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
1126     if r == 1 goto loop
1127 no_adv:
1128     .return ($I0)
1129 err:
1130     print "mismatch\n"
1131     .return (0)
1132 .end
1134 # scan for forced numbers
1135 # returns
1136 # -1 ... err
1137 # 0  ... no change
1138 # 1  ... changes
1139 .sub scan :method
1140     .local int any, y, x, m
1141     any = 0
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
1147     any |= $I0
1148     $I0 = self."scan_dbl"(rcss, i_rcss, "rows")
1149     any |= $I0
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
1155     any |= $I0
1156     $I0 = self."scan_dbl"(rcss, i_rcss, "cols")
1157     any |= $I0
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
1163     any |= $I0
1164     $I0 = self."step"()
1165     unless $I0 goto nd2
1166     self."display"()
1167 nd2:
1168     $I0 = self."scan_blocked"(rcss, i_rcss, "sqrs")
1169     any |= $I0
1170     (y, x, m) = self."best_pos"()
1171     if m != 1 goto not_uniq
1172         self."set_uniq"(y, x)
1173         any = 1
1174 not_uniq:
1175     .return (any)
1176 err:
1177     .return (-1)
1178 .end
1180 # scan for advanced methods
1181 # returns
1182 # -1 ... err
1183 # 0  ... no change
1184 # 1  ... changes
1185 .sub adv_scan :method
1186     $I0 = self."y_wing"()
1187     # TODO try more stuff
1188     .return ($I0)
1189 .end
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
1195     res = 0
1196     y = 0
1197     i_rows = getattribute self, "i_rows"
1198 loop_y:
1199     x = 0
1200     i_row = i_rows[y]
1201 loop_x:
1202     el = i_row[x]
1203     bits = bits0(el)
1204     if bits != 2 goto nxt_x
1205     $I0 = self."check_y_wing"(x, y, el)
1206     res |= $I0
1207 nxt_x:
1208     inc x
1209     if x < 9 goto loop_x
1210     inc y
1211     if y < 9 goto loop_y
1212     .return (res)
1213 .end
1215 .sub pair_vals
1216     .param int el
1217     .local int i, b, A, B
1218     A = 0
1219     i = 1           # A, B are 1-based
1220 loop:
1221     el >>= 1        # bits start at 1
1222     b = el & 1
1223     if b goto next
1224     if A goto A_is_set
1225     A = i
1226     goto next
1227 A_is_set:
1228     B = i
1229     .return (A, B)
1230 next:
1231     inc i
1232     if i <= 9 goto loop
1233     printerr "failed to fined pair"
1234     exit 1
1235 .end
1237 # get the square # of coors (x,y) TODO reuse
1238 .sub square_of
1239     .param int x
1240     .param int y
1241     x /= 3
1242     y /= 3
1243     y *= 3
1244     $I0 = x + y
1245     .return ($I0)
1246 .end
1248 # given the square # and idx inside, return coors (x,y) TODO reuse
1249 .sub square_to_xy
1250     .param int sq
1251     .param int idx
1252     .local int x, y
1253     x = sq % 3
1254     x *= 3
1255     $I0 = idx % 3
1256     x += $I0
1257     y = sq / 3
1258     y *= 3
1259     $I1 = idx / 3
1260     y += $I1
1261     .return (x, y)
1262 .end
1264 # look for another pair AC (A,C != B)
1265 # return C and the position in i_rcs
1266 .sub "y_wing-pair" :method
1267     .param pmc i_rcs
1268     .param int A
1269     .param int not_B
1270     .local int x, el, bits, p1, p2
1271     x = 0
1272 loop:
1273     el = i_rcs[x]
1274     bits = bits0(el)
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
1280         .return (p2, x)
1281 check_p2:
1282     if p2 != A goto next
1283         .return (p1, x)
1284 next:
1285     inc x
1286     if x < 9 goto loop
1287     .return (0,0)
1288 .end
1290 # look for another pair BC
1291 # return 0/1 and the position in i_rcs
1292 .sub "y_wing-pair_BC" :method
1293     .param pmc i_rcs
1294     .param int B
1295     .param int C
1296     .local int x, el, bits, p1, p2
1297     x = 0
1298 loop:
1299     el = i_rcs[x]
1300     bits = bits0(el)
1301     if bits != 2 goto next
1302     (p1, p2) = pair_vals(el)
1303     if p1 == B goto ok1
1304     if p2 == B goto ok2
1305     goto next
1306 ok1:
1307     if p2 != C goto next
1308         .return (1, x)
1309 ok2:
1310     if p1 != C goto next
1311         .return (1, x)
1312 next:
1313     inc x
1314     if x < 9 goto loop
1315     .return (0,0)
1316 .end
1318 # invalidate C from the given [start,end] range#
1319 # return 1 if something changed
1320 .sub "y_wing_inv" :method
1321     .param pmc i_rcs
1322     .param int C
1323     .param int start
1324     .param int end
1326     .local int changed, b
1327     .local pmc el
1328     changed = 0
1329     b = 1 << C
1330 loop:
1331     el = i_rcs[start]
1332     $I0 = el
1333     $I1 = $I0 & b
1334     if $I1 goto next
1335     el |= b
1336     changed = 1
1337 next:
1338     inc start
1339     if start <= end goto loop
1340     .return (changed)
1341 .end
1343 # find C for A B
1344 # and invalidate C if found
1345 .sub "find_C_y_wing_1" :method
1346     .param int x
1347     .param int y
1348     .param int A
1349     .param int B
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
1354     changed = 0
1355     sq = square_of(x, y)        # TODO reuse this func
1356     .local int C, c
1357     i_rcs = i_rcss[sq]
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"
1366         i_rcs  = i_rcss[x]
1367         (has_bc, c) = self."y_wing-pair_BC"(i_rcs, B, C)
1368         unless has_bc goto try_row
1369         bx = x
1370         by = c
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)
1378         end = start + 2
1379         changed = self."y_wing_inv"(i_rcs, C, start, end)
1380         # invalidate col x at BC
1381         i_rcs  = i_rcss[cx]
1382         sq = square_of(bx, by)
1383         ($I0, start) = square_to_xy(sq, 0)
1384         end = start + 2
1385         $I0 = self."y_wing_inv"(i_rcs, C, start, end)
1386         changed |= $I0
1387         goto show_debug
1388     try_row:
1389         if y == cy goto nope
1390         i_rcss = getattribute self, "i_rows"
1391         i_rcs  = i_rcss[y]
1392         (has_bc, c) = self."y_wing-pair_BC"(i_rcs, B, C)
1393         unless has_bc goto nope
1394         bx = c
1395         by = y
1396         .local int start, end
1397         # TODO invalidate row y too
1398         i_rcs  = i_rcss[cx]
1399         sq = square_of(bx, by)
1400         ($I0, start) = square_to_xy(sq, 0)
1401         end = start + 2
1402         changed = self."y_wing_inv"(i_rcs, C, start, end)
1403     show_debug:
1404         $I0 = self."debug"()
1405         unless $I0 goto ex
1406             $S0 = "CHG"
1407             if changed goto chg_ok
1408             $S0 = "noC"
1409         chg_ok:
1410             print $S0
1411             print " Y-WING A "
1412             print A
1413             print " B "
1414             print B
1415             print " C "
1416             print C
1417             print " at x "
1418             print x
1419             print " y "
1420             print y
1421             print " cx "
1422             print cx
1423             print " cy "
1424             print cy
1425             print " bx "
1426             print bx
1427             print " by "
1428             say by
1429             self."display"()
1430             goto ex
1432 check_row:
1433     i_rcss = getattribute self, "i_rows"
1434     i_rcs = i_rcss[y]
1435     # XXX TODO check that A is in a forced pair
1436     (C, c) = self."y_wing-pair"(i_rcs, A, B)
1437     cx = c
1438     cy = y
1439     unless C goto check_col
1440         i_rcss = getattribute self, "i_cols"
1441         i_rcs  = i_rcss[x]
1442         # XXX TODO check that B is in a forced pair
1443         (has_bc, by) = self."y_wing-pair_BC"(i_rcs, B, C)
1444         bx = cx
1445         unless has_bc goto check_col
1446         i_rcs  = i_rcss[cx]
1447         changed = self."y_wing_inv"(i_rcs, C, by, by)
1448         if changed goto show_debug
1450 check_col:
1451     # TODO
1452 nope:
1454     .return (changed)
1455 .end
1457 # find C for A, or B
1458 .sub "find_C_y_wing" :method
1459     .param int x
1460     .param int y
1461     .param int A
1462     .param int B
1464     .local int changed
1465     changed = self."find_C_y_wing_1"(x, y, A, B)
1466     unless changed goto not_A
1467     .return (changed)
1468 not_A:
1469     .return self."find_C_y_wing_1"(x, y, B, A)
1470 .end
1472 # check, if we find another pair with 1 digit in common with el
1473 .sub "check_y_wing" :method
1474     .param int x
1475     .param int y
1476     .param int el
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
1480     .local int A, B, C
1481     #trace 1
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)
1487 .end
1489 # the quare y has a uniq digit at x - set it
1490 .sub set_uniq :method
1491     .param int y
1492     .param int x
1493     .local pmc sqrs, sqr, e
1494     .local int n, b
1495     sqrs = getattribute self, "i_sqrs"
1496     sqr = sqrs[y]
1497     b = sqr[x]
1498     n = 1
1499 loop:
1500     $I0 = 1 << n
1501     $I1 = b & $I0
1502     if $I1 goto nxt
1503         sqrs = getattribute self, "sqrs"
1504         sqr = sqrs[y]
1505         e = sqr[x]
1506         e = n
1507         $I0 = self."debug"()
1508         unless $I0 goto nd
1509             print "uniq sqr="
1510             print y
1511             print " x="
1512             print x
1513             print " n="
1514             print n
1515             print "\n"
1516         nd:
1517         .return()
1518 nxt:
1519     inc n
1520     if n <= 9 goto loop
1521 .end
1523 # check inv of rows,cols,sqrs for forced numbers
1524 # returns
1525 # -1 ... err
1526 # 0  ... no change
1527 # 1  ... changes
1528 .sub scan_1 :method
1529     .param pmc rcss
1530     .param pmc i_rcss
1531     .param string what
1533     .local int x, y, c, m, n, b, xx, any
1534     .local pmc one, inv, e
1535     any = 0
1536     y = 0
1537 lpy:
1538     one = rcss[y]
1539     inv = i_rcss[y]
1540     n = 1
1541 lpn:
1542     x = 0
1543     b = 0
1544 lpx:
1545     c = inv[x]
1546     $I0 = 1 << n
1547     $I1 = c & $I0
1548     if $I1 goto nxt_x
1549         inc b
1550         xx = x
1551 nxt_x:
1552     inc x
1553     if x < 9 goto lpx
1555     if b != 1 goto nxt_n
1556         any = 1
1557         $P0 = one[xx]
1558         unless $P0 goto ok
1559         if $P0 != n goto err
1560         goto nxt_n
1561     ok:
1562         $P0 = n
1563         $I0 = self."debug"()
1564         unless $I0 goto nxt_n
1565         print "found "
1566         print what
1567         print " y="
1568         print y
1569         print " x="
1570         print xx
1571         print " n="
1572         say n
1573 nxt_n:
1574     inc n
1575     if n <= 9 goto lpn
1576     inc y
1577     if y < 9 goto lpy
1578     .return (any)
1579 err:
1580     .return (-1)
1581 .end
1583 # check double invs of rows,cols for forced rows/cols
1584 # returns
1585 # 0  ... no change
1586 # 1  ... changes
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
1591     .param pmc rcss
1592     .param pmc i_rcss
1593     .param string what
1595     .local pmc inv, bits
1596     .local int n, y, x, sx, sy, el, retval
1597     retval = 0
1598     n = 1
1599     # for all digits
1600 lpn:
1601     sx = 0
1602     # when scanning cols, sx is horizontal
1603     # need 3 cols at a time
1604 lpsx:
1605     bits = new 'FixedIntegerArray'
1606     bits = 3
1607     x = 0
1608 lpx:
1609     $I0 = sx * 3
1610     $I0 += x
1611     inv = i_rcss[$I0]
1612     sy = 0
1613 lpsy:
1614     y = 0
1615 lpy:
1616     $I1 = sy * 3
1617     $I1 += y
1618     el = inv[$I1]
1619     # if n is allowed, set a bit in bits
1620     $I2 = 1 << n
1621     $I2 &= el
1622     if $I2 goto blocked
1623     $I6 = bits[sy]
1624     $I5 = 1 << x
1625     $I6 |= $I5
1626     bits[sy] = $I6
1627 blocked:
1628     inc y
1629     if y < 3 goto lpy
1630     inc sy
1631     if sy < 3 goto lpsy
1632     inc x
1633     if x < 3 goto lpx
1634     $I3 = 0
1635 lp_c:
1636     $I4 = bits[$I3]
1637     if $I4 == 0 goto no_check
1638     inc $I3
1639     if $I3 < 3 goto lp_c
1640     #$S1 = sprintf "bits %x %x %x\n", bits
1641     #print $S1
1642     $I10 = self."check_dbl"(i_rcss, bits, sx, n, what)
1643     retval |= $I10
1644 no_check:
1645     inc sx
1646     if sx < 3 goto lpsx
1647 nxt_n:
1648     inc n
1649     if n <= 9 goto lpn
1650     .return (retval)
1651 .end
1653 # check if this is validly dbl blocking
1654 .sub check_dbl :method
1655     .param pmc i_rcss
1656     .param pmc bits
1657     .param int sx
1658     .param int n
1659     .param string what
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
1663     #trace 1
1664     m0 = bits[0]
1665     m1 = bits[1]
1666     m2 = bits[2]
1667     if m0 != m1 goto m02
1668     # m0 == m1
1669     b = bits1(m0)
1670     if b != 2 goto m02
1671     $I0 = bits1(m2)
1672     if $I0 != 3 goto m02
1673     .return self.inv_dbl(i_rcss, n, m0, sx, 2, what)
1674 m02:
1675     if m0 != m2 goto m12
1676     # m0 == m2
1677     b = bits1(m0)
1678     if b != 2 goto m12
1679     $I0 = bits1(m1)
1680     if $I0 != 3 goto m12
1681     .return self.inv_dbl(i_rcss, n, m0, sx, 1, what)
1682 m12:
1683     if m1 != m2 goto ret
1684     # m1 == m2
1685     b = bits1(m1)
1686     if b != 2 goto ret
1687     $I0 = bits1(m0)
1688     if $I0 != 3 goto ret
1689     .return self.inv_dbl(i_rcss, n, m1, sx, 0, what)
1690 ret:
1691     .return (0)
1692 .end
1694 # invalidate results found from check_dbl
1695 .sub inv_dbl :method
1696     .param pmc i_rcss
1697     .param int n
1698     .param int msk
1699     .param int sx
1700     .param int sy
1701     .param string what
1703     .local int x, y, b
1704     .local pmc inv, el
1705     x = sx * 3
1706     b = 0
1707 lpb:
1708     $I0 = 1 << b
1709     $I0 &= msk
1710     unless $I0 goto not_set
1711     $I2 = x + b
1712     inv = i_rcss[$I2]
1713     y = 0
1714 lpy:
1715     $I1 = sy * 3
1716     $I1 += y
1717     el = inv[$I1]
1718     $I3 = 1 << n
1719     el |= $I3
1720     inc y
1721     if y < 3 goto lpy
1722 not_set:
1723     inc b
1724     if b < 3 goto lpb
1725     $I0 = self."debug"()
1726     unless $I0 goto nd
1727         print "inv_dbl "
1728         print what
1729         print " n "
1730         print n
1731         print " msk "
1732         print msk
1733         print " sx "
1734         print sx
1735         print " sy "
1736         say sy
1737         self."display"()
1739     .return (1)
1740 .end
1742 # check for blocked rows or colums
1743 # returns
1744 # 0  ... no change
1745 # 1  ... changes
1746 .sub scan_blocked :method
1747     .param pmc sqrs
1748     .param pmc i_sqrs
1749     .param string what
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
1754     any = 0
1755     y = 0
1756 lpy:
1757     one = sqrs[y]
1758     inv = i_sqrs[y]
1759     n = 1
1760 lpn:
1761     x = 0
1762     b = 0
1763     rbl = 7         # blocked is reset per square row/col
1764     cbl = 7
1765     nulb = 0        # empty are set
1766     nulc = 0
1767 lpx:
1768     sc = x % 3      # square col and row
1769     sr = x / 3
1770     c = inv[x]
1771     $I0 = 1 << n
1772     $I1 = c & $I0
1773     if $I1 goto nxt_x
1774        $I0 = 1 << sr
1775        nulb |= $I0
1776        $I1 = bnot $I0
1777        rbl &= $I1
1778        $I0 = 1 << sc
1779        nulc |= $I0
1780        $I1 = bnot $I0
1781        cbl &= $I1
1782 nxt_x:
1783     inc x
1784     if x < 9 goto lpx
1786     b = 0
1787 loop_br:
1788     sh = 1 << b
1789     $I0 = rbl ~ 7
1790     $I0 &= 7
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)
1795         any |= $I0
1796 nbr:
1797     $I0 = cbl ~ 7
1798     $I0 &= 7
1799     if $I0 != sh goto nbc
1800     if nulc != sh goto nbc
1801         $I0 = self."inv_col"(y, b, n)
1802         any |= $I0
1803 nbc:
1804     inc b
1805     if b < 3 goto loop_br
1806 nxt_n:
1807     inc n
1808     if n <= 9 goto lpn
1809     inc y
1810     if y < 9 goto lpy
1811     .return (any)
1812 .end
1814 # set rest of row invalid due to blocked square
1815 # skip the square itself
1816 .sub "inv_row" :method
1817     .param int y
1818     .param int b
1819     .param int n
1820     .local pmc rows
1821     .local int r, sx
1822     rows = getattribute self, "i_rows"
1823     r = y / 3       # row of square y
1824     r *= 3
1825     r += b
1826     sx = y % 3      # skip / 3
1827     $I0 = self."inv_rc"(rows, r, sx, n, "row")
1828     .return ($I0)
1829 .end
1831 # set rest of col invalid due to blocked square
1832 .sub "inv_col" :method
1833     .param int y
1834     .param int b
1835     .param int n
1836     .local pmc cols
1837     .local int r, sx
1838     cols = getattribute self, "i_cols"
1839     r = y % 3       # col of square y
1840     r *= 3
1841     r += b
1842     sx = y / 3
1843     $I0 = self."inv_rc"(cols, r, sx, n, "col")
1844     .return ($I0)
1845 .end
1847 # set rest of row/col invalid due to blocked square
1848 .sub "inv_rc" :method
1849     .param pmc rcs
1850     .param int r
1851     .param int sx
1852     .param int n
1853     .param string what
1855     .local pmc rc
1856     .local int r, x, bb, any
1858     rc = rcs[r]
1860     bb = 1 << n
1861     any = 0
1862     x = 0
1863 loop:
1864     $I0 = x / 3         # skip this square
1865     if $I0 == sx goto nxt
1866     $P0 = rc[x]
1867     $I0 = $P0
1868     $I1 = $I0 & bb
1869     if $I1 goto nxt
1870         any = 1
1871         $P0 |= bb
1872 nxt:
1873     inc x
1874     if x < 9 goto loop
1875     unless any goto ret
1876         $I0 = self."debug"()
1877         unless $I0 goto ret
1878         print "found blocked "
1879         print what
1880         print "="
1881         print r
1882         print " n="
1883         say n
1884 ret:
1885     .return (any)
1886 .end
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)
1897 .end
1899 .sub sanity_check_rc :method
1900     .param pmc rows
1901     .param pmc cols
1902     .param string what
1904     .local pmc row, col, e1, e2
1905     .local int x, y
1906     y = 0
1907 lpy:
1908     row = rows[y]
1909     x = 0
1910 lpx:
1911     col = cols[x]
1912     e1 = row[x]
1913     e2 = col[y]
1914     eq_addr e1, e2, ok
1915     printerr "pmc borken rc y="
1916     printerr y
1917     printerr " x="
1918     printerr x
1919     printerr "\n"
1920     die 3, 100
1922     inc x
1923     if x < 9 goto lpx
1924     inc y
1925     if y < 9 goto lpy
1926 .end
1928 # backtrack progress
1929 .sub progress :method
1930     .param int size
1931     .param int y
1932     .param int x
1933     .param int n
1934     .param int m
1935     print "back_tracking "
1936     print size
1937     print " y="
1938     print y
1939     print " x="
1940     print x
1941     print " n="
1942     print n
1943     print " m="
1944     print m
1945     print "\n"
1946 .end
1948 # back_track tries
1949 .sub back_track :method
1950     .param pmc tries
1952     .local pmc tos, all, sqrs, sqr, e
1953     .local int r, size, x, y, n, m
1954     .local string state
1956     size = elements tries
1957     dec size
1958     tos = tries[size]
1959     state = freeze tos
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)
1964     .return (0)
1965 start:
1966     n = 1                       # try all numbers at best pos
1967 loop:
1968     all = thaw state            # restore state
1969     self.set_attrs(all)         # set attributes to this state
1970     sqrs = getattribute self, "sqrs"
1971     sqr = sqrs[y]
1972     e = sqr[x]
1973     e = n
1974     r = self."verify"()
1975     unless r goto nxt
1976     self."progress"(size, y, x, n, m)
1977     $I0 = self."step"()
1978     unless $I0 goto nd
1979     self."display"()
1981     r = self."solve"()
1982     if r == 0 goto nxt
1983     if r == 2 goto fin
1984     push tries, all
1985     r = self."back_track"(tries)
1986     if r == 2 goto fin
1987     $P0 = pop tries
1988 nxt:
1989     inc n
1990     if n <= 9 goto loop
1991     $I0 = self."debug"()
1992     unless $I0 goto nd2
1993     print "back "
1994     print size
1995     print "\n"
1996 nd2:
1997     .return (0)
1998 fin:
1999     .return (r)
2000 .end
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"
2010     y = 0
2011     mb = 10
2012     mx = -1
2013     my = -1
2014 lpy:
2015     sqr = sqrs[y]
2016     x = 0
2017 lpx:
2018     c = sqr[x]
2019     n = bits0(c)
2020     unless n goto no_min
2021     if n >= mb goto no_min
2022         mb = n
2023         mx = x
2024         my = y
2025 no_min:
2026     inc x
2027     if x < 9 goto lpx
2028     inc y
2029     if y < 9 goto lpy
2030     .return (my, mx, mb)
2031 .end
2033 .sub set_attrs :method
2034     .param pmc all
2035     .local pmc e
2037     e = all["rows"]
2038     setattribute self, "rows", e
2039     e = all["cols"]
2040     setattribute self, "cols", e
2041     e = all["sqrs"]
2042     setattribute self, "sqrs", e
2043     e = all["i_rows"]
2044     setattribute self, "i_rows", e
2045     e = all["i_cols"]
2046     setattribute self, "i_cols", e
2047     e = all["i_sqrs"]
2048     setattribute self, "i_sqrs", e
2049 .end
2051 # display support
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"]
2056     unless $I0 goto out
2057     stdscr = nc_start()
2058     cl = newclass "NCurses"
2059     addattribute cl, "win"
2060     p = new "NCurses"
2061     setattribute p, "win", stdscr
2063     setattribute self, "disp", p
2064     .return(p)
2065 out:
2066     cl = newclass "Dummy"
2067     p = new "Dummy"
2068     setattribute self, "disp", p
2069     .return(p)
2070 .end
2072 .sub end_display :method
2073     .local pmc opt
2074     opt = getattribute self, "opt"
2075     $I0 = defined opt["nc"]
2076     unless $I0 goto out
2077     nc_end()
2078 out:
2079 .end
2081 .namespace ["Dummy"]
2083 .sub "print" :multi(_, int, int, string) :method
2084     .param int r
2085     .param int c
2086     .param string s
2087     print s
2088 .end
2090 .sub "print" :multi(_, string) :method
2091     .param string s
2092     print s
2093 .end
2095 .sub "print" :multi(_, int) :method
2096     .param int s
2097     print s
2098 .end
2100 .sub "wait" :method
2101 .end
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
2109     .param int r
2110     .param int c
2111     .param string s
2112     .local pmc win, f
2114     win = getattribute self, "win"
2115     f = global "ncurses::mvwaddstr"
2116     f(win, r, c, s)
2117 .end
2119 .sub "print" :multi(_, string) :method
2120     .param string s
2121     .local pmc win, f
2123     win = getattribute self, "win"
2124     f = global "ncurses::waddstr"
2125     f(win, s)
2126 .end
2128 .sub "print" :multi(_, int) :method
2129     .param int i
2130     .local string s
2131     .local pmc win, f
2133     s = i
2134     win = getattribute self, "win"
2135     f = global "ncurses::waddstr"
2136     f(win, s)
2137 .end
2139 .sub "wait" :method
2140     .local pmc f
2141     .local int key
2142     f = global "ncurses::getch"
2143     key = f()
2144 .end
2146 .namespace []
2147 # ncurses support
2149 .sub nc_start
2150     .local pmc stdscr
2151     load_bytecode "library/ncurses.pasm"
2152     stdscr = _init_curses()
2153     .return(stdscr)
2154 .end
2156 .sub nc_end
2157     .local pmc endwin, curs_set
2158     curs_set = global "ncurses::curs_set"
2159     curs_set(1)
2160     endwin = global "ncurses::endwin"
2161     endwin()
2162 .end
2164 .sub _init_curses
2165     .local pmc INITSCR
2166     .local pmc START_COLOR
2167     .local pmc INIT_PAIR
2168     .local pmc COLOR_PAIR
2169     .local pmc WATTRON
2170     .local pmc CURS_SET
2171     .local pmc NODELAY
2172     .local pmc KEYPAD
2173     .local pmc STDSCR
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"
2182     STDSCR = INITSCR()
2183     START_COLOR()
2184     # Color pair 1, dark green fg, black background
2185     INIT_PAIR(1, 2, 0)
2186     $I0 = COLOR_PAIR(1)
2187     # We pass what's returned from COLOR_PAIR straight on
2188     ## WATTRON($I0)
2189     CURS_SET(0)                 # turn off cursor
2190     ## NODELAY(STDSCR, 1)       # set nodelay mode
2191     ## KEYPAD(STDSCR, 1)        # set keypad mode
2192     .return(STDSCR)
2193 .end
2195 =head1 Advanced checks
2197 =head2 Double blocked rows/columns
2199 Consider this one:
2201   # daily sudoku 16-nov-2005 very hard
2202   .5..3.9..
2203   .394.....
2204   .....964.
2205   .6...84..
2206   5.......8
2207   ..19...2.
2208   .826.....
2209   .....576.
2210   ..5.9..8.
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'.
2242 And then it's easy.
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
2249 Given this sudoku:
2251   # daily sudoku 16-nov-2005 very hard
2252   .5..3.9..
2253   .394.....
2254   .....964.
2255   .6...84..
2256   5.......8
2257   ..19...2.
2258   .826.....
2259   .....576.
2260   ..5.9..8.
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.
2299 =head2 Y-WING
2301 (This is partially still TODO)
2303 Given this suduku:
2305 # "unsolvable" 3 - Y-Wing
2306   . . . 8 . . . . 6
2307   . . 1 6 2 . 4 3 .
2308   4 . . . 7 1 . . 2
2309   . . 7 2 . . . 8 .
2310   . . . . 1 . . . .
2311   . 1 . . . 6 2 . .
2312   1 . . 7 3 . . . 4
2313   . 2 6 . 4 8 1 . .
2314   3 . . . . 5 . . .
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
2354 positions X.
2356 =head2 TODO
2358   # daily sudoku wed 28-dec-2005, very hard
2359   # backtracking
2360   ...52.63.
2361   .5.....7.
2362   9....8..2
2363   .17..4...
2364   .9.....6.
2365   ...8..31.
2366   1..6....5
2367   .4.....9.
2368   .86.95...
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
2396 See also std331.sud
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:
2407   square, row
2408   square, column
2409   row, columm
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.
2431 =cut
2433 # Local Variables:
2434 #   mode: pir
2435 #   fill-column: 100
2436 # End:
2437 # vim: expandtab shiftwidth=4 ft=pir: