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!