Eposic Sample Code: JavaScript Generators

Part Two: Randomly Generating NPCs
by Michael K. Eidson

In Part One of the JavaScript Generators tutorial, you learned how to create a simple generator that could randomly choose one thing from a list of several. Using what you learned in Part One, you could build online tools similar to the Eposic Event Generator or the Eposic Magical Effects Generator. The most difficult part of creating these simple generators is coming up with the list of things that you want your generator to choose from.

Putting together a generator of a slightly more complex nature, such as a generator for randomly generating NPCs, or complex magic items, etc., uses the same basic principles as the simple generators. Below is a generator for randomly generating NPCs using the 5th edition rules of the Tunnels and Trolls (TM) game. (For simplicity in this example generator, we restrict the NPCs to first level; ignore the possibility of Warrior-Wizards; disregard minimum IQ and DEX requirements for Wizards; limit the selection of kindred to Humans, Dwarves, Elves, and Hobbits; and do not generate the NPC's equipment, spells, and languages. For a more complex Tunnels and Trolls NPC generator, visit the RPG section of the Eposic site.)

Click the Generate button a few times to see the generator at work. Then we'll look at the source code, and discuss how you might customize it to build an NPC generator for your own game in the game system of your choice.


Tunnels&Trolls (TM) NPC Generator
Type:
Level:
Kin:
Sex:
Ht:
(inches) Wt:(lbs)
ST:
IQ: LK:
CON:
DEX: CHR:
Gold:



The HTML: The following HTML, which includes embedded JavaScript, is the code for a web page which contains only the above generator. It is the complete code for the page. I will describe how to customize it below the code segment.

<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html;CHARSET=iso-8859-1">
<SCRIPT LANGUAGE="JavaScript">
<!--
function makeArray(len)
{
    for (var i=0; i<len; i++)
    this[i] = null;
    this.length = len;
}
function random(n)
{
    seed = (0x015a4e35 * seed) % 0x7fffffff;
    return (seed >> 16) % n;
}
function dice(n,d)
{
 var dicetotal = 0;
 for (var i=0; i<n; i++)
 {
  dicetotal += random(d) + 1;
 }
 return dicetotal;
}

var now;
var seed;
var types;
var numTypes;
var kins;
var numKins;
var sexes;
var numSexes;
var baseHts;
var baseWts;

function initvars()
{
 now = new Date();
 seed = now.getTime() % 0xffffffff;
 numTypes = 0;
 numKins = 0;
 numSexes = 0;

 types = new makeArray(0);
 types[numTypes++] = "Warrior";
 types[numTypes++] = "Wizard";
 types[numTypes++] = "Rogue";

 kins = new makeArray(0);
 kins[numKins++] = "Human";
 kins[numKins++] = "Dwarf";
 kins[numKins++] = "Elf";
 kins[numKins++] = "Hobbit";

 sexes = new makeArray(0);
 sexes[numSexes++] = "Male";
 sexes[numSexes++] = "Female";

 baseHts = new makeArray(19);
 baseHts[3] = 48;
 baseHts[4] = 51;
 baseHts[5] = 53;
 baseHts[6] = 56;
 baseHts[7] = 58;
 baseHts[8] = 61;
 baseHts[9] = 63;
 baseHts[10] = 66;
 baseHts[11] = 68;
 baseHts[12] = 71;
 baseHts[13] = 73;
 baseHts[14] = 76;
 baseHts[15] = 78;
 baseHts[16] = 81;
 baseHts[17] = 83;
 baseHts[18] = 86;

 baseWts = new makeArray(19);
 baseWts[3] = 75;
 baseWts[4] = 90;
 baseWts[5] = 105;
 baseWts[6] = 120;
 baseWts[7] = 135;
 baseWts[8] = 150;
 baseWts[9] = 160;
 baseWts[10] = 170;
 baseWts[11] = 180;
 baseWts[12] = 190;
 baseWts[13] = 200;
 baseWts[14] = 225;
 baseWts[15] = 250;
 baseWts[16] = 280;
 baseWts[17] = 310;
 baseWts[18] = 350;
}

function genNPC()
{
 var whichType = random(numTypes);
 var typeText = types[whichType];
 document.theform.Type.value = typeText;

 var whichKin = random(numKins);
 var kinText = kins[whichKin];
 document.theform.Kin.value = kinText;

 var whichSex = random(numSexes);
 var sexText = sexes[whichSex];
 document.theform.Sex.value = sexText;

// just do first level NPCs in this simple generator
 document.theform.Level.value = 1;

 var baseHt = baseHts[dice(3,6)];
 var baseWt = baseWts[dice(3,6)];
 if (sexText == "Female")
 {
  baseHt -= 2;
  baseWt -= 10;
 }
 var baseST = dice(3,6);
 var baseIQ = dice(3,6);
 var baseLK = dice(3,6);
 var baseCON = dice(3,6);
 var baseDEX = dice(3,6);
 var baseCHR = dice(3,6);
 var baseGOLD = dice(3,6) * 10;

 if (kinText == "Human")
 {
  document.theform.Ht.value = baseHt;
  document.theform.Wt.value = baseWt;
  document.theform.ST.value = baseST;
  document.theform.IQ.value = baseIQ;
  document.theform.LK.value = baseLK;
  document.theform.CON.value = baseCON;
  document.theform.DEX.value = baseDEX;
  document.theform.CHR.value = baseCHR;
  document.theform.Gold.value = baseGOLD;
 }

 if (kinText == "Dwarf")
 {
  document.theform.Ht.value = parseInt(baseHt * 2 / 3);
  document.theform.Wt.value = parseInt(baseWt * 7 / 8);
  document.theform.ST.value = baseST * 2;
  document.theform.IQ.value = baseIQ;
  document.theform.LK.value = baseLK;
  document.theform.CON.value = baseCON * 2;
  document.theform.DEX.value = baseDEX;
  document.theform.CHR.value = parseInt(baseCHR * 2 / 3);
  document.theform.Gold.value = baseGOLD;
 }

 if (kinText == "Elf")
 {
  document.theform.Ht.value = parseInt(baseHt * 11 / 10);
  document.theform.Wt.value = baseWt;
  document.theform.ST.value = baseST;
  document.theform.IQ.value = parseInt(baseIQ * 3 / 2);
  document.theform.LK.value = baseLK;
  document.theform.CON.value = parseInt(baseCON * 2 / 3);
  document.theform.DEX.value = parseInt(baseDEX * 3 / 2);
  document.theform.CHR.value = baseCHR * 2;
  document.theform.Gold.value = baseGOLD;
 }

 if (kinText == "Hobbit")
 {
  document.theform.Ht.value = parseInt(baseHt / 2);
  document.theform.Wt.value = parseInt(baseWt / 2);
  document.theform.ST.value = parseInt(baseST / 2);
  document.theform.IQ.value = baseIQ;
  document.theform.LK.value = baseLK;
  document.theform.CON.value = baseCON * 2;
  document.theform.DEX.value = parseInt(baseDEX * 3 / 2);
  document.theform.CHR.value = baseCHR;
  document.theform.Gold.value = baseGOLD;
 }

 return;
}
// -->
</SCRIPT>
<TITLE>T&amp;T NPC Generator</TITLE>
</HEAD>

<BODY TEXT="black" BGCOLOR="white" onload="initvars()">
<FORM ACTION="javascript:genNPC()" METHOD="POST" NAME="theform">
<B>Tunnels&amp;Trolls (TM) NPC Generator</B><BR>
<B>Type:</B><INPUT TYPE="TEXT" NAME="Type" SIZE="8">
<B>Level:</B><INPUT TYPE="TEXT" NAME="Level" SIZE="2"><BR>
<B>Kin:</B><INPUT TYPE="TEXT" NAME="Kin" SIZE="6">
<B>Sex:</B><INPUT TYPE="TEXT" NAME="Sex" SIZE="6"><BR>
<B>Ht:</B><INPUT TYPE="TEXT" NAME="Ht" SIZE="6">(inches)
<B>Wt:</B><INPUT TYPE="TEXT" NAME="Wt" SIZE="4">(lbs)<BR>
<B>ST:</B><INPUT TYPE="TEXT" NAME="ST" SIZE="4">
<B>IQ:</B><INPUT TYPE="TEXT" NAME="IQ" SIZE="4">
<B>LK:</B><INPUT TYPE="TEXT" NAME="LK" SIZE="4"><BR>
<B>CON:</B><INPUT TYPE="TEXT" NAME="CON" SIZE="4">
<B>DEX:</B><INPUT TYPE="TEXT" NAME="DEX" SIZE="4">
<B>CHR:</B><INPUT TYPE="TEXT" NAME="CHR" SIZE="4"><BR>
<B>Gold:</B><INPUT TYPE="TEXT" NAME="Gold" SIZE="6"><BR>
<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Generate">
<INPUT TYPE="RESET" NAME="Reset" VALUE="Clear">
</FORM>
</BODY>
</HTML>

Understanding the Code: Before you can customize the above HTML to generate NPCs to suit your own needs, you need to understand a bit about how it works. To this end, let's examine portions of the code. (It is assumed that you know some HTML already, and understand what certain basic tags, such as <B>, stand for. If this is a bad assumption, then you might be able to figure out what's going on from the following explanation, but I'd still suggest that you get a basic book on HTML and read it.)

When you begin to create a JavaScript generator like the one above, you'll want to determine first exactly what it is that you'll be generating. You can't very well start writing JavaScript without knowing what you need to write JavaScript for. So you'll start your generator by writing the HTML that defines the text fields for which you plan to generate values. Any code for text fields is always placed in a FORM, and FORMs always reside in the BODY of the HTML file. The BODY always comes after the HEAD. JavaScript functions like those above are best placed in the HEAD. What this all means when it comes to writing these JavaScript generators is that even though the HEAD physically comes before the BODY in an HTML file, it's best to write the BODY first, and then go back and add the HEAD and the JavaScript afterwards.

So let's start our examination of the above example code with the BODY and FORM tags:

<BODY TEXT="black" BGCOLOR="white" onload="initvars()">
<FORM ACTION="javascript:genNPC()" METHOD="POST" NAME="theform">

The important part of this BODY tag is the onload="initvars()". This tells the web browser that as soon as it finishes loading the HTML for the page, it should run the JavaScript function called initvars. This function seeds the random number generator for us (just like in Part One), and initializes some arrays for us. We'll look at the initvars function a bit more later.

Whereas the BODY tag tells the web browser what JavaScript function to call to initialize things, the FORM tag tells the web browser what JavaScript function to call when the Generate button is clicked. An HTML form is often used to submit data to a CGI program on a web server, but you can tell it to invoke a JavaScript function instead with a prefix of javascript:. Thus, in the code for our FORM tag above, we've told it to invoke a JavaScript function called genNPC whenever the Generate button is clicked.

The FORM tag also includes a NAME attribute. It's set to "theform" in our example. Our JavaScript functions will need to use this name when referring to the text fields in the form. More about this later.

The next line is pretty much basic HTML, and is there as a visible label for the web page. You can change the page label in any way you see fit.

The next line after the page label is:

<B>Type:</B><INPUT TYPE="TEXT" NAME="Type" SIZE="8">

Here, we are setting up a bold label (<B>Type:</B>) and a text field (the INPUT tag) in which a value for that label may be displayed. The text field has a NAME attribute of "Type". This name is used in the JavaScript code when we place a value in the text field. In our example code above, we would reference the value of this text field as document.theform.Type.value. Here, theform is the name we gave our FORM, and Type is the name we gave our text field.

This text field has a SIZE attribute of "8". The SIZE attribute tells the web browser how many characters are to be visible within the text field. You can set the SIZE attribute of each text field to be whatever you deem appropriate for that text field. Why did we assign "8" to the SIZE attribute of this particular text field? This particular text field can contain one of three strings: Warrior, Wizard, or Rogue. The longest of these is seven characters long. So we could have set the SIZE attribute of this text field to "7" and it would have accomodated any of the strings we might place in it. But since I like to give a little extra space, I increased the "7" to "8". You can set the size of your text fields in any way you see fit, of course.

The next several lines of HTML that follow are for additional text fields, each having a NAME attribute that is set to a string representative of what we want to use that text field for, and a SIZE attribute that will accomodate the largest string that we might want to place in the text field. After the HTML code for the text fields, there is a line that defines the Generate button and the Clear button. Then we close up the FORM, the BODY, and the HTML.

Keep in mind that when you set out to write your own customized JavaScript generator, the number of text fields and the labels, NAMEs, and SIZEs of these text fields will depend on exactly what it is you're trying to generate.

Now that we've examined the BODY of the above code, let's examine the HEAD. We've got some basic HTML stuff in there, including the TITLE for the page, which you'll want to change to reflect the contents of your web page. But the majority of the HEAD's content is JavaScript code. There are five JavaScript functions defined: makeArray, random, dice, initvars, and genNPC. Let's take a look at each of these functions.

makeArray: OK, I confess, I lifted this function from Nick Heinle's DESIGNING WITH JAVASCRIPT, published by O'Reilly & Associates, Inc. way back in 1997. (I don't think either Nick or O'Reilly will mind us using this function, especially if you go buy your own copy of the book. Even though this is an old book by internet standards, the wisdom it contains still applies today.) As the book explains, you could do away with makeArray, and just use Array, which is standard in all modern JavaScript-enabled browsers. Is there anyone out there really still using Netscape 2? If you don't think any of your audience is using such an antiquated browser, then you needn't worry about using makeArray, and can just use the standard JavaScript Array function.

random: This function returns a random number in the range 0 to N-1, where you pass N as an argument. To give credit where credit is due--this function was also lifted from Nick Heinle's JavaScript book. The algorithm is, however, a commonly known one, and the only thing that really matters in the function is the choice of number by which you multiply the seed each time the function is called. Change this number, and you change the quality of the random number generator. I could have tried to come up with another number as good as Nick's, but Nick's number seems to give good results, so I kept it. Again, I don't think Nick or O'Reilly will mind, since I've given them credit, and you're thinking about buying the book because of my recommendation, right?

dice: Now this function (as well as the remaining two that follow) is my creation, even if it is a simple little thing (much like makeArray and random, above). It rolls a specified number of dice of specified sizes and returns the total, using random to simulate the roll of individual dice. The first argument to dice is the number of dice to roll and the second argument is the number of sides on each die. Thus, dice(3,6) indicates that the dice function is to roll three six-sided dice and return the result.

After the dice function are listed some variables. These variables could have been listed anywhere outside of the five JavaScript functions. The variables that are listed are those that are shared between multiple JavaScript functions. All of these variables are initialized in the initvars function.

initvars: Theoretically, you shouldn't have to wait until the web page loads to initialize variables. However, I've found that it works better and across more browsers if you do so. Thus, the initvars function is used to seed the random number generator and to create and populate the arrays that hold the lists that we'll be using when we randomly fill in some of the text fields. Take time to study the above initvars function, and see how it compares to the initvars function back in Part One. In Part One, we were only trying to randomly fill in one text field. Here in Part Two, we need to fill in multiple text fields. Thus, initvars must initialize multiple lists.

One thing to take note of in the above initvars function is how the baseHts and baseWts arrays are populated. The values placed in these arrays start at index 3 and go to index 18. That's because we are going to roll 3d6 for indexing the array. The values that are placed into these arrays are taken from the Height and Weight table in the T&T rulesbook (converting the feet and inches in the table to total inches). These values are used for Human characters; we'll have to modify them for other kindred, but that's something the genNPC function will take care of.

genNPC: This is the function that is invoked each time the user clicks on the Generate button. For the Type, Kin, and Sex text fields, genNPC acts just like the genthing function back in Part One, randomly choosing from the list of available types, kins, and sexes, and setting the associated text fields with the selected values. The code in this example that sets the Type field, for example, is the following:

 var whichType = random(numTypes);
 var typeText = types[whichType];
 document.theform.Type.value = typeText;

Do you see the similarity between this code and the code for genthing in Part One?

These three lines of code could be condensed into one line, but if you want to perform other calculations based on the selected type, it's easier to do it with either the generated number or the text associated with it, rather than using document.theform.Type.value in your calculations.

After setting the Type, Kin, and Sex text fields, genNPC then sets the Level text field to a constant 1, since this generator is a simple one, and trying to generate an NPC of a higher level than first level would greatly increase the complexity of the program, so we avoid it here.

Next, genNPC determines the base height and weight of the character. The lines that select the NPC's base height and weight are as follows:

 var baseHt = baseHts[dice(3,6)];
 var baseWt = baseWts[dice(3,6)];

This code simulates the rolling of three six-sided dice and uses the result to index the baseHts array, which contains the list of possible base heights. It does the same for the NPC's base weight.

Then the code checks to see if the character is female, and, if so, adjusts the base height and weight according to the rules:

 if (sexText == "Female")
 {
  baseHt -= 2;
  baseWt -= 10;
 }

Next, we roll the NPC's base attributes and gold. In this example, we roll straight 3d6 for each attribute. If you want to do something a little more complex, such as rolling 4d6 and dropping the high die, or rolling 3d6 with triples adding and rolling again, you've got a little more work cut out for you; we'll leave such complexities as exercises for the reader.

 var baseST = dice(3,6);
 var baseIQ = dice(3,6);
 var baseLK = dice(3,6);
 var baseCON = dice(3,6);
 var baseDEX = dice(3,6);
 var baseCHR = dice(3,6);
 var baseGOLD = dice(3,6) * 10;

Once we've rolled up the base stats, we're ready to modify them according to the NPC's kindred modifiers. For a Human NPC, we merely use all the base stats that we just rolled:

 if (kinText == "Human")
 {
  document.theform.Ht.value = baseHt;
  document.theform.Wt.value = baseWt;
  document.theform.ST.value = baseST;
  document.theform.IQ.value = baseIQ;
  document.theform.LK.value = baseLK;
  document.theform.CON.value = baseCON;
  document.theform.DEX.value = baseDEX;
  document.theform.CHR.value = baseCHR;
  document.theform.Gold.value = baseGOLD;
 }

If the NPC is a Dwarf, we modify the base stats according to the T&T rules:

 if (kinText == "Dwarf")
 {
  document.theform.Ht.value = parseInt(baseHt * 2 / 3);
  document.theform.Wt.value = parseInt(baseWt * 7 / 8);
  document.theform.ST.value = baseST * 2;
  document.theform.IQ.value = baseIQ;
  document.theform.LK.value = baseLK;
  document.theform.CON.value = baseCON * 2;
  document.theform.DEX.value = baseDEX;
  document.theform.CHR.value = parseInt(baseCHR * 2 / 3);
  document.theform.Gold.value = baseGOLD;
 }

We use the standard JavaScript parseInt function to make sure that any expressions dealing with division are rounded down to a whole number; we don't want any fractional stats for our generated NPC.

The code for Elf or Hobbit NPCs is similar. It's just a matter of modifying the base stats according to the rules for the selected kindred.

That's all there is to it.

Customizing the Code: To customize the above code, first determine exactly what you want to generate in your generator. Then follow this seven step process:

  1. Create the BODY and FORM of your HTML page, laying out all the labels and text fields you want to use, and naming them appropriately. Set the size of each text field. If you want, add some text, align your fields using tables, and pretty things up a bit more than we did in our above example.

  2. Make sure the onload event of the BODY tag is set to "initvars()".

  3. Make sure the FORM's ACTION attribute is set to "javascript:genNPC()", and the FORM's NAME attribute is "theform". (OK, once you know what you're doing, you can use different names than these, but until then, stick to the formula.)

  4. Copy the makeArray, random, and dice functions from above, as is, and paste them into the HEAD of your HTML document.

  5. Create the initvars function for your generator. For those fields that you will be selecting randomly from lists, call makeArray for each list, and assign values, either numbers or text, to the cells of the array at appropriate indices in the array. The appropriate indices depend on what dice you will be rolling to generate indices into the array. For instance, if you'll be rolling 3d6 to generate a value, the indices into your array will be 3, 4, 5, etc., up to 18. If you'll be rolling 2d6, the indices into your array will be 2, 3, 4, etc., up to 12. For some lists, you'll just want to pick one item from each list, with each item in a list having the same chance of being picked as any other item in the list. In this case, populate the array associated with such a list in the same manner as we populated the types, kins, and sexes arrays.

  6. Write your genNPC function. Your primary goal in this routine is to make sure that every text field on your page is assigned a value every time the Generate button is clicked. You can use the code in this example as a starting point. You may have to experiment or study up on JavaScript to get exactly what you want. This function can get very complicated quite easily. Try to keep it simple at first, then build on the simple generator to create a more complex generator.

  7. The finishing touch: List all the variables that are shared between multiple functions, placing them in the JavaScript area, but outside of any of the functions.

I realize there's a lot to remember here, but with a little time, patience, and a lot of studying and experimenting, you can create a JavaScript NPC generator custom-fit for your own RPGs. With a little practice, you can be creating JavaScript generators that generate more than just NPCs. And, hey, if you decide to publish any generators on your own web site using what you've learned here, let me know, and I'll point a link at you from my Links page.

If you're interested in learning JavaScript, Nick's book DESIGNING WITH JAVASCRIPT is good, but I'd also suggest that you get a copy of David Flanagan's JAVASCRIPT; THE DEFINITIVE GUIDE (the latest edition, though you can learn a lot from the earlier editions, too). It's the book that yours truly used to learn JavaScript, so it must be good!!

Happy coding!