Eposic Archive: JavaScript Animated D6 Dice Roller Code, Part Four

This page is in the Eposic Archive. Web pages in the Eposic Archive are possibly out of date and will not be maintained, but are being retained for historical purposes. Thank you for visiting Eposic!

A Sample, Functional Combat System

Now we'll look at using the dice roller code to implement a functional combat system. First, of course, we need to design our combat system. This combat system will use only six-sided dice, naturally, since this dice roller only rolls six-sided dice. But we also want a few character stats, and some opponent stats, eh?

For stats, let's keep things simple. We need an Offense stat, a Defense stat, and a Health stat. Let's say that each stat is to be in the range of 1 to 6. We'll generate these stats without animating any dice (this will give us a chance to see another useful D6 class method in action).

Once we have stats for our combatants, we'll click a button that will roll four dice. The first pair of dice will be for the hero of our story, and the second pair of dice will be for the monster that's attacking our hero. In each pair of dice rolled, the first die is an Offense roll, and the second die is a Defense roll. Each combatant will add his Offense stat to his Offense roll to come up with an Offense Total. Likewise for Defense. We'll then compare the hero's Offense Total against the monster's Defense Total; if it exceeds the monster's Defense Total, then the monster takes damage to its Health rating equal to the difference between the hero's Offense Total and the monster's Defense Total. Then do the same thing for the hero's Defense Total and the monster's Offense Total, subtracting any excess from the hero's Health. Got it?

Each click of the Roll Dice button represents one combat turn. At the end of each combat turn, we'll check for combatant fatigue and/or death. Combatant fatigue occurs when a combatant has been involved in combat for more combat turns than the combatant's Health rating (this check is made after accounting for any damage dealt in the current combat turn). When a combatant fatigues, he loses one point of Defense, unless his Defense is already 0, in which case he loses one point of Offense. Neither Defense or Offense will drop below 0 due to fatigue.

If at the end of any combat turn either the hero or the monster has zero or less Health, that combatant is dead. The survivor, if any, is declared the winner. In a full-fledged game, if the hero survived, we'd probably grant experience points and/or gold, but in this example, we'll just congratulate him.

Take a moment to try out the combat system. Then come back, and we'll look at the code...

