Rock-Paper-Scissors

This is a simple application that uses VoiceXML and Javascript to implement the classic game "Rock-Paper-Scissors." It includes audio taunts recorded by a very annoying kid. Try out this app and see how well you do!

Try It!
  1. Set your Application URL to http://studio.247-inc.net/library2/code/ex-111/index.vxml
  2. Call the number shown in VXML Tools page
  3. Sign in, and play!

The application consists of a single form and two script blocks. The first script block references an external file, rps.js, that contains a JavaScript class, CRPS, that encapsulates the rules for playing the game. The second script block defines a function, pickRandNum, that takes a maximum value as a parameter and returns an integer from 0 to that value.

Several variables are defined at document scope. oGame stores a reference to an instance of the CRPS class. wavedir stores the base URI to the recorded audio. iWins, iLosses, and iDraws keep track of the number of wins, losses, and draws while the application executes. aPrefixes is a JavaScript array used to construct the filename of some of the recorded audio files.

The dialog declares a field item variable, weapon that stores the user's choice of weapon. A property indicates that the VoiceXML interpreter should wait three seconds before firing a noinput event. If a noinput event occurs, the user-defined noinput handler calls the pickRandNum function to construct the path to one of three recorded audio files applicable to a noinput scenario.

The grammar defines the weapons the user is allowed to choose from. If the user utters something outside the grammar, the VoiceXML interpreter fires a nomatch event, and the user-defined nomatch handler calls the pickRandNum function to construct the path to one of three recorded audio files applicable to a nomatch scenario.

If the user utters a valid weapon, the filled element calls the PickRandomWeapon of the CRPS object to pick a weapon on behalf of the computer followed by the BeatsByIndex method to determine who won based upon the rules defined in the class. Given the result, the application declares several several anonymously-scoped variables to give the user feedback whether they've won, lost, or drawn against the computer. After the game result audio is played to the user a clear statement is executed to erase the contents of the form item variable weapon and, hence, restart form interpretation.

The VoiceXML code for this example follows:

<?xml version="1.0"?>
<!-- 
Tellme Studio Code Example 111 
Copyright (C) 2000-2001 Tellme Networks, Inc. All Rights Reserved. 

THIS CODE IS MADE AVAILABLE SOLELY ON AN "AS IS" BASIS, WITHOUT WARRANTY 
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, 
WARRANTIES THAT THE CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A 
PARTICULAR PURPOSE OR NON-INFRINGING. 
--> 
<vxml version="2.0"> 

<!-- document-scoped scripts and variables -->
<script src="rps.js"/>
<script> 
function pickRandNum(max) 
{
   return Math.floor(Math.random()*(parseInt(max, 10)+1));
}

// instantiate a rock-paper-scissors game object
var oGame = new CRPS();
</script> 

<!-- base path to recorded audio -->
<var name="wavdir" expr="'./'"/>
<!-- keep track of the # of wins/losses/draws -->
<var name="iLosses" expr="0"/>
<var name="iWins" expr="0"/>
<var name="iDraws" expr="0"/>

<!-- array of prefixes for win/lose audio filenames -->
<var name="aPrefixes" expr="new Array('roc', 'pap', 'sci')"/>

