#!/usr/bin/perl ### sylver - compute winning moves in Sylver Coinage. # # If g.c.d. > 1, you can get erroneous results # from setting the precision too low. sub usage { die "usage: $prog [option] ... number ...\n" . "-a\tshow all winning moves\n" . "-mN\tset precision to N longwords\n" . "-s\tread positions from standard input\n" . "-uFILE\twrite P-positions to FILE\n" . "-v\tshow losing moves and replies\n" } use integer; $0 =~ m![^/]+$! and $prog = $&; while (@ARGV and $ARGV[0] =~ /^-/) { $flag = shift; $flag eq "-a" and $allflag = 1 and next; if ($flag eq "-m") { &usage unless @ARGV; $mflag = shift; &usage unless $mflag !~ /\D/ && $mflag; next; } elsif ($flag =~ /^-m/) { $mflag = $'; &usage unless $mflag !~ /\D/ && $mflag; next; } elsif ($flag eq "-s") { $streammode = 1; next; } elsif ($flag eq "-u") { &usage unless @ARGV; $ufile = shift; next; } elsif ($flag =~ /^-u/) { $ufile = $'; next; } $flag eq "-v" and $vflag = 1 and next; &usage; } if ($ufile) { $streammode and die "sylver: -u not allowed with -s\n"; open UFILE, ">$ufile" or die "sylver: cannot write to $ufile\n"; } if ($streammode) { @ARGV and die "$prog: command-line argument position not allowed with -s\n"; while ($_ = ) { chomp; @_ = split " ", $_; $_[-1] or pop @_; # Drop final zero. &mainsolve(@_); } } else { @ARGV or &usage; &mainsolve(@ARGV); } exit 0; sub mainsolve { grep /\D/, @_ and &usage; grep {$_<2} @_ and die "$prog: numbers must be greater than 1\n"; foreach (sort {$a<=>$b} @_) { # Eliminate duplicates. $_ == $prev or $prev = $_ and push @arg, $_; } $g = &gcd(@arg); die "$prog: you must specify -m if g > 1\n" if $g > 1 && !$mflag; warn "$prog: g = 1, ignoring -m$mflag\n" if $g == 1 && $mflag; print join " ", "#", @arg, "\n"; if ($g==1) { $t = syltop(@arg); if ($t==1) { print "P\n"; exit 0; } $precision = 1 + $t / 32; } else { $precision = $mflag; $t = $precision * 32 - 1; } $string = $empty; for (@arg) { $string = &make($_, $string) } &init; $tag = "# g=$g"; if ($g == 1) { $tag .= ", t=$t"; if (&quiet($string)) { $tag .= " quiet ender" } } print "$tag\n"; # If it's a known long P-position, and -v isn't specified, # just print "P" and exit. if (!$vflag && $safe{$string} && $arg[0] != 12) { print "P\n"; exit; } &solve(1, $allflag, $string) || print "P\n"; } sub init { $empty = chr(0) x (4 * $precision); %safe = (); %canned1 = (2 => [3], 3 => [2], 4 => [6], 6 => [4,9], 8 => [12], 10 => [5,14,26]); unless ($g % 2) { &addsafe([4, 6], [8, 10, 22], [8, 10, 12, 14], [8,12,18,22], [8,12,26,30], [8,12,34,38], [8,12,42,46], [8,12,50,54]); } unless ($g % 3) { &addsafe([6, 9], [12, 15, 18], [12, 18, 21]); } unless ($g % 4) { &addsafe([8, 12]); } } sub addsafe { for (@_) { my $spos = $empty; for my $m (@$_) { $spos = &make($m, $spos) } $safe{$spos} = 1 unless (~"$spos" & "$string") =~ /[^\0]/o; } } sub solve { (my $printflag, my $allflag, my $pos) = @_; my $retval = 0; my $count=0; my $pair = $empty; my $b; my $x; my $response; my $bomb; # Is this position a single value? for ($b=1; $b<=$t; $b++) { last if vec($pos, $b, 1) } $pos eq &make($b, $empty) and return solve1($printflag, $allflag, $b); # Is it {2, 3}? ord(substr $pos, 0, 1) == 0xfc and return 0; # To save time, check for an instant winner: for ($x=2; $x<=$t; $x++) { next if vec($pos, $x, 1); # illegal return $x if $safe{&make($x, $pos)}; } my $fuse = 0; for ($x=2; $x <= $t; $x++) { if (vec($pos, $x, 1)) { # Not a legal move. $fuse ||= $x; # Measure the fuse. last if --$bomb == 0; next; } $bomb = $fuse; # Light the fuse. next if vec($pair, $x, 1); # Eliminated by pairing. my $newpos = &make($x, $pos); my $safe = $safe{$newpos}; if (!$safe and $response = &solve(0, 0, $newpos)) { vec($pair, $response, 1) = 1 if $response > $x; if ($printflag && $vflag) { # Print it as a clique if appropriate. if ($response > $x) { $clique = 1 } else { my $rpos = &make($response, $pos); $clique = !vec($rpos, $x, 1); } print $clique? "($x,$response)\n" :"$x? $response!\n"; } } else { unless ($safe) { $safe{$newpos} = 1; print UFILE &zdisp($newpos); } $printflag and print "$x!\n"; $allflag or return $x; $retval ||= $x; } } return $retval; } # syltop # # ARGUMENTS: position as a sorted set of numbers. sub syltop { my $top = ($_[-1] - 1) * ($_[-2] - 1) - 1; my $tprecision = 1 + $top / 32; my $vec = chr(0) x $tprecision; $t = $top; # temporarily for (@_) { $vec = &make($_, $vec) } while ($top > 1 && vec($vec, $top, 1)) { $top-- } return $top; } sub solve1 { (my $printflag, my $allflag, my $pos) = @_; my $sols = $canned1{$pos}; if ($sols) { if ($printflag) { foreach (@$sols) { print "$_!\n"; $allflag or last; } } return ${$sols}[0]; } &isprime($pos) and return 0; die "$prog: cannot solve {$pos}\n"; } # make - make a move in a position. # # ARGUMENTS: 0. move # 1. position as string sub make { my $i; my $j; (my $move, my $pos) = @_; vec($pos, $move, 1) = 1; for ($i=2; $i<=$t-$move; $i++) { next unless vec($pos, $i, 1); vec($pos, $move+$i, 1) = 1; } return $pos; } # quiet - is a position a quiet ender? # # ARGUMENT: 0. position as string # # This algorithm uses the characterization that no two legal moves # sum to t. sub quiet { # Find highest legal move. my ($post, $i, $t2); for ($post=$t; $post>0; $post--) { vec($_[0], $post, 1) or last; } $t2 = $t / 2; for ($i=1; $i<=$t2; $i++) { return 0 if !vec($_[0], $i, 1) && !vec($_[0], $post-$i, 1); } return 1; } # Mathematical functions: sub isprime { my $x = $_[0]; my $i; $x % 2 or return 0; my $lim = int(sqrt($x)); for ($i=3; $i<=$lim; $i+=2) { $x % $i or return 0; } return 1; } sub gcd { @_ == 1 and return $_[0]; my @list = sort {$a<=>$b} @_; my $a = shift @list; my $b = shift @list; my $g = gcd2($a,$b); @list or return $g; return &gcd2($g, &gcd(@list)); } sub gcd2 { (my $a, my $b) = @_; while ($a) { ($b, $a) = ($a, $b % $a) } return $b; } # Display functions: sub bits { my $r = ""; for ($b=0; $b<=$t; $b++) { $r .= vec($_[0], $b, 1) } return $r; } sub zdisp { my $r = ""; my $goal = $empty; for ($b=0; $b<=$t; $b++) { next unless vec($_[0], $b, 1) && !vec($goal, $b, 1); $r .= "$b "; $goal = &make($b, $goal); } return $r . "0\n"; } # Convert a bitmap to a canonical array. sub makearray { my $pos = shift; my $got = $empty; my @ret = (); for ($b=2; $b<=$t; $b++) { last if $pos eq $got; next unless vec($pos, $b, 1) && !vec($got, $b, 1); push @ret, $b; $got = &make($b, $got); } return @ret; }