OK, then, here's the code for the combat file, in it's entirety:

  1 <html>
  2 <head>
  3 <title>Example Combat System using the JavaScript Animated D6 Dice Roller</title>
  4 <script type='text/javascript' src='d6.js'></script>
  5 <script type='text/javascript'>
  6 var gameInfo = {
  7   'hero' : {
  8     'Offense' : D6.quickRandom(6),
  9     'Defense' : D6.quickRandom(6),
 10     'Health'  : D6.quickRandom(6)
 11   },
 12   'monster' : {
 13     'Offense' : D6.quickRandom(6),
 14     'Defense' : D6.quickRandom(6),
 15     'Health'  : D6.quickRandom(6)
 16   },
 17   'turns' : 0
 18 }
 19 
 20 function attack(total, combatInfo, results) {
 21   var hero = combatInfo.hero;
 22   var monster = combatInfo.monster;
 23   combatInfo.turns++;
 24   var heroOffenseTotal = results[0] + hero.Offense;
 25   var heroDefenseTotal = results[1] + hero.Defense;
 26   var monsterOffenseTotal = results[2] + monster.Offense;
 27   var monsterDefenseTotal = results[3] + monster.Defense;
 28   var monsterWounds = 0;
 29   var heroWounds = 0;
 30   if (heroOffenseTotal > monsterDefenseTotal) {
 31     monsterWounds = heroOffenseTotal - monsterDefenseTotal;
 32   }
 33   if (monsterOffenseTotal > heroDefenseTotal) {
 34     heroWounds = monsterOffenseTotal - heroDefenseTotal;
 35   }
 36   hero.Health -= heroWounds;
 37   monster.Health -= monsterWounds;
 38   var message = 'Combat turn: ' + combatInfo.turns + '<br /><br />';
 39   if (monsterWounds < 1) message += 'Hero missed!<br />';
 40   else message += 'Hero hit Monster for ' + monsterWounds + ' damage!<br />';
 41   if (heroWounds < 1) message += 'Monster missed!<br />';
 42   else message += 'Monster hit Hero for ' + heroWounds + ' damage!<br />';
 43   if (monster.Health < 1) {
 44     message += '<br /><b>Monster has died!</b><br />';
 45     if (hero.Health > 0) {
 46       message += '<br /><b>Congratulations on your victory!</b><br />';
 47       message += '<br /><a href="combat1.html">New combat.</a><br />';
 48       D6.setButtonLabel("none");
 49     }
 50   } else if (monster.Health < combatInfo.turns) {
 51     message += '<br /><em>Monster is getting tired!</em><br />';
 52     if (monster.Defense > 0)
 53       monster.Defense--;
 54     else if (monster.Offense > 0)
 55       monster.Offense--;
 56   }
 57   if (hero.Health < 1) {
 58     message += '<br /><b>Hero has died!</b><br />';
 59     message += '<br /><em>Sorry for the inconvenience!</em><br />';
 60     message += '<br /><a href="combat1.html">New combat.</a><br />';
 61     D6.setButtonLabel("none");
 62   } else if ((hero.Health < combatInfo.turns) && (monster.Health > 0)) {
 63     message += '<br /><em>Hero is getting tired!</em><br />';
 64     if (hero.Defense > 0)
 65       hero.Defense--;
 66     else if (hero.Offense > 0)
 67       hero.Offense--;
 68   }
 69   var messageElem = document.getElementById('messages');
 70   if (messageElem) messageElem.innerHTML = message;
 71   updateStat('heroOffense', hero.Offense);
 72   updateStat('heroDefense', hero.Defense);
 73   updateStat('heroHealth', hero.Health);
 74   updateStat('monsterOffense', monster.Offense);
 75   updateStat('monsterDefense', monster.Defense);
 76   updateStat('monsterHealth', monster.Health);
 77 }
 78 
 79 function updateStat(statId, statValue) {
 80   var statElem = document.getElementById(statId);
 81   if (statElem) statElem.innerHTML = statValue;
 82 }
 83 </script>
 84 </head>
 85 <body>
 86 <h1>Combat!</h1>
 87 <table border='1' cellspacing='0' cellpadding='5'>
 88   <tr>
 89     <td><b>Hero:</b></td>
 90     <td>Offense:
 91       <span id='heroOffense'><script type='text/javascript'>document.write(gameInfo.hero.Offense);</script></span>
 92       / <script type='text/javascript'>document.write(gameInfo.hero.Offense);</script>
 93     </td>
 94     <td>Defense:
 95       <span id='heroDefense'><script type='text/javascript'>document.write(gameInfo.hero.Defense);</script></span>
 96       / <script type='text/javascript'>document.write(gameInfo.hero.Defense);</script>
 97     </td>
 98     <td>Health:
 99       <span id='heroHealth'><script type='text/javascript'>document.write(gameInfo.hero.Health);</script></span>
