Eposic JavaScript D6 Dice Code
Part Four: 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 Eposic 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 </body>
133 </html>
We load the Eposic dice 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