< Back to Code Samples
<?php
/*
rps.php
Written by Aaron Hill (C)2007
This file handles all the processing and fields AJAX calls sent in by the document.
Session variables are used to enforce a stochastic environment.
*/
// First start the session
session_name("RPS");
session_start();
if (!isset($_SESSION['history'])) { initRPS(); }
// What needs to be done, if anything? This sanitizes the input and prevents
// "Undefined Index" variables, which are a pain.
$_ACTION = isset($_GET['action']) ? $_GET['action'] : "none";
switch($_ACTION) { // For AJAX //
case "init": // Intialize the game - this is only done on initial page load
initRPS();
startMatch();
break;
case "reset": // Start a new game this is done at the beginning of every match
startMatch();
break;
case "throw": // A single round. Again, the GET info is sanitized to prevent undefineds
$player = isset($_GET['throw']) ? $_GET['throw'] : NULL;
if ($player) throwRPS($player);
break;
case "score": // Get the score data for the round. This is a little complex here.
// This doesn't NEED to be done, but it makes the code look cleaner.
$totalRounds = $_SESSION['totalRounds'];
// Who was the most recent winner?
$winner = $_SESSION['history']['Winner'][$totalRounds];
// If it wasn't a tie, prep the response text.
if ($winner != "Tie") {
$points = $_SESSION['score'][$winner];
$scoreInfo = $winner . "&" . $points;
}
// Tie :(
else {
$scoreInfo = "Tie&0";
}
// Send the response text back.
echo $scoreInfo;
break;
case "stats":
// Get the Stats information - this is called after every round.
$stats = getStats(); // retrieves an array of data from below.
// Start a table. Yes, I know, HTML in PHP is ugly.
echo "<table id=\"statistics\" cellspacing=0>
<tr><th width=80>Name</th><th width=80>Player</th><td width=160> </td><th width=80>AI</th></tr>";
// This iterative is just to get the keys out of the array so that the methods
// can be abstracted.
foreach ($stats["Player"]["Raw"] as $rowName => $raw) {
// Used for calculating composition (this is the denominator)
$totalStat = max(($stats['Player']['Raw'][$rowName] + $stats['AI']['Raw'][$rowName]),1);
// Used for calculating composition (this is the numerator)
$playerStat = min(100 * ($stats['Player']['Raw'][$rowName] / $totalStat), 100);
$aiStat = min(100 * ($stats['AI']['Raw'][$rowName] / $totalStat), 100);
// List the stats for player and AI in order.
echo "<tr><td>$rowName</td>
<td>" . $stats['Player']['Raw'][$rowName] . " (" . round($playerStat,2) . "%)</td>";
// This bar will show the proportional distribution of stats
// for AI vs. Player specifically
// This is what makes the cool red/blue bar thing. Just a graphical
// way of showing the percentages.
echo "<td width=\"100\">
<span style=\"width:" . $playerStat . "%\" id=\"barPlayer\"> </span>
<span style=\"width:" . $aiStat . "%\" id=\"barAI\"> </span>
</td>
<td>" . $stats['AI']['Raw'][$rowName] . " (" . round($aiStat,2) . "%)</td>";
}
echo "</table>";
break;
default:
// Cover everything else. Prevents some errors and probing.
break;
}
function initRPS()
{
// game history - may be used for heuristics
$_SESSION['history'] = array("Player"=>array(), "AI"=>array(), "Winner"=>array());
$_SESSION['totalRounds'] = 0; // total rounds. used for assigning unique ID to history
$_SESSION['round'] = 0; // rounds for this game
$_SESSION['matchCount'] = 0; // number of matches played
$_SESSION['matchWins'] = 0; // number of matches won by player
$_SESSION['matchLosses'] = 0; // number of matches won by AI
}
function startMatch()
{
// reset the score session variable
$_SESSION['score'] = array("Player" => 0, "AI" => 0);
// increment the match counter
$_SESSION['matchCount']++;
// Send back a response to be stuck into the message box.
echo "Match " . $_SESSION['matchCount'] . ": Fire when ready!";
}
function throwRPS($player)
{
// Pre-increment the number of rounds. the # of rounds is used as a key for the
// arrays. Total rounds is ALL rounds, round is just for this match.
$totalRounds = ++$_SESSION['totalRounds'];
$round = ++$_SESSION['round'];
// assigned to a function so we can add artificial intelligence later. My co-worker and
// I wrote a really sweat AI that uses a Markov Chain to guess what you're likely to
// pick. If you have ANY pattern at all, it will guess it. As the game goes on, it gets
// progressively better. I didn't include it though since I didn't write it myself.
// So we're just using regular old random.
$ai = get_ai_pick();
// Record this round in the history session variable.
$_SESSION['history']['Player'][$totalRounds] = $player;
$_SESSION['history']['AI'][$totalRounds] = $ai;
// Record the score, check for victory, etc.
score_round($round, $totalRounds, $ai, $player);
// Sends back a response text encoding the picks by both player and AI.
echo $player . "_" . $ai;
}
function score_round($round, $totalRounds, $ai, $player)
{
/* ************************************************* *
* victory matrix - a much easier way to determine winners.
* Since every pair has a pre-defined victory outcome, it made
* sense to use a matrix for it.
*
* Rock[] Paper[] Scissors[] <-- Player's choice
* V V V
* Rock Paper Scissors <-- Player ties with this row ("Tie")
* Scissors Rock Paper <-- Player wins with this row ("Player")
* Paper Scissors Rock <-- Player loses in this row ("AI")
*************************************************** */
$victory_matrix = array(
"rock" => array("rock"=>"Tie", "scissors"=>"Player", "paper"=>"AI"),
"paper" => array("paper"=>"Tie", "rock"=>"Player", "scissors"=>"AI"),
"scissors" => array("scissors"=>"Tie", "paper"=>"Player", "rock"=>"AI")
);
// First, determine the winner.
$_SESSION['history']['Winner'][$totalRounds] = $victory_matrix[$player][$ai];
// If it's not a tie, then increment the appropriate person's score.
if ($victory_matrix[$player][$ai] != "Tie")
$_SESSION['score'][$victory_matrix[$player][$ai]]++;
// If the player or AI gets 3 points, give them a matchwin.
if ($_SESSION['score']['Player'] == "3") $_SESSION['matchWins']++;
else if ($_SESSION['score']['AI'] == "3") $_SESSION['matchLosses']++;
}
function getStats(){
// This function returns the stats array. It's just various statistical
// data about the game history. It should be pretty self-explanatory.
$stats = array(
"Player" => array(
"Raw"=>array(
"Rock"=>0,
"Paper"=>0,
"Scissors"=>0,
"Round Wins"=>0,
"Ties"=>0,
"Match Wins"=>$_SESSION['matchWins']),
"Percent"=>array()
),
"AI" => array(
"Raw"=>array(
"Rock"=>0,
"Paper"=>0,
"Scissors"=>0,
"Round Wins"=>0,
"Ties"=>0,
"Match Wins"=>$_SESSION['matchLosses']),
"Percent"=>array()
)
);
// prevents Div/0 errors
$totalRounds = max($_SESSION['totalRounds'], 1);
// I am indeed aware that if a game went on for a VERY LONG TIME this
// method isn't the most efficient. However the player would have to play
// a couple hundred games before it would even become noticeable.
foreach ($_SESSION['history']['Player'] as $round => $choice)
{
$stats["Player"]["Raw"][ucwords($choice)]++;
$stats["AI"]["Raw"][ucwords($_SESSION['history']['AI'][$round])]++;
if ($_SESSION['history']['Winner'][$round] == "Player")
$stats["Player"]["Raw"]["Round Wins"]++;
else if ($_SESSION['history']['Winner'][$round] == "Tie") {
$stats["Player"]["Raw"]["Ties"]++;
$stats["AI"]["Raw"]["Ties"]++;
}
else
$stats["AI"]["Raw"]["Round Wins"]++;
}
// Establish proportions //
// This foreach loop is really just to extract the property iteratively. The actual
// value, $v, isn't used at all -- but if I didn't separate it into $key => $value,
// it would just give me the value and not the key.
foreach ($stats["Player"]["Raw"] as $property => $v)
{
// Match Wins are calculated using Match Count, not Total Rounds
if ($property == "Match Wins") { continue; }
$stats["Player"]["Percent"][$property] = $stats["Player"]["Raw"][$property] / $totalRounds;
$stats["AI"]["Percent"][$property] = $stats["AI"]["Raw"][$property] / $totalRounds;
}
$stats["Player"]["Percent"]["Match Wins"] = $stats["Player"]["Raw"]["Match Wins"] / max($_SESSION['matchCount'],1);
$stats["AI"]["Percent"]["Match Wins"] = $stats["AI"]["Raw"]["Match Wins"] / max($_SESSION['matchCount'],1);
return $stats;
}
function get_ai_pick(){
// If there are AI heuristics, they'll go here.
// Possible ideas: pattern checking, statistic checking, etc.
// The idea is to make it smarter over time.
$RPS = array("rock", "paper", "scissors");
return $RPS[rand(0,2)];
}
?>