100       / <script type='text/javascript'>document.write(gameInfo.hero.Health);</script>
101     </td>
102   </tr>
103   <tr>
104     <td><b>Monster:</b></td>
105     <td>Offense:
106       <span id='monsterOffense'><script type='text/javascript'>document.write(gameInfo.monster.Offense);</script></span>
107       / <script type='text/javascript'>document.write(gameInfo.monster.Offense);</script>
108     </td>
109     <td>Defense:
110       <span id='monsterDefense'><script type='text/javascript'>document.write(gameInfo.monster.Defense);</script></span>
111       / <script type='text/javascript'>document.write(gameInfo.monster.Defense);</script>
112     </td>
113     <td>Health:
114       <span id='monsterHealth'><script type='text/javascript'>document.write(gameInfo.monster.Health);</script></span>
115       / <script type='text/javascript'>document.write(gameInfo.monster.Health);</script>
116     </td>
117   </tr>
118 </table>
119 
120 <script type='text/javascript'>
121 D6.dice(4, attack, gameInfo);
122 </script>
123 
124 <div id='messages'><pre>
125 Click the Roll Dice button to begin the combat!
126 The first die is the hero's Offense roll.
127 The second die is the hero's Defense roll.
128 The third die is the monster's Offense roll.
129 The fourth die is the monster's Defense roll.
130 </pre></div>
131 
132 <p xmlns:dct="http://purl.org/dc/terms/" xmlns:vcard="http://www.w3.org/2001/vcard-rdf/3.0#">
133   <a rel="license"
134      href="http://creativecommons.org/publicdomain/zero/1.0/">
135     <img src="http://i.creativecommons.org/p/zero/1.0/88x31.png" style="border-style: none;" alt="CC0" />
136   </a>
137   <br />
138   To the extent possible under law,
139   <a rel="dct:publisher"
140      href="http://eposic.org">
141     <span property="dct:title">Michael K. Eidson</span></a>
142   has waived all copyright and related or neighboring rights to
143   <span property="dct:title">JavaScript Animated D6 Dice Roller</span>.
144 This work is published from:
145 <span property="vcard:Country" datatype="dct:ISO3166"
146       content="US" about="http://eposic.org">
147   United States</span>.
148 </p>
149 
150 </body>
151 </html>

We load the JavaScript Animated D6 Dice Roller classes on line 4. Lines 5 and 83 are the beginning and ending script tags that enclose additional JavaScript code. Let's examine that code in detail.

Lines 6 through 18 define the gameInfo global variable, which is used to store info about our hero, the monster, and the number of combat turns that pass during combat. These are all items to which our callback will need access.

Lines 7 through 11 define our hero character. We use JavaScript Object Notation (JSON) to create a hero object with three properties, namely, Offense, Defense, and Health. The values of these properties are obtained from the quickRandom method of the D6 class. The quickRandom method takes one integer argument larger than 1, and returns a pseudo-random integer value less than or equal to the supplied argument, but not less than 1. Thus, we set each of the hero's stats to a random value in the range 1 to 6 with the call D6.quickRandom(6).

Lines 12 through 16 define the monster's stats, which are determined in a manner similar to the hero's stats.

Line 17 initializes the number of combat turns that have passed so far to 0.

Lines 20 through 77 define the callback we'll be using in our call to the D6.dice function. We've named the callback attack, but we could have used any name, as long as it matches what we use for the second argument of D6.dice. In our attack callback, we want to compare the values rolled on the individual dice rolls against each other. To do this, we can't settle for only knowing the sum of the dice, as we did in the example in Part Three. We need to know the individual die rolls. We'll also need to have a hero and a monster, and know how many combat turns have passed.

Though previous examples only used one or two arguments in the callback function, there are actually three arguments passed to the callback function each time it is called by the dice roller code. The three arguments of a callback function are (1) the result total, (2) the callback data supplied during the call to D6.dice, and (3) an array containing the individual dice roll results.

In our example, we don't really care about the sum of the dice, so we'll basically be ignoring the first argument. But since it's the first argument, it has to be there if the second or third arguments are.

The second argument to the callback is referred to as the callback data. It is the same variable or object that is passed in as the third argument to D6.dice. If you take a moment to look at line 121, where D6.dice is called, you'll see that its third argument is gameInfo. The second argument of our callback is combatInfo. This means that combatInfo in the callback is the same object as the gameInfo global object.

The callback can update the callback data as much as it wants, and the modified callback data will be available to subsequent calls of the callback as its second argument.

The third argument of our callback, the results argument, is an array containing the individual die roll results. For example, since we're rolling 4 dice, the results array will have 4 elements. The first element is the number rolled on the first die. The second element is the number rolled on the second die. And so on.

Lines 21 and 22 grab the hero and monster objects from the combatInfo object. Line 23 increments the number of combat turns that have passed so far. (Remember, since this is a property of the combatInfo object, our callback data, the incremented value will be available to us the next time the callback is called.)

Likewise, if we modify any of the properties of the hero or monster objects, those modifications will be available to us the next time the callback is called. So let's do some modifying!

