Single-page web app from AppletI'm seeking feedback on my first single-page web app using RequireJS and a web worker. I converted it from a Java applet, and the code is up on GitHub.It's a combat simulator based on the old-school (1977) game of Melee. The RequireJS design is based on jrburke's single page app template.Here's what the whole design looks like in UML:Feedback I'm seekingI'm an experienced Java programmer who, upon realizing Java applets don't run in browsers anymore (I know I'm late to that party!), decided to bite the bullet and move to JavaScript. The result I have seems to work well and in some ways is probably better than my Java applet originally was. I'm curious for feedback on my approach to converting the applet and the use of RequireJS, web workers, etc.I tried as best I could to reproduce the Java Swing GUI in HTML 5 and BootStrap. I'm not 100% happy with an HTML Select with multiple option to reproduce a Java JList. It's not great in mobile versions of Chrome (Android), as it remains closed until you tap on it. There's no easy way to select all, for example (you have to tap once on each item!). But it works great in desktop browsers. You can try the live version of the simulator.Since HTML 5 web workers have almost 100% separate memory space from the main thread, I intentionally didn't use the same class with RequireJS in two separate threads (see the UML diagram). Probably this isn't a hard an fast rule. But in an early design, one of my classes (HeroesSingleton) was being loaded twice (once in the main, once in the web worker) and so it wasn't actually a singleton. That seems to be a gotcha with RequireJS and threads.I followed a few templates for RequireJS class designs for the objects in the simulator, e.g., Hero, Weapon, etc. In the simulator, only Hero and Game objects are objects that change state (not immutable). I'm pretty sure the methods (getters) I have there are overkill since there is no true private member in JavaScript (as far as I understand RequireJS). I know about underscore convention for indicating private, and I know about closures (but didn't see an obvious way to hide implementation details in RequireJS).Finally, without a good editor/IDE, it's possible to make a lot of mistakes (e.g. unbalanced parentheses, calling the wrong function in a module, etc.) that don't get detected until run-time. I'd love feedback on how to save time with IntelliSense developing a single-page RequireJS or any other modular technique. Visual Studio 2013 isn't bad. For now, I don't want to go into a full-on web framework (such as Angular) since my short-term problem is getting existing Applets to work as single-page apps in JavaScript.Here are a few classes (not all are complete or even present, but again all the code is up on GitHub):controller.js/* global sorttable */define([./HeroesSingleton], function (HeroesSingleton) {    use strict;    var isPoleWeaponsChargeFirstRoundChecked = false;    var isDefendVsPoleChargeChecked = false;    var isVerboseChecked = false;    var webWorker = {};    // ...    return {        start: function () {            // ...            // crunch the numbers in a web worker            var worker = new Worker(app/simulator.js);            webWorker = worker;            //worker.postMessage(hello);            worker.addEventListener(message, function (event) {                var data = event.data;                //console.log(Web worker messaged me:  + event.data);                switch (data.cmd) {                    case 'worker started':                        // give worker the info                        worker.postMessage({ 'selectedHeroes': selectedHeroes, 'boutCount': boutCount, 'isPoleWeaponsChargeFirstRound': isPoleWeaponsChargeFirstRoundChecked, 'isDefendVsPoleCharge': isDefendVsPoleChargeChecked, 'isVerbose': isVerboseChecked });                        var p = document.createElement('p');                        p.className = bg-info;                        p.appendChild(document.createTextNode(Calculating results - please wait.));                        document.getElementById(matchupWins).appendChild(p);                        p = document.createElement('p');                        p.className = bg-info;                        p.appendChild(document.createTextNode(Calculating results - please wait.)); document.getElementById(heroWins).appendChild(p);                        break;                    case 'log':                        logBuffer += data.message + \n;                        break;                    case 'progressUpdate':                        progressBar.value = data.progress;                        break;                    case 'finished':                        /**                         * Clear messages                          */                        clearDiv(heroWins);                        clearDiv(matchupWins);                        var heroWinsTable = createTableFromProperties(data.heroWins, (selectedHeroes.length - 1) * boutCount, Results for  + selectedHeroes.length +  heroes, paired up for  + boutCount +  bouts each, false);                        document.getElementById(heroWins).appendChild(heroWinsTable);                        sorttable.makeSortable(heroWinsTable);                        var matchupWinsTable = createTableFromProperties(data.matchupWins, boutCount, Pairwise results for  + selectedHeroes.length +  heroes, paired up for  + boutCount +  bouts each:, true);                        document.getElementById(matchupWins).appendChild(matchupWinsTable);                        sorttable.makeSortable(matchupWinsTable);                        /**                         * Force tables to be sorted                         */                        var myTH = document.getElementById(matchwins);                        sorttable.innerSortFunction.apply(myTH, []); // once for ascending                        sorttable.innerSortFunction.apply(myTH, []); // again for descending (stupid but it's how it works)                        myTH = document.getElementById(wins); // top table last, since the icon only shows on last table sorted...                        sorttable.innerSortFunction.apply(myTH, []);                        sorttable.innerSortFunction.apply(myTH, []);                        verboseOutputText.value = logBuffer;                        startButton.disabled = false;                        stopButton.disabled = true;                        break;                    default:                        console.log(Unrecognized message from web worker: );                        console.log(data);                        break;                }            });            worker.addEventListener(error, function () {                console.log(WORKER ERROR, arguments);            });            // worker takes over leaving the GUI thread free to update        },        stop: function () {            /**             * Stop the web worker             */            var stopButton = this;            this.disabled = true;            webWorker.terminate();            /**             * Clear results from previous run              */            clearDiv(heroWins);            clearDiv(matchupWins);            var p = document.createElement('p');            p.className = bg-warning;            p.appendChild(document.createTextNode(No results becase the simulator was stopped before it finished.));            document.getElementById(matchupWins).appendChild(p);            p = document.createElement('p');            p.className = bg-warning;            p.appendChild(document.createTextNode(No results becase the simulator was stopped before it finished.)); document.getElementById(heroWins).appendChild(p);            document.getElementById(startSimulation).disabled = false;        }    };});simulator.js -- note: loaded from an HTML 5 web workerimportScripts('../lib/require.js');require([./HeroesSingleton, ./Hero, ./Game, ./controller, ./Logger], function (HeroesSingleton, Hero, Game, controller, Logger) {    use strict;    var poleWeaponsChargeFirstRound = null;    var defendVsPoleCharge = null;    self.postMessage({ cmd: worker started });    self.addEventListener('message', function (event) {        /**         * Only one type of message to start this thread         */        var data = event.data;        var heroSet = [];  // list of heroes to fight        Game.createHeroesMap();        var completeHeroMap = Game.getHeroMap();        data.selectedHeroes.forEach(function (heroName) {            var hero = completeHeroMap[heroName];            heroSet.push(hero);        }, this);        /**         * Configure simulator options         */        Logger.setMute(!data.isVerbose);        poleWeaponsChargeFirstRound = data.isPoleWeaponsChargeFirstRound;        defendVsPoleCharge = data.isDefendVsPoleCharge;        tryAllCombinations(heroSet, data.boutCount);    });    function tryAllCombinations(heroSet, boutCount) {        var matchupWins = {};  // map of hero and integer        var heroWins = {};        var game = null;        var score = [2];        var progress = 0;        // how many bouts total is N * N-1 * boutCount        var totalIterations = heroSet.length * (heroSet.length - 1) * boutCount / 2;        var iterationCount = 0;        heroSet.forEach(function (hero1) {            heroWins[hero1.getName()] = 0;            heroSet.forEach(function (hero2) {                if (hero1 != hero2) matchupWins[hero1.getName() + / + hero2.getName()] = 0;            }, this);        }, this);        //console.log(heroWins);        for (var h1 = 0; h1 < heroSet.length; h1++) {            var hero1 = heroSet[h1];            var h2 = 0;            var hero2 = heroSet[h2];            for (h2 = h1 + 1; h2 < heroSet.length; h2++) {                hero2 = heroSet[h2];                var sumRounds = 0;                score[0] = 0;                score[1] = 0;                Logger.log('Matchup: ' + hero1.getName() + ' vs. ' + hero2.getName());                for (var bout = 0; bout < boutCount; bout++) {                    Logger.log(Bout:  + bout + 1 +  of  + boutCount);                    /**                    * update progress bar on page (assumes max is 100)                    */                    progress = Math.ceil((++iterationCount / totalIterations) * 100);                    self.postMessage({ cmd: progressUpdate, progress: progress });                    // clone heroes (resets them) prior to fighting                    var fightingHero1 = Object.create(hero1);                    var fightingHero2 = Object.create(hero2);                    // console.log(fightingHero1);                    // console.log(fightingHero2);                    game = new Game(fightingHero1, fightingHero2, poleWeaponsChargeFirstRound, defendVsPoleCharge);                    var winningFighter = game.fightToTheDeath();                    if (winningFighter !== null) {                        var losingFighter = (winningFighter == fightingHero1 ? fightingHero2 : fightingHero1);                        score[(winningFighter == fightingHero1 ? 0 : 1)]++;                        matchupWins[winningFighter.getName() + / + losingFighter.getName()]++;                    }                    sumRounds += game.round;                }                /**                 * Update the total stats for these heroes                 */                heroWins[hero1.getName()] += score[0];                heroWins[hero2.getName()] += score[1];            }        }        /**         * Put stats back on page         */        self.postMessage({ cmd: finished, heroWins: heroWins, matchupWins: matchupWins });    }    function updateProgress() {        var progressBar = document.getElementById(progress);        if (progressBar.value < progressBar.max) {            setTimeout(updateProgress, 80);        } else {            return;        }    }});Hero.jsdefine([./Weapon, ./Logger], function (Weapon, Logger) {    use strict;    // Start with the constructor    function Hero(name, st, dx, weapon, armor, shield) {        this.name = name;        this.st = st;        this.dx = dx;        this.readiedWeapon = weapon;        this.armor = armor;        this.shield = shield;        this.knockedDown = false;        this.standingUp = false;        this.pickingUpWeapon = false;        this.droppedWeapon = Weapon.NONE;        this.damageTaken = 0;        this.damageTakenThisRound = 0;        this.injuryDexPenalty = false;        this.recovering = false;        this.defending = false;        this.charging = false;    }    // Now add methods    Hero.prototype.getName = function () {        return this.name;    };    Hero.prototype.getST = function () {        return this.st;    };    Hero.prototype.adjST = function () {        return Math.max(this.st - this.damageTaken, 0);    };    Hero.prototype.getDX = function () {        return this.dx;    };    Hero.prototype.adjustedDx = function () {        return this.dx - this.armor.getDexAdjustment() - this.shield.getDexAdjustment() - (this.injuryDexPenalty ? 2 : 0) - (this.isStrengthLowPenalty() ? 3 : 0);    };    Hero.prototype.isAlive = function () {        return (this.st - this.damageTaken > 0);    };    Hero.prototype.isConscious = function () {        return (this.st - this.damageTaken > 1);    };    Hero.prototype.isKnockedDown = function () {        return this.knockedDown;    };    Hero.prototype.standUp = function () {        this.standingUp = true;    };    /**     * These rules maybe should go into Game (better cohesion)     */    Hero.prototype.newRound = function () {        this.charging = false;        this.defending = false;        this.damageTakenThisRound = 0;        if (this.standingUp) {            this.knockedDown = false;            this.standingUp = false;        }        else if (this.pickingUpWeapon)  // technically was picking up weapon last round        {            this.readiedWeapon = this.droppedWeapon;            this.droppedWeapon = Weapon.NONE;            this.pickingUpWeapon = false;        }        /*         * Dex penalty due to injury lasts one complete round         */        if (this.injuryDexPenalty && this.recovering) {            this.injuryDexPenalty = false;            this.recovering = false;        }        else if (this.injuryDexPenalty) {            this.recovering = true;        }    };    Hero.prototype.takeHits = function (hits) {        var armorPoints = this.armor.hitsStopped() + this.shield.hitsStopped();        var damageDone = hits - armorPoints;        if (damageDone < 0) damageDone = 0;        Logger.log(this.name +  taking  + hits +  hits.);        Logger.log(this.armor.getName() +  stops  + this.armor.hitsStopped() );        Logger.log(this.shield.getName() +  stops  + this.shield.hitsStopped());        Logger.log(this.name +  taking  + damageDone +  damage.);        this.takeDamage(damageDone);        return damageDone;    };    /**     * After it's got past armor, etc.     */    Hero.prototype.takeDamage = function (damageDone) {        this.damageTaken += damageDone;        this.damageTakenThisRound += damageDone;        this.injuryDexPenalty = this.sufferingDexPenalty();        if (this.injuryDexPenalty) Logger.log(this.name +  has an adjDx penalty of -2 for remainder of this round and the NEXT round.);        Logger.log(this.name +  has now taken  + this.damageTaken +  points of damage, ST =  + this.st + (this.damageTaken >=this.st ?  and is DEAD. : (this.st - this.damageTaken == 1 ?  and is UNCONSCIOUS. : .)));        if (this.damageTakenThisRound >= 8) {            this.knockedDown = true;            Logger.log(this.name +  has been knocked down by damage.);        }        if (this.isStrengthLowPenalty()) Logger.log(this.name +  has an additional DX adjustment of -3 due to ST <= 3.);    }    Hero.prototype.sufferingDexPenalty = function () {        return (this.damageTakenThisRound >= 5 || this.recovering);    };    Hero.prototype.isStrengthLowPenalty = function () {        return (this.st - this.damageTaken <= 3);    };    Hero.prototype.setDefending = function () {        this.defending = true;    };    Hero.prototype.isDefending = function () {        return this.defending;    };    Hero.prototype.setCharging = function (isCharging) {//        Logger.log(Hero: setCharge to  + isCharging);        this.charging = isCharging;    };    Hero.prototype.isCharging = function () {        return this.charging;    };    Hero.prototype.isProne = function () {        return this.pickingUpWeapon;    };    Hero.prototype.getWeapon = function () {        return this.weapon;    };    Hero.prototype.getReadiedWeapon = function () {        return this.readiedWeapon;    }    Hero.prototype.dropWeapon = function () {        this.droppedWeapon = this.readiedWeapon;        this.readiedWeapon = Weapon.NONE;    };    Hero.prototype.breakWeapon = function () {        this.readiedWeapon = Weapon.NONE;        this.droppedWeapon = Weapon.NONE; // shouldn't need this, but just in case    };    Hero.prototype.getDroppedWeapon = function () {        return this.droppedWeapon;    }    Hero.prototype.pickUpWeapon = function () {        this.pickingUpWeapon = true;    }    Hero.prototype.getArmor = function () {        return this.armor;    };    Hero.prototype.armorPoints = function () {        this.armor.hitsStopped() + this.shield.hitsStopped();    };    Hero.prototype.getShield = function () {        return this.shield;    };    Hero.prototype.toString = function () {        return this.name + \n + this.armor.toString() + \n + this.readiedWeapon.toString();    }    Hero.prototype.canDoDamage = function () {        return this.isConscious() && (this.readiedWeapon !== Weapon.NONE || this.droppedWeapon !== Weapon.NONE);    }    // And now return the constructor function    return Hero;});Weapon.jsdefine([./Die,./Logger],function (Die, Logger) {    use strict;    // Pattern from https://stackoverflow.com/a/10280735/1168342    // Start with the constructor    function Weapon(name, st, dice, modifier, isTwoHanded, isThrown, isPole) {        if (!(this instanceof Weapon)) {            throw new TypeError(Weapon constructor cannot be called as a function.);        }        this.name = name;        this.st = st;        this.dice = dice;        this.modifier = modifier;        this.isTwoHanded = isTwoHanded;        this.isThrown = isThrown;        this._isPole = isPole;    }    // Now add methods    Weapon.prototype.getName = function () {        return this.name;    };    Weapon.prototype.isTwoHanded = function () {        return this.isTwoHanded;    }    Weapon.prototype.isThrown = function () {        return this.isThrown;    }    Weapon.prototype.isPole = function () {        return this._isPole;    }    Weapon.prototype.doDamage = function () {            Logger.log(                    Rolling for weapon doing                             + this.dice                            + d                            + ((this.modifier > 0) ? + : )                            + ((this.modifier != 0) ? this.modifier : )                            +  damage.);        var damage = 0;        for (var i = 0; i < this.dice; i++)        {            damage += Die.roll();        }        damage += this.modifier;        if (this.modifier != 0) Logger.log(((this.modifier > 0) ? + : ) + this.modifier);        if (damage < 0) damage = 0;        Logger.log(Total weapon damage:  + damage);        return damage;    }    Weapon.prototype.toString = function () {        return this.name +  ( + this.dice + D + ((this.modifier > 0) ? + : ) + ((this.modifier != 0) ? this.modifier : ) + );    }    Weapon.NONE = new Weapon(None, 0, 0, 0, false, false, false);    Weapon.DAGGER = new Weapon(Dagger, 0, 1, -1, true, false, false);     Weapon.RAPIER = new Weapon(Rapier, 9, 1, 0, false, false, false);    Weapon.CLUB = new Weapon(Club, 9, 1, 0, true, false, false);    Weapon.HAMMER = new Weapon(Hammer, 10, 1, 1, true, false, false);    Weapon.CUTLASS = new Weapon(Cutlass, 10, 2, -2, false, false, false);    Weapon.SHORTSWORD = new Weapon(Shortsword, 11, 2, -1, false, false, false);    Weapon.MACE = new Weapon(Mace, 11, 2, -1, true, false, false);    Weapon.SMALL_AX = new Weapon(Small ax, 11, 1, 2, false, false, false);    Weapon.BROADSWORD = new Weapon(Broadsword, 12, 2, 0, false, false, false);    Weapon.MORNINGSTAR = new Weapon(Morningstar, 13, 2, 1, false, false, false);    Weapon.TWO_HANDED_SWORD = new Weapon(Two-handed sword, 14, 3, -1, false, true, false);    Weapon.BATTLEAXE = new Weapon(Battleaxe, 15, 3, 0, false, true, false);    // pole weapons    Weapon.JAVELIN = new Weapon(Javelin, 9, 1, -1, true, false, true);    Weapon.SPEAR = new Weapon(Spear, 11, 1, 1, true, true, true);    Weapon.HALBERD = new Weapon(Halberd, 13, 2, -1, false, true, true);    Weapon.PIKE_AXE = new Weapon(Pike axe, 15, 2, 2, false, true, true);    // And now return the constructor function    return Weapon;});
Combat simulator - single page JavaScript with requirejs from a Java Applet
javascript;modules;require.js