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/&/&amp;/gs;
    $line =~ s/</&lt;/gs;
    $line =~ s/>/&gt;/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