< 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 (
$playerthrowRPS($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>&nbsp;</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\">&nbsp;</span>
                    <span style=\"width:" 
$aiStat "%\" id=\"barAI\">&nbsp;</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)];
}
?>