<form id="play">
  
  <block> 
    <audio expr="wavdir + 'welcome.wav'"/> 
  </block> 

  <field name="weapon"> 
    <property name="timeout" value="3.0s"/>
    
    <prompt> 
      <audio expr="wavdir + 'menu.wav'">Choose rock, paper, or scissors.</audio> 
    </prompt> 

    
    <grammar mode="voice"
         root="root_rule"
         tag-format="semantics/1.0"
         type="application/srgs+xml"
         version="1.0"
         xml:lang="en-US">
        <rule id="root_rule" scope="public">
            <one-of>
                <item>
                    rock
                    <tag>out.weapon = "0";</tag>
                </item>
                <item>
                    paper
                    <tag>out.weapon = "1";</tag>
                </item>
                <item>
                    scissors
                    <tag>out.weapon = "2";</tag>
                </item>
                <item>
                    score
                    <tag>out.weapon = "10";</tag>
                </item>
            </one-of>
        </rule>

    </grammar>
 

    <nomatch>
      <!-- play a random nomatch prompt -->
      <!-- recorded audio files are nom[n].wav where n is 0..2 -->
      <audio expr="wavdir + 'nom' + pickRandNum(2) + '.wav'"> </audio> 
      <audio expr="wavdir + 'restart.wav'"/> 
      <reprompt/> 
    </nomatch> 

    <noinput> 
      <!-- play a random noinput prompt -->
      <!-- recorded audio files are noi[n].wav where n is 0..2 -->
      <audio expr="wavdir + 'noi' + pickRandNum(2) + '.wav'"> </audio> 
      <audio expr="wavdir + 'restart.wav'"/>
      <reprompt/> 
    </noinput> 

    <filled> 
      <if cond="10==weapon">
         <audio>You've won <value expr="iWins"/>, 
         lost <value expr="iLosses"/>, 
         and drawn <value expr="iDraws"/></audio>
         <break size="medium"/>
      <else/>

      <!-- pick a random weapon on behalf of the computer -->
      <var name="iComputerWeapon" expr="oGame.PickRandomWeapon()" />
      <!-- calculate the result of the clash -->
      <var name="iResult" expr="oGame.BeatsByIndex(weapon, iComputerWeapon)"/>
      <!-- lookup the prefix for the user's choice of weapons -->
      <var name="sPrefix" expr="aPrefixes[weapon]"/>
      <!-- get the friendly names for the user's and computer's weapons -->
      <var name="sUserWeapon" expr="oGame.GetWeaponNameByIndex(weapon)"/>
      <var name="sComputerWeapon" expr="oGame.GetWeaponNameByIndex(iComputerWeapon)"/>

      <if cond="-1==iResult">
        <!-- user loses -->
        <assign name="iLosses" expr="iLosses+1"/>
        <!-- each loss has three possible taunts; pick one -->
        <var name="iRand" expr="pickRandNum(2)" />
        <audio expr="wavdir + sPrefix + '-lose' + iRand + '.wav'"> 
            <value expr="sUserWeapon"/> loses to <value expr="sComputerWeapon"/>
        </audio> 

        <audio expr="wavdir + 'loser' + pickRandNum(2) + '.wav'"> 
          You're a loser. 
        </audio> 

      <elseif cond="1==iResult"/> 
         <!-- user wins -->
         <assign name="iWins" expr="iWins+1"/>
         <audio expr="wavdir + sPrefix + '-win0.wav'"> 
           <value expr="sUserWeapon"/> wins over <value expr="sComputerWeapon"/>
         </audio> 

         <audio expr="wavdir + 'winner' + pickRandNum(2) + '.wav'"> 
           You win. I hate you.
         </audio> 

      <elseif cond="0==iResult"/>
        <assign name="iDraws" expr="iDraws+1"/>
         <!-- no recorded audio for this -->
         <audio>draw</audio>

      <else/>
         <log>Unexpected return value (<value expr="iResult"/>) from RPS::BeatsByIndex</log>
      </if>
      
      <audio expr="wavdir + 'restart.wav'"> </audio> 

      </if>

	  <!-- clear the form item variable to restart form interpretation -->
      <clear namelist="weapon"/> 
    </filled> 

  </field> 
</form> 

</vxml> 

The JavaScript code for this example follows:

// rock-paper-scissors class
function CRPS()
{
   // array of weapons
   this._aWeapons = ["rock", "paper", "scissors"];

   // hash of weapons reverse mapped to indices
   this._hWeapons = {"rock" : 0, "paper" : 1, "scissors" : 2};

   // matrix (multi-dimensional array) of rules
   // indices of the main array correspond to weapons
   // inner array indices correspond to opposing weapon
   // 0 indicates a draw (e.g. rock vs. rock)
   // -1 indicates losing (e.g. rock vs. paper)
   // 1 indicates winning (e.g. paper vs. rock)
   this._aRules = [
         [0, -1, 1],
         [1, 0, -1],
         [-1, 1, 0]
      ];

}

// given two weapons, returns whether weapon1 beats weapon2
CRPS.prototype.BeatsRIP = function(weapon1, weapon2)
{
	return this._hRules[weapon1][weapon2];
}

// given two weapon indices, returns true if the 1st beats the 2nd; false otherwise
CRPS.prototype.BeatsByName = function(weapon1, weapon2)
{
   var iWeapon1 = this._hWeapons[weapon1];
   var iWeapon2 = this._hWeapons[weapon2];

   if (null == iWeapon1 || null == iWeapon2)
   {
      return null;
   }
   
   return this.BeatsByIndex(iWeapon1, iWeapon2);
}

// given two weapon indices, 
// returns 1 if the 1st weapon beats the 2nd; 0 if it's a draw, and -1 if 1st loses to 2nd
CRPS.prototype.BeatsByIndex = function(iWeapon1, iWeapon2)
{
   var iWeapons = this._aRules.length-1;
   if (iWeapon1 < 0 || iWeapon1 > iWeapons || iWeapon2 < 0 || iWeapon2 > iWeapons)
   {
      return null;
   }

   return this._aRules[iWeapon1][iWeapon2];
}

// pick a random weapon and return its index
CRPS.prototype.PickRandomWeapon = function()
{
	return Math.floor(Math.random()*this._aWeapons.length);
}

// retrieve the weapon's name given its index
CRPS.prototype.GetWeaponNameByIndex = function(i)
{
	if (isNaN(i) || i < 0 || i > this._aWeapons.length)
	{
		i = this.PickRandomWeapon();
	}
	
	return this._aWeapons[i];
}


[24]7 Inc.| Terms of Service| Privacy Policy| General Disclaimers