Hanafuda
Return
#! /usr/bin/perl
use CGI;
$data_path = "/var/www/hanafuda_data/";
$timeout = 600; # seconds
# data file format:
# timestamp
# statename (reg, score, hand, draw, koi
# next player to act
# space-separated hand 1
# space-separated taken 1
# space-separated table
# space-separated hand 2
# space-separated taken 2
# cardfromhand,cardontable,cardfromdeck,cardontable
# score
sub sort_hand {
my ($hand) = @_;
my %seen = ();
my @cards = split(/,/,$hand);
foreach (@cards) {
$seen{$_} = 1;
}
$hand = "";
for ($i = 1;$i <= 48;$i++) {
if ($seen{$i} == 1) {
if ($hand ne "") {
$hand .= ",";
}
$hand .= $i;
}
}
return $hand;
}
sub deal_hand {
my %cards = ();
my $hand = "";
my $line = "";
for ($i = 1;$i <= 8;$i++) {
$j = int(rand() * 48) + 1;
while ($cards{$j} >= 1) {
$j = int(rand() * 48) + 1;
}
if ($i > 1) {
$line .= ",";
}
$line .= $j;
$cards{$j} = 1;
}
$hand .= sort_hand($line);
$j = 0;
for ($i = 0;$i <= 11;$i++) {
if ($cards{($i * 12) + 1} == 1 && $cards{($i * 12) + 2} == 1 &&
$cards{($i * 12) + 2} == 3 && $cards{($i * 12) + 4} == 1) {
# all 4 cards of 1 month in one place - redeal
return deal_hand();
} elsif (($cards{($i * 12) + 1} == 1 && $cards{($i * 12) + 2} == 1) ||
($cards{($i * 12) + 1} == 1 && $cards{($i * 12) + 3} == 1) ||
($cards{($i * 12) + 1} == 1 && $cards{($i * 12) + 4} == 1) ||
($cards{($i * 12) + 2} == 1 && $cards{($i * 12) + 3} == 1) ||
($cards{($i * 12) + 2} == 1 && $cards{($i * 12) + 4} == 1) ||
($cards{($i * 12) + 3} == 1 && $cards{($i * 12) + 4} == 1)) {
# 2 cards of 1 month in one place
$j++;
}
}
if ($j == 4) {
# 4 pairs of cards from 1 month in one place - redeal
return deal_hand();
}
$hand .= "\n\n";
$line = "";
for ($i = 1;$i <= 8;$i++) {
$j = int(rand() * 48) + 1;
while ($cards{$j} >= 1) {
$j = int(rand() * 48) + 1;
}
if ($i > 1) {
$line .= ",";
}
$line .= $j;
$cards{$j} = 2;
}
$hand .= sort_hand($line);
$j = 0;
for ($i = 0;$i <= 11;$i++) {
if ($cards{($i * 12) + 1} == 2 && $cards{($i * 12) + 2} == 2 &&
$cards{($i * 12) + 3} == 2 && $cards{($i * 12) + 4} == 2) {
# all 4 cards of 1 month in one place - redeal
return deal_hand();
} elsif (($cards{($i * 12) + 1} == 2 && $cards{($i * 12) + 2} == 2) ||
($cards{($i * 12) + 1} == 2 && $cards{($i * 12) + 3} == 2) ||
($cards{($i * 12) + 1} == 2 && $cards{($i * 12) + 4} == 2) ||
($cards{($i * 12) + 2} == 2 && $cards{($i * 12) + 3} == 2) ||
($cards{($i * 12) + 2} == 2 && $cards{($i * 12) + 4} == 2) ||
($cards{($i * 12) + 3} == 2 && $cards{($i * 12) + 4} == 2)) {
# 2 cards of 1 month in one place
$j++;
}
}
if ($j == 4) {
# 4 pairs of cards from 1 month in one place - redeal
return deal_hand();
}
$hand .= "\n";
$line = "";
for ($i = 1;$i <= 8;$i++) {
$j = int(rand() * 48) + 1;
while ($cards{$j} >= 1) {
$j = int(rand() * 48) + 1;
}
if ($i > 1) {
$line .= ",";
}
$line .= $j;
$cards{$j} = 3;
}
$hand .= sort_hand($line);
$j = 0;
for ($i = 0;$i <= 11;$i++) {
if ($cards{($i * 12) + 1} == 3 && $cards{($i * 12) + 2} == 3 &&
$cards{($i * 12) + 3} == 3 && $cards{($i * 12) + 4} == 3) {
# all 4 cards of 1 month in one place - redeal
return deal_hand();
} elsif (($cards{($i * 12) + 1} == 3 && $cards{($i * 12) + 2} == 3) ||
($cards{($i * 12) + 1} == 3 && $cards{($i * 12) + 3} == 3) ||
($cards{($i * 12) + 1} == 3 && $cards{($i * 12) + 4} == 3) ||
($cards{($i * 12) + 2} == 3 && $cards{($i * 12) + 3} == 3) ||
($cards{($i * 12) + 2} == 3 && $cards{($i * 12) + 4} == 3) ||
($cards{($i * 12) + 3} == 3 && $cards{($i * 12) + 4} == 3)) {
# 2 cards of 1 month in one place
$j++;
}
}
if ($j == 4) {
# 4 pairs of cards from 1 month in one place - redeal
return deal_hand();
}
$hand .= "\n\n";
return $hand;
}
sub score_hand {
my ($taken) = @_;
my @cards = split(/,/,$taken);
my %seen = ();
my $plains = 0;
my $animals = 0;
my $ribbons = 0;
my $brights = 0;
foreach (@cards) {
if (is_bright($_)) { $brights++; }
if (is_ribbon($_)) { $ribbons++; }
if (is_animal($_)) { $animals++; }
if (is_plain($_)) { $plains++; }
$seen{$_} = 1;
}
my $points = 0;
if ($plains >= 10) {
$points += $plains - 9;
}
if ($animals >= 5) {
$points += $animals - 4;
}
if ($ribbons >= 5) {
$points += $ribbons - 4;
}
if ($brights == 5) {
$points += 10;
} elsif ($brights == 4) {
if ($seen{41} == 1) {
$points += 7;
} else {
$points += 8;
}
} elsif ($brights == 3 && $seen{41} != 1) {
$points += 5;
}
if ($seen{2} == 1 && $seen{6} == 1 && $seen{10} == 1) {
# poetry ribbons
$points += 5;
}
if ($seen{22} == 1 && $seen{34} == 1 && $seen{38} == 1) {
# blue ribbons
$points += 5;
}
if ($seen{21} == 1 && $seen{25} == 1 && $seen{37} == 1) {
# boar, deer, butterfly
$points += 5;
}
if ($seen{29} == 1 && $seen{44} == 1) {
# full moon & lightning
$points += 5;
}
if ($seen{17} == 1 && $seen{29} == 1) {
# full moon & water lily
$points += 5;
}
if ($points >= 7) {
$points = $points * 2;
}
return $points;
}
sub is_plain {
my ($card) = @_;
if ($card == 3 || $card == 4 || $card == 7 || $card == 8 || $card == 11 ||
$card == 12 || $card == 15 || $card == 16 || $card == 19 || $card == 20 ||
$card == 23 || $card == 24 || $card == 27 || $card == 28 || $card == 31 ||
$card == 32 || $card == 33 || $card == 35 || $card == 36 || $card == 39 ||
$card == 40 || $card == 44 || $card == 46 || $card == 47 || $card == 48) {
return 1;
} else {
return 0;
}
}
sub is_animal {
my ($card) = @_;
if ($card == 5 || $card == 13 || $card == 17 || $card == 21 || $card == 25 ||
$card == 30 || $card == 33 || $card == 37 || $card == 42) {
return 1;
} else {
return 0;
}
}
sub is_ribbon {
my ($card) = @_;
if ($card == 2 || $card == 6 || $card == 10 || $card == 14 || $card == 18 ||
$card == 22 || $card == 26 || $card == 34 || $card == 38 || $card == 43) {
return 1;
} else {
return 0;
}
}
sub is_bright {
my ($card) = @_;
if ($card == 1 || $card == 9 || $card == 29 || $card == 41 || $card == 45) {
return 1;
} else {
return 0;
}
}
sub name_card {
my ($card) = @_;
$name = "";
if ($card == 0) {
return "table";
} elsif ($card <= 4) {
$name = "Jan ";
} elsif ($card <= 8) {
$name = "Feb ";
} elsif ($card <= 12) {
$name = "Mar ";
} elsif ($card <= 16) {
$name = "Apr ";
} elsif ($card <= 20) {
$name = "May ";
} elsif ($card <= 24) {
$name = "Jun ";
} elsif ($card <= 28) {
$name = "Jul ";
} elsif ($card <= 32) {
$name = "Aug ";
} elsif ($card <= 36) {
$name = "Sep ";
} elsif ($card <= 40) {
$name = "Oct ";
} elsif ($card <= 44) {
$name = "Nov ";
} else {
$name = "Dec ";
}
if (is_bright($card)) {
if ($card == 41) { $name .= "1/2"; }
$name .= "B";
}
if (is_ribbon($card)) {
$name .= "R";
if ($card == 2 || $card == 6 || $card == 10) {
$name .= "(P)";
} elsif ($card == 22 || $card == 34 || $card == 38) {
$name .= "(B)";
}
}
if (is_animal($card)) { $name .= "A"; }
if (is_plain($card)) { $name .= "P"; }
return $name;
}
sub summarize_hand {
my ($file,$slot,$player) = @_;
my ($timestamp,$state,$nextplayer,$handa,$takena,$table,$handb,$takenb,
$actions,$score) = split(/\n/,$file);
if ($state ne "score" && $state ne "hand" && $state ne "draw" &&
$state ne "koi") {
return "Invalid game\n";
}
$summary = "";
$show_hand_controls = 0;
$show_table_controls = 0;
if ($state eq "koi" && $player == $nextplayer) {
$score =~ /(\d+),(\d+),(\d),([-0-9]+)/;
$koia = $1;
$koib = $2;
$new_koi = 0;
if ($player == 1) {
$new_koi = score_hand($takena);
if ($koib > 0) {
$summary .= "Your taken cards are worth " . $new_koi . ". Opponent " .
"has Koi-Koied, which will double this score.<br>\n";
} else {
$summary .= "Your taken cards are worth " . $new_koi . ".<br>\n";
}
} else {
$new_koi = score_hand($takenb);
if ($koia > 0) {
$summary .= "Your taken cards are worth " . $new_koi . ". Opponent " .
"has Koi-Koied, which will double this score.<br>\n";
} else {
$summary .= "Your taken cards are worth " . $new_koi . ".<br>\n";
}
}
$summary .= "<form action=hanafuda.cgi method=post>" .
"<input type=hidden name=player value=" . $player . ">" .
"<input type=hidden name=slot value=" . $slot . ">" .
"<input type=hidden name=command value=koikoi>\n" .
"<input type=submit value='Koi-Koi (Continue)'></form>\n" .
"<form action=hanafuda.cgi method=post>" .
"<input type=hidden name=player value=" . $player . ">" .
"<input type=hidden name=slot value=" . $slot . ">" .
"<input type=hidden name=command value=bank>\n" .
"<input type=submit value='End Hand and Score'></form>\n";
} elsif ($state eq "hand" && $player == $nextplayer) {
$show_hand_controls = 1;
$show_table_controls = 1;
$summary .= "<form action=hanafuda.cgi method=post>" .
"<input type=hidden name=player value=" . $player . ">" .
"<input type=hidden name=slot value=" . $slot . ">" .
"<input type=hidden name=command value=playhand>\n";
} elsif ($state eq "draw" && $player == $nextplayer) {
$show_table_controls = 1;
$summary .= "<form action=hanafuda.cgi method=post>" .
"<input type=hidden name=player value=" . $player . ">" .
"<input type=hidden name=slot value=" . $slot . ">" .
"<input type=hidden name=command value=playdraw>\n";
}
if ($player == 2) {
# switch so that $handa and $takena have the current player's cards
$tmp = $handa;
$handa = $handb;
$handb = $tmp;
$tmp = $takena;
$takena = $takenb;
$takenb = $tmp;
}
$summary .= "Your Taken Cards<br>";
if ($takena eq "") {
$summary .= "None<br>\n";
} else {
$list = "";
$plains = "";
$plaincount = 0;
$animals = "";
$animalcount = 0;
$ribbons = "";
$ribboncount = 0;
$brights = "";
$brightcount = 0;
@cards = split(/,/,$takena);
foreach (@cards) {
$card = $_;
if ($list ne "") {
$list .= ", ";
}
$list .= name_card($card);
if (is_bright($card)) {
$brights .= "<img width=24 height=40 src=/hanafuda/" . $card . "t.jpg>";
$brightcount++;
}
if (is_animal($card)) {
$animals .= "<img width=24 height=40 src=/hanafuda/" . $card . "t.jpg>";
$animalcount++;
}
if (is_ribbon($card)) {
$ribbons .= "<img width=24 height=40 src=/hanafuda/" . $card . "t.jpg>";
$ribboncount++;
}
if (is_plain($card)) {
$plains .= "<img width=24 height=40 src=/hanafuda/" . $card . "t.jpg>";
$plaincount++;
}
}
$summary .= $list . "<br>" . $plains . " " . $plaincount . " Plains" .
$animals . " " . $animalcount . " Animals " .
$ribbons . " " . $ribboncount . " Ribbons " .
$brights . " " . $brightcount . " Brights<br>\n";
}
$summary .= "Your Hand<br>";
if ($handa eq "") {
$summary .= "None<br>\n";
} else {
$summary .= "<table border=0><tr>";
@cards = split(/,/,$handa);
foreach (@cards) {
$card = $_;
if ($show_hand_controls == 1) {
$summary .= "<td><img width=72 height=120 src=/hanafuda/" . $card .
".jpg onClick=\"document.getElementById('hand_" . $card .
"').click()\"><br><input type=radio id=\"hand_" . $card .
"\" name=handcard value=" . $card . ">";
} else {
$summary .= "<td><img width=72 height=120 src=/hanafuda/" . $card .
".jpg><br>";
}
$summary .= name_card($card) . "</td>";
}
$summary .= "</tr></table>\n";
}
$summary .= "On Table<br>";
if ($table eq "") {
$summary .= "None<br>\n";
if ($show_table_controls == 1) {
$summary .= "<input type=hidden name=tablecard value=0>";
}
} else {
$i = 0;
$summary .= "<table border=0><tr>";
@cards = split(/,/,$table);
foreach (@cards) {
$card = $_;
if ($state eq "draw" && $i == 0) {
$summary .= "<td>Drawn:<br><img width=72 height=120 src=/hanafuda/" .
$card . ".jpg><br>" . name_card($card) . "</td>";
if ($show_table_controls == 1) {
$summary .= "<input type=hidden name=drawcard value=" . $card . ">";
}
} else {
if ($show_table_controls == 1) {
$summary .= "<td><img width=72 height=120 src=/hanafuda/" . $card .
".jpg onClick=\"document.getElementById('table_" . $card .
"').click()\"><br><input type=radio id=\"table_" . $card .
"\" name=tablecard value=" . $card . ">";
} else {
$summary .= "<td><img width=72 height=120 src=/hanafuda/" . $card .
".jpg><br>";
}
$summary .= name_card($card) . "</td>";
}
$i++;
}
if ($show_table_controls == 1) {
$summary .= "<td><input type=radio name=tablecard value=0>None";
}
$summary .= "</tr></table>\n";
}
if ($show_table_controls == 1) {
$summary .= "<input type=submit value='Play'></form>\n";
}
$summary .= "Opponent's Hand<br>";
if ($handb eq "") {
$summary .= "None<br>\n";
} else {
$summary .= "<table border=0><tr>";
@cards = split(/,/,$handb);
foreach (@cards) {
$summary .= "<td><img width=72 height=120 src=/hanafuda/0-1.jpg></td>";
}
$summary .= "</tr></table>\n";
}
$summary .= "Opponent's Taken Cards<br>";
if ($takenb eq "") {
$summary .= "None<br>\n";
} else {
$list = "";
$plains = "";
$plaincount = 0;
$animals = "";
$animalcount = 0;
$ribbons = "";
$ribboncount = 0;
$brights = "";
$brightcount = 0;
@cards = split(/,/,$takenb);
foreach (@cards) {
$card = $_;
if ($list ne "") {
$list .= ", ";
}
$list .= name_card($card);
if (is_bright($card)) {
$brights .= "<img width=24 height=40 src=/hanafuda/" . $card . "t.jpg>";
$brightcount++;
}
if (is_animal($card)) {
$animals .= "<img width=24 height=40 src=/hanafuda/" . $card . "t.jpg>";
$animalcount++;
}
if (is_ribbon($card)) {
$ribbons .= "<img width=24 height=40 src=/hanafuda/" . $card . "t.jpg>";
$ribboncount++;
}
if (is_plain($card)) {
$plains .= "<img width=24 height=40 src=/hanafuda/" . $card . "t.jpg>";
$plaincount++;
}
}
$summary .= $list . "<br>" . $plains . " " . $plaincount . " Plains " .
$animals . " " . $animalcount . " Animals " .
$ribbons . " " . $ribboncount . " Ribbons " .
$brights . " " . $brightcount . " Brights<br>\n";
}
if ($actions =~ /(\d+),(\d+),(\d+),(\d+)/) {
$summary .= "<strong>Last actions: played " . name_card($1) . " from hand" .
" on " . name_card($2) . ", then drew " . name_card($3) . " and played" .
" on " . name_card($4) . "</strong><br>\n";
} elsif ($actions =~ /(\d+),(\d+)/) {
$summary .= "<strong>Last actions: played " . name_card($1) . " from " .
"hand on " . name_card($2) . "</strong><br>\n";
}
if ($score ne "") {
if ($score =~ /(\d+),(\d+),(\d),([-0-9]+)/) {
$koia = $1;
$koib = $2;
$lastkoi = $3;
$score = $4;
if ($lastkoi == 0) {
if (($player == 1 && $koia == 6) || ($player == 2 && $koib == 6)) {
$summary .= "Dealer's Privelege: you gets 6 points<br>\n";
} elsif (($player == 2 && $koia == 6) || ($player == 1 && $koib == 6)) {
$summary .= "Dealer's Privelege: opponent gets 6 points<br>\n";
} else {
$summary .= "Nobody has Koi-Koied yet<br>\n";
}
} elsif ($player == 1) {
if ($koia > 0) {
$summary .= "You last Koi-Koied at " . $koia . " points<br>\n";
}
if ($koib > 0) {
$summary .= "Opponent last Koi-Koied at " . $koib . " points<br>\n";
}
if ($lastkoi == 1) {
$summary .= "You were the last one to Koi-Koi<br>\n";
} elsif ($lastkoi == 2) {
$summary .= "Opponent was the last one to Koi-Koi<br>\n";
}
} else {
if ($koib > 0) {
$summary .= "You last Koi-Koied at " . $koib . " points<br>\n";
}
if ($koia > 0) {
$summary .= "Opponent last Koi-Koied at " . $koia . " points<br>\n";
}
if ($lastkoi == 2) {
$summary .= "You were the last one to Koi-Koi<br>\n";
} elsif ($lastkoi == 1) {
$summary .= "Opponent was the last one to Koi-Koi<br>\n";
}
}
} else {
$summary .= "<strong>End of hand</strong><br>\n";
}
if ($score == 0) {
$summary .= "Score: tied<br>\n";
} elsif ($player == 1) {
if ($score > 0) {
$summary .= "Score: " . $score . " in your favor<br>\n";
} else {
$summary .= "Score: " . (-1 * $score) . " in opponent's favor<br>\n";
}
} else {
if ($score < 0) {
$summary .= "Score: " . (-1 * $score) . " in your favor<br>\n";
} else {
$summary .= "Score: " . $score . " in opponent's favor<br>\n";
}
}
}
return $summary;
}
$result = "";
$refresh_timestamp = 0;
$query = new CGI;
$command = $query->param("command");
$slot = $query->param("slot");
$slot =~ s/\D//gs;
if ($slot < 1 || $slot > 10) {
$slot = 1;
}
$player = $query->param("player");
$player =~ s/\D//gs;
if ($command eq "getsource") {
$result .= "<a href=hanafuda.cgi>Return</a><hr><pre>\n";
open(RD,"<",$ENV{"SCRIPT_FILENAME"});
while (my $line = readline(RD)) {
$line =~ s/&/&/gs;
$line =~ s/</</gs;
$line =~ s/>/>/gs;
$result .= $line;
}
close(RD);
$result .= "</pre>\n";
} elsif ($command eq "p1reg") {
$proceed = 1;
if (-e $data_path . $slot) {
open(RD,$data_path . $slot);
$line = <RD>;
close(RD);
$line =~ s/\D//gs;
$time = time();
if ($time < $line + $timeout) {
$proceed = 0;
}
}
if ($proceed == 1) {
$refresh_timestamp = time();
open(WR,">" . $data_path . $slot);
print WR $refresh_timestamp . "\nreg\n";
$player = 1;
$result = "Registered! The other player should go to <a " .
"href=hanafuda.cgi?command=p2reg&slot=" . $slot . ">this URL</a>.<br>" .
"You can wait for the other player, or <a href=hanafuda.cgi?" .
"command=status&player=1&slot=" . $slot . ">click here</a> once the " .
"other player has joined.";
} else {
$result = "Sorry, that game is in progress. " .
"<a href=hanafuda.cgi>Return to title</a>";
}
} elsif ($command eq "p2reg") {
$state = "";
if (-e $data_path . $slot) {
open(RD,$data_path . $slot);
$line = <RD>;
$state = <RD>;
close(RD);
$line =~ s/\D//gs;
$state =~ s/\s//gs;
$time = time();
if ($time >= $line + $timeout) {
$state = "";
$cmd = "rm " . $data_path . $slot;
`$cmd`;
}
}
if ($state eq "") {
$result = "Sorry, that game has not yet been registered. " .
"<a href=hanafuda.cgi>Return to title</a>";
} elsif ($state ne "reg") {
$result = "Sorry, there is already a second player for that game. " .
"<a href=hanafuda.cgi>Return to title</a>";
} else {
$refresh_timestamp = time();
$file = $refresh_timestamp . "\nhand\n1\n" . deal_hand() . "\n0,0,0,0\n";
open(WR,">" . $data_path . $slot);
print WR $file;
close(WR);
$player = 2;
$result = summarize_hand($file,$slot,$player);
}
} elsif ($command eq "deal") {
$state = "";
$score = "";
if (-e $data_path . $slot) {
open(RD,$data_path . $slot);
$timeline = <RD>;
$state = <RD>;
$line = <RD>;
$line = <RD>;
$line = <RD>;
$line = <RD>;
$line = <RD>;
$line = <RD>;
$line = <RD>;
$score = <RD>;
close(RD);
$timeline =~ s/\D//gs;
$state =~ s/\s//gs;
$time = time();
if ($time >= $timeline + $timeout) {
$state = "";
$cmd = "rm " . $data_path . $slot;
`$cmd`;
}
}
if ($state eq "") {
$result = "Sorry, that game has not yet been registered. " .
"<a href=hanafuda.cgi>Return to title</a>";
} elsif ($state ne "score") {
$result = "Invalid command. <a href=hanafuda.cgi>Return to title</a>";
} else {
$refresh_timestamp = time();
if ($score =~ /(\d+),(\d+),(\d+),([-0-9]+)/) {
$score = $4;
}
$file = $refresh_timestamp . "\nhand\n" . $player . "\n" . deal_hand() .
"\n0,0,0," . $score . "\n";
open(WR,">" . $data_path . $slot);
print WR $file;
close(WR);
$result = summarize_hand($file,$slot,$player);
}
} elsif ($command eq "playhand") {
$timeline = "";
$state = "";
$current_player = "";
$file = "";
if (-e $data_path . $slot) {
$i = 0;
open(RD,$data_path . $slot);
while ($line = <RD>) {
$i++;
$file .= $line;
if ($i == 1) {
$timeline = $line;
} elsif ($i == 2) {
$state = $line;
} elsif ($i == 3) {
$current_player = $line;
}
}
close(RD);
$timeline =~ s/\D//gs;
$state =~ s/\s//gs;
$time = time();
if ($time >= $timeline + $timeout) {
$state = "";
$cmd = "rm " . $data_path . $slot;
`$cmd`;
}
}
$handcard = $query->param("handcard");
$handcard =~ s/\D//gs;
$tablecard = $query->param("tablecard");
$tablecard =~ s/\D//gs;
if ($state ne "hand") {
$result = "Invalid command. <a href=hanafuda.cgi>Return to title</a>";
} elsif ($player != $current_player) {
$result = "It is not your turn. <a href=hanafuda.cgi>Return to title</a>";
} elsif ($handcard eq "") {
$result = "<strong>You must select a card from your hand.</strong><br>" .
summarize_hand($file,$slot,$player);
} elsif ($tablecard eq "") {
$result = "<strong>You must select a card from the table.</strong><br>" .
summarize_hand($file,$slot,$player);
} elsif (int(($handcard - 1)/4) != int(($tablecard - 1)/4) &&
$tablecard != 0) {
$result = "<strong>Invalid play. Either the cards' months must match, " .
"or you must discard to the table.</strong><br>" .
summarize_hand($file,$slot,$player);
} else {
$refresh_timestamp = time();
my ($timestamp,$state,$nextplayer,$handa,$takena,$table,$handb,$takenb,
$actions,$score) = split(/\n/,$file);
if ($player == 1) {
if ($handa =~ /,$handcard,/) {
$handa =~ s/,$handcard,/,/;
} elsif ($handa =~ /^$handcard,/) {
$handa =~ s/^$handcard,//;
} elsif ($handa =~ /,$handcard$/) {
$handa =~ s/,$handcard$//;
} else {
$handa = "";
}
} else {
if ($handb =~ /,$handcard,/) {
$handb =~ s/,$handcard,/,/;
} elsif ($handb =~ /^$handcard,/) {
$handb =~ s/^$handcard,//;
} elsif ($handb =~ /,$handcard$/) {
$handb =~ s/,$handcard$//;
} else {
$handb = "";
}
}
if ($tablecard == 0) {
if ($table == "") {
$table = $handcard;
} else {
$table .= "," . $handcard;
}
} elsif ($player == 1) {
if ($takena ne "") {
$takena .= ",";
}
$takena .= $handcard . "," . $tablecard;
} else {
if ($takenb ne "") {
$takenb .= ",";
}
$takenb .= $handcard . "," . $tablecard;
}
if ($tablecard != 0) {
# table has at least 2 cards at this point
if ($table =~ /,$tablecard,/) {
$table =~ s/,$tablecard,/,/;
} elsif ($table =~ /^$tablecard,/) {
$table =~ s/^$tablecard,//;
} elsif ($table =~ /,$tablecard$/) {
$table =~ s/,$tablecard$//;
} else {
$table = "";
}
} else {
$table = sort_hand($table);
}
my %seen_cards = ();
if ($takena ne "") {
my @cards = split(/,/,$takena);
foreach (@cards) {
$seen_cards{$_} = 1;
}
}
if ($handa ne "") {
my @cards = split(/,/,$handa);
foreach (@cards) {
$seen_cards{$_} = 1;
}
}
if ($takenb ne "") {
my @cards = split(/,/,$takenb);
foreach (@cards) {
$seen_cards{$_} = 1;
}
}
if ($handb ne "") {
my @cards = split(/,/,$handb);
foreach (@cards) {
$seen_cards{$_} = 1;
}
}
if ($table ne "") {
my @cards = split(/,/,$table);
foreach (@cards) {
$seen_cards{$_} = 1;
}
}
$new_card = int(rand() * 48) + 1;
while ($seen_cards{$new_card} == 1) {
$new_card = int(rand() * 48) + 1;
}
if ($table ne "") {
$table = $new_card . "," . $table;
} else {
$table = $new_card;
}
$actions = $handcard . "," . $tablecard;
$file = $refresh_timestamp . "\ndraw\n" . $player . "\n" . $handa . "\n" .
$takena . "\n" . $table . "\n" . $handb . "\n" . $takenb . "\n" .
$actions . "\n" . $score . "\n";
open(WR,">" . $data_path . $slot);
print WR $file;
close(WR);
$result = summarize_hand($file,$slot,$player);
}
} elsif ($command eq "playdraw") {
$timeline = "";
$state = "";
$current_player = "";
$file = "";
if (-e $data_path . $slot) {
$i = 0;
open(RD,$data_path . $slot);
while ($line = <RD>) {
$i++;
$file .= $line;
if ($i == 1) {
$timeline = $line;
} elsif ($i == 2) {
$state = $line;
} elsif ($i == 3) {
$current_player = $line;
}
}
close(RD);
$timeline =~ s/\D//gs;
$state =~ s/\s//gs;
$time = time();
if ($time >= $timeline + $timeout) {
$state = "";
$cmd = "rm " . $data_path . $slot;
`$cmd`;
}
}
$drawcard = $query->param("drawcard");
$drawcard =~ s/\D//gs;
$tablecard = $query->param("tablecard");
$tablecard =~ s/\D//gs;
if ($state ne "draw") {
$result = "Invalid command. <a href=hanafuda.cgi>Return to title</a>";
} elsif ($player != $current_player) {
$result = "It is not your turn. <a href=hanafuda.cgi>Return to title</a>";
} elsif ($drawcard eq "" || $tablecard eq "") {
$result = "<strong>You must select a card from the table.</strong><br>" .
summarize_hand($file,$slot,$player);
} elsif (int(($drawcard - 1)/4) != int(($tablecard - 1)/4) &&
$tablecard != 0) {
$result = "<strong>Invalid play. Either the cards' months must match, " .
"or you must discard to the table.</strong><br>" .
summarize_hand($file,$slot,$player);
} else {
$refresh_timestamp = time();
my ($timestamp,$state,$nextplayer,$handa,$takena,$table,$handb,$takenb,
$actions,$score) = split(/\n/,$file);
# table can not be empty at this point
$table =~ s/^$drawcard,//;
if ($tablecard == 0) {
$table .= "," . $drawcard;
} elsif ($player == 1) {
if ($takena ne "") {
$takena .= ",";
}
$takena .= $drawcard . "," . $tablecard;
} else {
if ($takenb ne "") {
$takenb .= ",";
}
$takenb .= $drawcard . "," . $tablecard;
}
if ($tablecard != 0) {
if ($table eq $tablecard) {
$table = "";
} elsif ($table =~ /,$tablecard,/) {
$table =~ s/,$tablecard,/,/;
} elsif ($table =~ /^$tablecard,/) {
$table =~ s/^$tablecard,//;
} elsif ($table =~ /,$tablecard$/) {
$table =~ s/,$tablecard$//;
} else {
$table = "";
}
} else {
$table = sort_hand($table);
}
$actions .= "," . $drawcard . "," . $tablecard;
$new_koi = 0;
$koia = 0;
$koib = 0;
$lastkoi = 0;
$game_score = 0;
if ($score =~ /(\d+),(\d+),(\d),([-0-9]+)/) {
$koia = $1;
$koib = $2;
$lastkoi = $3;
$game_score = $4;
}
$end_of_hand_this_player_advances = 0;
if ($player == 1) {
$new_koi = score_hand($takena);
if ($new_koi > $koia) {
$file = $refresh_timestamp . "\nkoi\n1\n" . $handa . "\n" .
$takena . "\n" . $table . "\n" . $handb . "\n" . $takenb . "\n" .
$actions . "\n" . $score . "\n";
open(WR,">" . $data_path . $slot);
print WR $file;
close(WR);
} elsif ($handb ne "") {
$file = $refresh_timestamp . "\nhand\n2\n" . $handa . "\n" .
$takena . "\n" . $table . "\n" . $handb . "\n" . $takenb . "\n" .
$actions . "\n" . $score . "\n";
open(WR,">" . $data_path . $slot);
print WR $file;
close(WR);
} elsif ($lastkoi == 1) {
$end_of_hand_this_player_advances = 1;
if ($koib > 0) {
$koia = $koia * 2;
}
$score = $game_score + $koia;
$file = $refresh_timestamp . "\nscore\n1\n" . $handa . "\n" .
$takena . "\n" . $table . "\n" . $handb . "\n" . $takenb . "\n" .
$actions . "\n" . $score . "\n";
open(WR,">" . $data_path . $slot);
print WR $file;
close(WR);
} elsif ($lastkoi == 2) {
if ($koia > 0) {
$koib = $koib * 2;
}
$score = $game_score - $koib;
$file = $refresh_timestamp . "\nscore\n2\n" . $handa . "\n" .
$takena . "\n" . $table . "\n" . $handb . "\n" . $takenb . "\n" .
$actions . "\n" . $score . "\n";
open(WR,">" . $data_path . $slot);
print WR $file;
close(WR);
} else {
$score = "0,6,0," . ($game_score - 6);
$file = $refresh_timestamp . "\nscore\n2\n" . $handa . "\n" .
$takena . "\n" . $table . "\n" . $handb . "\n" . $takenb . "\n" .
$actions . "\n" . $score . "\n";
open(WR,">" . $data_path . $slot);
print WR $file;
close(WR);
}
} else {
$new_koi = score_hand($takenb);
if ($new_koi > $koib) {
$file = $refresh_timestamp . "\nkoi\n2\n" . $handa . "\n" .
$takena . "\n" . $table . "\n" . $handb . "\n" . $takenb . "\n" .
$actions . "\n" . $score . "\n";
open(WR,">" . $data_path . $slot);
print WR $file;
close(WR);
} elsif ($handa ne "") {
$file = $refresh_timestamp . "\nhand\n1\n" . $handa . "\n" .
$takena . "\n" . $table . "\n" . $handb . "\n" . $takenb . "\n" .
$actions . "\n" . $score . "\n";
open(WR,">" . $data_path . $slot);
print WR $file;
close(WR);
} elsif ($lastkoi == 1) {
if ($koib > 0) {
$koia = $koia * 2;
}
$score = $game_score + $koia;
$file = $refresh_timestamp . "\nscore\n1\n" . $handa . "\n" .
$takena . "\n" . $table . "\n" . $handb . "\n" . $takenb . "\n" .
$actions . "\n" . $score . "\n";
open(WR,">" . $data_path . $slot);
print WR $file;
close(WR);
} elsif ($lastkoi == 2) {
$end_of_hand_this_player_advances = 1;
if ($koia > 0) {
$koib = $koib * 2;
}
$score = $game_score - $koib;
$file = $refresh_timestamp . "\nscore\n2\n" . $handa . "\n" .
$takena . "\n" . $table . "\n" . $handb . "\n" . $takenb . "\n" .
$actions . "\n" . $score . "\n";
open(WR,">" . $data_path . $slot);
print WR $file;
close(WR);
} else {
$score = "6,0,0," . ($game_score + 6);
$file = $refresh_timestamp . "\nscore\n1\n" . $handa . "\n" .
$takena . "\n" . $table . "\n" . $handb . "\n" . $takenb . "\n" .
$actions . "\n" . $score . "\n";
open(WR,">" . $data_path . $slot);
print WR $file;
close(WR);
}
}
if ($end_of_hand_this_player_advances == 1) {
$result = "<form action=hanafuda.cgi method=post>" .
"<input type=hidden name=player value=" . $player . ">" .
"<input type=hidden name=slot value=" . $slot . ">" .
"<input type=hidden name=command value=deal>" .
"<input type=submit value='Next Round'></form>\n" .
summarize_hand($file,$slot,$player);
} else {
$result = summarize_hand($file,$slot,$player);
}
}
} elsif ($command eq "koikoi") {
$timeline = "";
$state = "";
$current_player = "";
$file = "";
if (-e $data_path . $slot) {
$i = 0;
open(RD,$data_path . $slot);
while ($line = <RD>) {
$i++;
$file .= $line;
if ($i == 1) {
$timeline = $line;
} elsif ($i == 2) {
$state = $line;
} elsif ($i == 3) {
$current_player = $line;
}
}
close(RD);
$timeline =~ s/\D//gs;
$state =~ s/\s//gs;
$time = time();
if ($time >= $timeline + $timeout) {
$state = "";
$cmd = "rm " . $data_path . $slot;
`$cmd`;
}
}
if ($state ne "koi") {
$result = "Invalid command. <a href=hanafuda.cgi>Return to title</a>";
} elsif ($player != $current_player) {
$result = "It is not your turn. <a href=hanafuda.cgi>Return to title</a>";
} else {
$refresh_timestamp = time();
my ($timestamp,$state,$nextplayer,$handa,$takena,$table,$handb,$takenb,
$actions,$score) = split(/\n/,$file);
$new_koi = 0;
$koia = 0;
$koib = 0;
$game_score = 0;
if ($score =~ /(\d+),(\d+),(\d),([-0-9]+)/) {
$koia = $1;
$koib = $2;
$game_score = $4;
}
if ($player == 1) {
$new_koi = score_hand($takena);
$score = $new_koi . "," . $koib . ",1," . $game_score;
$file = $refresh_timestamp . "\nhand\n2\n" . $handa . "\n" .
$takena . "\n" . $table . "\n" . $handb . "\n" . $takenb . "\n" .
$actions . "\n" . $score . "\n";
open(WR,">" . $data_path . $slot);
print WR $file;
close(WR);
} else {
$new_koi = score_hand($takenb);
$score = $koia . "," . $new_koi . ",2," . $game_score;
$file = $refresh_timestamp . "\nhand\n1\n" . $handa . "\n" .
$takena . "\n" . $table . "\n" . $handb . "\n" . $takenb . "\n" .
$actions . "\n" . $score . "\n";
open(WR,">" . $data_path . $slot);
print WR $file;
close(WR);
}
$result = summarize_hand($file,$slot,$player);
}
} elsif ($command eq "bank") {
$timeline = "";
$state = "";
$current_player = "";
$file = "";
if (-e $data_path . $slot) {
$i = 0;
open(RD,$data_path . $slot);
while ($line = <RD>) {
$i++;
$file .= $line;
if ($i == 1) {
$timeline = $line;
} elsif ($i == 2) {
$state = $line;
} elsif ($i == 3) {
$current_player = $line;
}
}
close(RD);
$timeline =~ s/\D//gs;
$state =~ s/\s//gs;
$time = time();
if ($time >= $timeline + $timeout) {
$state = "";
$cmd = "rm " . $data_path . $slot;
`$cmd`;
}
}
if ($state ne "koi") {
$result = "Invalid command. <a href=hanafuda.cgi>Return to title</a>";
} elsif ($player != $current_player) {
$result = "It is not your turn. <a href=hanafuda.cgi>Return to title</a>";
} else {
$refresh_timestamp = time();
my ($timestamp,$state,$nextplayer,$handa,$takena,$table,$handb,$takenb,
$actions,$score) = split(/\n/,$file);
$new_koi = 0;
$koia = 0;
$koib = 0;
$game_score = 0;
if ($score =~ /(\d+),(\d+),(\d),([-0-9]+)/) {
$koia = $1;
$koib = $2;
$game_score = $4;
}
if ($player == 1) {
$koia = score_hand($takena);
if ($koib > 0) {
$koia = $koia * 2;
}
$score = $game_score + $koia;
$file = $refresh_timestamp . "\nscore\n1\n" . $handa . "\n" .
$takena . "\n" . $table . "\n" . $handb . "\n" . $takenb . "\n" .
$actions . "\n" . $score . "\n";
open(WR,">" . $data_path . $slot);
print WR $file;
close(WR);
} else {
$koib = score_hand($takenb);
if ($koia > 0) {
$koib = $koib * 2;
}
$score = $game_score - $koib;
$file = $refresh_timestamp . "\nscore\n2\n" . $handa . "\n" .
$takena . "\n" . $table . "\n" . $handb . "\n" . $takenb . "\n" .
$actions . "\n" . $score . "\n";
open(WR,">" . $data_path . $slot);
print WR $file;
close(WR);
}
$result = "<form action=hanafuda.cgi method=post>" .
"<input type=hidden name=player value=" . $player . ">" .
"<input type=hidden name=slot value=" . $slot . ">" .
"<input type=hidden name=command value=deal>" .
"<input type=submit value='Next Round'></form>\n" .
summarize_hand($file,$slot,$player);
}
} elsif ($command eq "status") {
$timeline = "";
$state = "";
$file = "";
$current_player = "";
if (-e $data_path . $slot) {
$i = 0;
open(RD,$data_path . $slot);
while ($line = <RD>) {
$i++;
$file .= $line;
if ($i == 1) {
$timeline = $line;
} elsif ($i == 2) {
$state = $line;
} elsif ($i == 3) {
$current_player = $line;
}
}
close(RD);
$timeline =~ s/\D//gs;
$state =~ s/\s//gs;
$time = time();
if ($time >= $timeline + $timeout) {
$state = "";
$cmd = "rm " . $data_path . $slot;
`$cmd`;
}
}
if ($state eq "") {
$result = "Sorry, the game has expired. " .
"<a href=hanafuda.cgi>Return to title</a>";
} elsif ($state eq "reg") {
$refresh_timestamp = $timeline;
$result = "Waiting for player 2...";
} else {
$refresh_timestamp = time();
if ($state eq "score" && $player == $current_player) {
$result = "<form action=hanafuda.cgi method=post>" .
"<input type=hidden name=player value=" . $player . ">" .
"<input type=hidden name=slot value=" . $slot . ">" .
"<input type=hidden name=command value=deal>" .
"<input type=submit value='Next Round'></form>\n" .
summarize_hand($file,$slot,$player);
} else {
$result = summarize_hand($file,$slot,$player);
}
}
} else {
$result = "Current table status:<ol>\n";
for ($slot = 1;$slot <= 8;$slot++) {
$timeline = "";
$state = "";
if (-e $data_path . $slot) {
$i = 0;
open(RD,$data_path . $slot);
while ($line = <RD>) {
$i++;
$file .= $line;
if ($i == 1) {
$timeline = $line;
} elsif ($i == 2) {
$state = $line;
}
}
close(RD);
$timeline =~ s/\D//gs;
$state =~ s/\s//gs;
$time = time();
if ($time >= $timeline + $timeout) {
$state = "";
$cmd = "rm " . $data_path . $slot;
`$cmd`;
}
}
if ($state eq "") {
$result .= "<li>Open - <a href=hanafuda.cgi?command=p1reg&slot=" . $slot .
">Start playing here</a></li>\n";
} elsif ($state eq "reg") {
$result .= "<li>Waiting for player 2 - <a href=hanafuda.cgi?" .
"command=p2reg&slot=" . $slot . ">Start playing here</a></li>\n";
} else {
$result .= "<li>Busy</li>\n";
}
}
$result .= "</ol>\n<a href=hanafuda.cgi?command=getsource>View Game Source" .
"</a><hr>\nCopyright 2011 Winged Cat Solutions.<br>\nReport any bugs " .
"to WingedCat on IRC: EsperNet #billy_vs_snakeman";
}
print "Content-Type: text/html\r\n\r\n" .
"<html><head><title>Hanafuda</title>\n";
if ($refresh_timestamp > 0 && $result !~ /<form /) {
print "<meta http-equiv=\"refresh\" content=\"10; url=hanafuda.cgi?" .
"command=status&player=" . $player . "&slot=" . $slot . "\">\n";
}
print "</head><body><h2>Hanafuda</h2>\n" . $result . "</body></html>\n";
# Card list:
# 1B
# 1RP
# 1P
# 1P
# 2A
# 2RP
# 2P
# 2P
# 3B
# 3RP
# 3P
# 3P
# 4A
# 4R
# 4P
# 4P
# 5A
# 5R
# 5P
# 5P
# 6A
# 6RB
# 6P
# 6P
# 7A
# 7R
# 7P
# 7R
# 8B
# 8A
# 8P
# 8P
# 9AP
# 9RB
# 9P
# 9P
# 10A
# 10RB
# 10P
# 10P
# 11B.5
# 11A
# 11R
# 11P
# 12B
# 12P
# 12P
# 12P
#
# Score and rules:
# Plains: 10=1 point, +1 per Plain
# Ribbons: 5=1 point, +1 per Ribbon
# Animals: 5=1 point, +1 per Animal
# Brights (not including 41/11B): 3=5 points, 4=8, 3=11B=7, 4+11B=10
# All Blue Ribbons: 5 points
# All Poetry Ribbons: 5 points
# Boar, Deer, Butterfly (21, 25, 37): 5 points
# Full Moon and Lightning (29 + 44): 5 points
# Full Moon and Water Lily (17 + 29): 5 points
# 7+ points: x2
# opponent Koi-Koi: x2
# nothing: 6 to dealer
# redeal if 2 cards each of 4 months or all 4 cards of 1 month on table or hand