I just started learning JavaScript (and the Knockout framework), and decided to try and make an initiative tracker for Dungeons and Dragons 5th Edition. It is designed to take an arbitrary number of Characters (that can either be added manually or chosen from a list of existing ones) and randomly order them for an encounter.
It also has functionality to manually set some initiatives (in case the players want to roll for themselves) while randomizing ones that haven't been set, and locking some characters so that they are not removed when an encounter is cleared (intended for players so their info doesn't need to be put in repeatedly)
As a beginner the things I'm wondering are if I'm following best practices and if I'm making any obvious (or non-obvious) mistakes?
/*
This is a program that will track initiatives for encounters in D&D5e (It would also work with other systems that use similar rules, but they won't be able to easily import monsters like D&D5e can)
*/
$.getJSON("Monsters.json", function (data){ //Load all the monsters
//Local copy of https://dl.dropboxusercontent.com/s/iwz112i0bxp2n4a/5e-SRD-Monsters.json
/*
The ListModel for knockout to use
*/
function InitiativeListModel() {
this.characters = ko.observableArray([new Character("",0), new Character("",0)]); //Setup the page with 2 blank characters
this.monsterData = ko.observableArray();
this.selected = ko.observable()
for (var i = 0; i < data.length; i++){ //Populate the monsterData array with the monsters' names and initiative scores
var a = data[i].name;
var b = data[i].dexterity;
this.monsterData.push(new Monster(a, b, i));
}
/*
Adds a blank character to the encounter. Intended for PCs and custom monsters.
*/
this.addCharacter = function() {
this.characters.push(new Character("", 0));
};
/*
Sets all initiative values to 0
*/
this.clearInitiative = function(){
for (i = 0; i < this.characters().length; i++){
this.characters()[i].initValue(0);
}
};
/*
Randomizes initiatives. A value will not be randomized if the current initiative value is not 0. This is so that players have the ability roll their own initiative. It then sorts all members of the encounter by their initiative value from highest to lowest.
*/
this.rollInitiative = function (){
for (i = 0; i < this.characters().length; i++){
if (this.characters()[i].initValue() != 0) { continue; }
this.characters()[i].initValue((Math.floor(Math.random() * 20)) + 1 + parseInt(this.characters()[i].modifier()));
}
this.characters.sort(function (left, right){
if (left.initValue() > right.initValue()){ return -1; }
if (left.initValue() < right.initValue()){ return 1; }
if (left.modifier() > right.modifier()) { return -1; }
if (left.modifier() < right.modifier()) { return 1;}
return 0;
})
}
/*
The clears all unlocked characters from the encounter. Locking is designed so that you don't have to keep adding characters for the players
*/
this.clearCharacters = function(){
this.characters.remove(function(item) { return item.locked() == false; });
}
/*
Adds the monster chosen from the drop down to the encounter
*/
this.addMonster = function() {
this.characters.push(new Character(this.monsterData()[this.selected()].name(), this.monsterData()[this.selected()].modifier()));
}
}
/*
Defines the properties of a character
*/
function Character (name, mod) {
this.locked = ko.observable(false); //This is used so that the info for PCs doesn't have to be entered continually
this.name = ko.observable(name ? name : "");
this.modifier = ko.observable(mod ? mod : 0);
this.initValue = ko.observable(0);
}
/*
Defines the properties of a monster based on the imported JSON
*/
function Monster (name, dexterity, id){
this.name = ko.observable(name);
//A monsters initiative modifier is equal to its dexterity modifier. To calculate the dexterity modifier from the dexterity score you subtract 10 from the score, divide that by 2 and then round down.
var temp = parseInt(dexterity)-10;
temp /= 2;
this.modifier = ko.observable(Math.floor(temp));
this.id = ko.observable(id);
this.displayName = ko.observable(name + ' (' + this.modifier() + ')')
}
ko.applyBindings(new InitiativeListModel());
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<!DOCTYPE html>
<html>
<head>
<title>D&D Tools - Initiative Tracker</title>
<script type='text/javascript' src='knockout-3.4.2.js'></script>
<script type='text/javascript' src='jquery-3.2.1.js'></script>
</head>
<body>
<h1>Initiative Tracker</h1>
<button data-bind="click: addCharacter">Add Blank Character</button>
<button data-bind="click: addMonster">Add Selected Monster</button>
<select data-bind="options: monsterData, optionsText: 'displayName', optionsValue: 'id', value: selected" ></select>
<button data-bind="click: clearCharacters">Clear Characters</button>
<table>
<thead>
<tr>
<th>Locked</th>
<th>Name</th>
<th>Init Mod</th>
<th>Initiative</th>
</tr>
</thead>
<tbody data-bind="foreach: characters">
<tr>
<td><input type="checkbox" data-bind="checked: locked"/></td>
<td><input data-bind="value: name"/></td>
<td><input data-bind="value: modifier"/></td>
<td><input data-bind="value: initValue"/></td>
</tr>
</tbody>
</table>
<button data-bind="click: clearInitiative">Clear Initiative</button>
<button data-bind="click: rollInitiative">Roll Initiative</button>
</body>
<script type='text/javascript' src='initiativeTracker.js'></script>
</html>
The snippet doesn't work properly (I'm assuming because it can't find Monster.json) for a working version see here.