First, of course, we need to do some calculations and comparisons, to see whether or not either combatant hit the other, and, if so, how much damage was caused. We calculate the hero's Offense Total in line 24, the hero's Defense Total in line 25, and the similar values for the monster in lines 26 and 27. In lines 28 through 35, we determine how much damage each combatant dealt. If the Offense Total of one combatant is higher than the Defense Total of the other combatant, then the second combatant is wounded. In lines 36 and 37, we subtract any damage done to a combatant from that combatant's Health stat.

In lines 38 through 68, we build up the HTML for displaying a message about the outcome of the combat turn, including whether either combatant is getting tired (fatigued), and how much damage each combatant took this turn. If either combatant has died, that needs to be part of the displayed message, too. The message variable is used to accumulate the HTML-formatted message that will be displayed.

Lines 39 and 40 check to see if the hero hit the monster. Lines 41 and 42 check to see if the monster hit the hero. Line 43 checks to see if the monster has died. If it has, lines 44 through 49 append more HTML to the message variable, stating that the monster has died. On line 45 we check to see if the hero is still alive. If so, we append HTML to the message variable to congratulate the hero. On line 47, we add a bit of HTML defining a hyperlink to reload the combat page, so you can run a new combat.

If the combat is over (one or both combatants is defeated), there's no reason to keep the Roll Dice button on the page. Line 48 removes the button. To do this, we call the D6.setButtonLabel method, and pass it the keyword "none" as it's argument.

If the monster still lives, line 50 checks to see if the monster is fatiguing. If it is, then lines 51 through 56 perform some updates. Line 51 updates the message variable to include the message that the monster is getting tired. Lines 52 and 53 decrement the monster's Defense rating if it hasn't already dropped to zero; otherwise, the Offense rating is decremented, if it hasn't already dropped to zero.

Lines 57 through 68 check the hero's status (dead? fatigued?) and updates the hero object similar to how the monster object was updated above. The code here will also remove the dice rolling button if the hero dies, so the dice can't be rolled again if the monster lives but the hero dies.

Lines 69 and 70 find the messages element and set its innerHTML property to the value of the message variable. If you look at line 124, you will see where the messages element is defined. In fact, it is pre-loaded with a message, which can be read when the page first loads. This message is on lines 125 through 129. This message will be replaced by the HTML we've stored in the message variable.

To wrap up our callback, we call the updateStat function once for each hero and monster stat, to update the display if any stats have changed. The updateStat function is defined on lines 79 through 82. The function takes two arguments, the id of an element on the page, and the updated value of the stat to be displayed in that element. If you look at lines 91, 95, 99, 106, 110, and 114, you'll see the <span> tags that have the designated ids. You'll also notice that these <span> tags are pre-loaded with the original values from the hero and monster objects. The appropriate call to the updateStat function will replace the contents of the corresponding <span> tag with the current value of the corresponding stat.

Lines 85 through 132 define the body of the web page. We've already discussed portions of the body. Much of it is basic HTML, laying out our hero and monster data in a tabular format. There's some JavaScript embedded in the table to print out the original values of our hero and monster stats. Then on line 121 we make our call to the D6.dice function. The first argument indicates that four dice are to be rolled. The second argument is a reference to our callback. The third argument is our callback data. The D6.dice function will accept five arguments, but we let the last two default in this case.

We end with our messages area, which was discussed at length above.

This is a fairly simple combat system. The callback is still nearly 60 lines long, and the HTML for the table layout of the stats is 30+ lines long. So you can see that a more complex combat system could easily take hundreds of lines of JavaScript and HTML to implement. The dice code is not going to make it a whole lot easier to write a combat system. But it's 500+ lines of code that you don't have to write if you do want to include a dice roller in a game on your own web site.

There's still more dice roller features that you've yet to see! But be warned, if you think an example of 100+ lines is too long, the next example is not for you. I just figured I'd warn you about that up front...

Next, we'll make use of iframes to create a template for running full-fledged online RPG adventures, as described in Part Five: An Example Mini-Adventure!

Part Five: An Example Mini-Adventure! -->

<-- Part Three: Expanding Your Dice Rolling Capabilities

Eposic web dude Michael K. Eidson