#!/usr/bin/perl -w use strict; use integer; use Getopt::Std; # Boards are read from stdin or as filename on the command line. # Board format is 21 lines, with space or a digit in each location. # Trailing characters on each line are optional. # Holds solved cells in an 441-element array my @puzzle; my ($helpercmd,$nosolve,$noboard,$widemodein,$widemodeout); sub initboard() { my $i; @puzzle=(); for($i=0;$i<441;$i++) { $puzzle[$i]=0; } } sub placenum($$) { my ($i,$val)=@_; die if($puzzle[$i]); $puzzle[$i]=$val; } sub parsecmdline() { my %opts=(); my $opts="1234567IOhnqw"; unless(getopts($opts,\%opts)) { print STDERR "usage: sudoku [-$opts]\n"; exit(1); } my $skippasses=join("",grep {$opts{$_}} 1..7); $helpercmd="sudoku -9$skippasses"; $nosolve=$opts{n}; $noboard=$opts{q}; $widemodein=($opts{w} or $opts{I}); $widemodeout=($opts{w} or $opts{O}); if($opts{h}) { print <<"EOD"; usage: sudoku [-$opts] -# don't use pass <#> (pass 9, guess-and-check, is never used) -I use wide mode on input: an extra space between each column -O use wide mode on output: an extra space between each column -h show this help message -n don't try to solve the board -q don't output any board (unless -v is given and board is unsolved) -w use wide mode: an extra space between each column EOD exit(0); } } sub mustbewhite($) { my ($i)=@_; my $row=$i/21; my $col=$i%21; return 1 if((9<=$col and $col<12) and ($row<6 or 15<=$row)); return 1 if((9<=$row and $row<12) and ($col<6 or 15<=$col)); return 0; } sub printboard() { my ($i,$j); for($i=0;$i<21;$i++) { for($j=0;$j<21;$j++) { print " " if($widemodeout and $j); if(mustbewhite(21*$i+$j)) { print " "; } else { print $puzzle[21*$i+$j]?$puzzle[21*$i+$j]:$widemodeout?"_":" "; } } print "\n"; } } sub readfile() { my ($i,$j,$line); for($i=0;$i<21;$i++) { die unless(defined($line=<>)); chomp $line; for($j=0;$j<21;$j++) { last unless(length $line); if($widemodein and $j) { die "Invalid character in widemode column: ".substr($line,0,1) if(" " ne substr($line,0,1)); $line=substr($line,1); last unless(length $line); } if(substr($line,0,1) =~ /[1-9]/) { die "Digit in whitespace-required area at r".($i+1)."c".($j+1) if(mustbewhite($i*21+$j)); placenum(21*$i+$j,substr($line,0,1)); } elsif(substr($line,0,1) =~ /[^0 ._]/) { die "Invalid character in board: ".substr($line,0,1); } $line=substr($line,1); } } } sub invokehelper($) { my ($offset)=@_; my $old=""; my $found=0; my ($i,$j,$new); for($i=0;$i<9;$i++) { for($j=0;$j<9;$j++) { $old.=$puzzle[21*$i+$j+$offset]?$puzzle[21*$i+$j+$offset]:" "; } $old.="\n"; } $new=`echo '$old'|sudoku -9`; die unless(defined $new and 90==length($new)); for($i=0;$i<90;$i++) { if(substr($old,$i,1) ne substr($new,$i,1)) { placenum(21*($i/10)+($i%10)+$offset,substr($new,$i,1)); $found++; } } return $found; } sub solveboard() { my $keepgoing; do { $keepgoing=0; $keepgoing+=invokehelper(0); $keepgoing+=invokehelper(12); $keepgoing+=invokehelper(132); $keepgoing+=invokehelper(252); $keepgoing+=invokehelper(264); } while($keepgoing); } # Returns true iff the board is fully solved sub issolved() { my $retval=1; my ($i,$j); for($i=0;$i<441;$i++) { next if(mustbewhite($i)); return 0 unless($puzzle[$i]); } return 1; } initboard(); parsecmdline(); readfile(); solveboard() unless($nosolve); my $issolved=issolved(); printboard() unless($noboard); exit 1 if($noboard and not $issolved); 0;