#!/usr/bin/perl -w use strict; my $NUM_BOWLS = 12; my $PASS = -1; my $WINNING_SCORE = 24; my $PLAYER_ONE = 0; my $PLAYER_TWO = 1; my $DRAW = 2; my $NONE = 3; my @type; my $AUTO = 0; my $SEMI = 1; my $MANUAL = 2; my %db; sub move { my ($m, $player, @score, @stones); ($m, $player, $score[0], $score[1], @stones) = @_; die if $m != $PASS and ($m < 0 or $m >= $NUM_BOWLS); die if $player != 0 and $player != 1; die if $score[0] < 0 or $score[0] >= $WINNING_SCORE; die if $score[1] < 0 or $score[1] >= $WINNING_SCORE; die if @stones != $NUM_BOWLS; if ($m != $PASS) { my @old_stones = @stones; my $left = $stones[$m]; my ($target, $b); die "bad move $m: bowl contains no stones" unless $stones[$m]; $stones[$m] = 0; $target = ($m + 1) % $NUM_BOWLS; while ($left--) { if ($old_stones[$target] == 1) { if ($stones[$target] == 1) { $stones[$target] = 0; ++ $score[$player]; } die if $stones[$target] != 0; ++ $score[$player]; } else { ++ $stones[$target]; } $target = ($target + 1) % $NUM_BOWLS; } } return ($player ? 0 : 1, $score[0], $score[1], @stones); } sub draw { my ($next, @score, @stones); ($next, $score[0], $score[1], @stones) = @_; die if $next != 0 and $next != 1; die if $score[0] < 0; die if $score[1] < 0; die if @stones != $NUM_BOWLS; my $b; print "\n--------\n"; print "Score: $score[0] - $score[1] (", $score[0] - $score[1], " to Player 1)\n"; print "Player ", $next+1, "'s turn\n"; print "Bowl number:"; for ($b = $NUM_BOWLS - 1; $b >= ($NUM_BOWLS / 2); $b--) { print "\t", $b+1; } print "\n\t"; for ($b = $NUM_BOWLS - 1; $b >= ($NUM_BOWLS / 2); $b--) { print "\t", $stones[$b]; } print "\n\n\t"; for ($b = 0; $b < ($NUM_BOWLS / 2); $b++) { print "\t", $stones[$b]; } print "\nBowl number:"; for ($b = 0; $b < ($NUM_BOWLS / 2); $b++) { print "\t", $b+1; } print "\n\n"; } sub owner { my $b = shift; die if $b < 0 || $b >= $NUM_BOWLS; if ($b < $NUM_BOWLS / 2) { return 0; } else { return 1 } } sub must_pass { my ($player, @stones); ($player, undef , undef, undef, @stones) = @_; die if $player != 0 and $player != 1; die if @stones != $NUM_BOWLS; my $b; for ($b = 0; $b < $NUM_BOWLS; $b++) { if (owner($b) == $player and $stones[$b] != 0) { return 0; } } return 1; } sub get_move { my ($next, @score, @stones); ($next, $score[0], $score[1], @stones) = @_; die if $next != 0 and $next != 1; die if $score[0] < 0 or $score[0] >= $WINNING_SCORE; die if $score[1] < 0 or $score[1] >= $WINNING_SCORE; die if @stones != $NUM_BOWLS; if (must_pass($next, $next, @score, @stones)) { print "Player ", $next+1, " cannot move - enter p to pass\n"; my $tmp; do { chomp ($tmp = ); } until (lc $tmp eq 'p'); return $PASS; } else { my $m = -2; do { print "Player ", $next+1, ": which bowl? "; chomp ($m = ); if ($m =~ tr/0-9//c) { print "Input contains bad chars\n"; $m = -2; } else { -- $m; if ($m < 0 || $m >= $NUM_BOWLS) { print "No such bowl\n"; $m = -2; } elsif (owner($m) != $next) { print "Bowl not yours\n"; $m = -2; } elsif ($stones[$m] == 0) { print "Bowl contains no stones\n"; $m = -2; } } } while ($m == -2); return $m; } } sub winner { my ($next, @score, @stones); ($next, $score[0], $score[1], @stones) = @_; die if $next != 0 and $next != 1; die if $score[0] < 0; die if $score[1] < 0; die if @stones != $NUM_BOWLS; if ($score[0] >= $WINNING_SCORE) { return $PLAYER_ONE; } elsif ($score[1] >= $WINNING_SCORE) { return $PLAYER_TWO; } elsif (must_pass(0, $next, @score, @stones) and must_pass(1, $next, @score, @stones)) { return $DRAW; } else { return $NONE } } sub starting_position { return (0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4); } sub opponent { my $p = shift; die if $p != 0 and $p != 1; return $p ? 0 : 1; } sub force_to_p1 { my ($next, @score, @stones); ($next, $score[0], $score[1], @stones) = @_; die if $next != 1; die if $score[0] < 0; die if $score[1] < 0; die if @stones != $NUM_BOWLS; my (@newstones, $i); for ($i = 0; $i < $NUM_BOWLS / 2; $i++) { $newstones[$i + $NUM_BOWLS / 2] = $stones[$i]; } for ($i = $NUM_BOWLS / 2; $i < $NUM_BOWLS; $i++) { $newstones[$i - $NUM_BOWLS / 2] = $stones[$i]; } return (0, $score[1], $score[0], @newstones); } sub best_move { my ($next, @score, @stones); ($next, $score[0], $score[1], @stones) = @_; my $m; if ($next == 0) { if (must_pass(0, $next, @score, @stones)) { print "Must pass, returning \$PASS\n"; return $PASS; } if (rand() < .9) { my $state = join(' ', @_); if (not defined($db{$state})) { print "New position\n"; chomp ($m = `./owari $state 0`); print OUT_DB "$state: $m\n" or die "can't print '$state: $m' to filehandle OUT_DB: $!"; print "$state: $m\n"; $db{$state} = $m; die if $m eq 'pass'; return $m; } else { print "Seen before\n"; print "$state: $db{$state}\n"; return $db{$state}; } } else { print "Random!\n"; my $randbowl; do { $randbowl = int (rand $NUM_BOWLS / 2); } until ($stones[$randbowl]); return $randbowl; } } else { my @oldargs = @_; my $oldstate = join(' ', @oldargs); print "Player 2: $oldstate\n"; @_ = force_to_p1(@_); $m = best_move(@_); if ($m == $PASS) { return $PASS; } else { return ($m + $NUM_BOWLS / 2) % $NUM_BOWLS; } } } # Main program open(DB, 'db') or die; while () { chomp; my ($state, $move); unless (($state, $move) = /^(.+): (\d+)$/) { warn "bad line $_\n"; next; } $db{$state} = $move; # print "$state: $move\n"; } use IO::Handle; open(OUT_DB, '>>out_db') or die; OUT_DB->autoflush(1); if (@ARGV) { $type[0] = $type[1] = $AUTO; } else { my $i = 0; while ($i != 1 and $i != 2) { print "Enter 1 for computer goes first, 2 for second\n"; chomp ($i = ); } -- $i; $type[$i] = $SEMI; $type[opponent($i)] = $MANUAL; } my @game; for (;;) { @game = starting_position; draw(@game); while (winner(@game) == $NONE) { my $m; my $player = $game[0]; if ($type[$player] == $AUTO) { $m = best_move(@game); print "Recommended move for player ", $player+1, " - "; if ($m == $PASS) { print "pass\n"; } else { print "bowl ", $m+1, "\n"; } } elsif ($type[$game[0]] == $SEMI) { $m = best_move(@game); print "Recommended move for player ", $player+1, " - "; if ($m == $PASS) { print "pass\n"; } else { print "bowl ", $m+1, "\n"; } $m = get_move(@game); } else { $m = get_move(@game); } @game = move($m, @game); draw(@game); } if (winner(@game) == $PLAYER_ONE) { print "Player 1 has won\n"; } elsif (winner(@game) == $PLAYER_TWO) { print "Player 2 has won\n"; } elsif (winner(@game) == $DRAW) { print "A draw\n"; } else { die; } } exit(0);