Deep Tech Point
first stop in your tech adventure

How to make simple Battle Simulator demo in JavaScript

May 23, 2021 | Javascript

In this tutorial, we are going to create a relatively simple battle simulator project with n <= 5 battle units to fight between each other. The aim of the project is to achieve a complete and working JavaScript code. With this code, we will cover a lot of JavaScript language and coding concepts, such as object literal vs object constructor notation, functions vs. arrow functions – what’s the difference, and when and why to use each. We will also take a look at the concept of randomization and how Math.random() and Math.floor() methods can work in our favor. This will hopefully help you understand these concepts a bit better than reading about them individually. Let’s get started.

Battle Simulator specification

The code can be run in any modern browser but for the best performance should be run on the NodeJS server. It does not depend on any external library.
A battle unit has the following properties:

For all those pacifists out there, rechargeTime is the time the battle unit – a tank or whatever you imagine under the name battle unit – needs to prepare for another shooting. The unit can attack only when its rechargeTime is filled up. When rechargeTime is up, the unit will attack any other random unit that is still alive.
The running behavior is asynchronous and is controlled by an interval function. Every unit will attack once it is recharged, and every unit can be attacked while it is still recharging. When a unit dies the program will remove the dead unit from the global static object of running units.
When a unit attacks, it only inflicts damage on the attacked unit, it does not receive any damage from the unit that was attacked.

Critical chance

The critical chance increases the risk chance, it’s the one that rises the randomness, therefore rockets the excitement and unpredictability of who is going to win. If criticalChance is bigger or equal(>=) to a randomly generated number between 0 and 100, the damage becomes critical damage, and it will be applied once. The critical damage is multiplied by N and is defined as a global static variable, between 1 and 5.
The battle lasts until at least one unit is alive. The battle progress log is displayed in the console.

Object literal or object constructor notation

The battle unit has properties (and functions/ methods). Therefore, in JavaScript this battle unit with properties should be represented as an object. Thankfully, JavaScript is a very flexible language – you can achieve the same result even if you choose a different approach, in this case, either with object literal or construction function. However, you should be aware that specific approaches fit better than others in specific requirements. You can read more about the differences between object literal or object constructor notation and when to use each in this article.

One way to approach solving this problem would be to create a battle unit as an object literal and then make other units with Object.create(). We are using object properties that are referencing other properties, therefore, it not straightforward to use object constructor notation. Besides, with object literal notation we would have to resort to getters to compute properties. Moreover, with object literal notation you would need to ‘override’ properties values, and that process is more inconvenient than just passing value to the constructor. In addition, with encapsulation we will apply private and public members, and it is easier to implement with the constructor function. So, for the reasons mentioned in this paragraph, the first choice for the creation of battle unit objects is object constructor notation.

// object literal notation with getter
var BattleUnitLiteral1 = {
    name: 'Unit1',
    health: 100,
    get rechargeTime() {
        return 1000 * this.health / 100;
    }
}

var BattleUnitLiteral2 = Object.create(
    BattleUnitLiteral1, {
        name: { value: 'Unit2' }
    }
);

// object constructor notation
function BattleUnitConstructor(name) {
    this.name = name;
    this.health = 100;
    this.rechargeTime = 1000 * this.health / 100;
}

BattleUnit1 = BattleUnitConstructor('Unit1');
BattleUnit2 = BattleUnitConstructor('Unit2');

Normal functions or arrow functions

Next, we will need a few functions/ methods with our battle unit objects, for example, to calculate damage since it is dependent on the unit’s current health level. We can use the classic or regular notation for functions or newer arrow notation. Once you get used to arrow notation it is a way to go. It is shorter and doesn’t change the value of “this” inside callback function meaning it doesn’t define its own execution context. You can read more about the differences between normal functions and arrow functions and when to use each here.

// regular function notation
function sum (a, b) {
    return a + b;
}

// arrow version of the same function
const sum = (a, b) => a + b;

Randomization of simulation

Randomization of simulation increases the unpredictability of who among battle units is going to win. There’s also a requirement for a random number on two occasions in the code. With the first one, we need to generate a random number between 0 and 100, and with the second one, we need to choose a random battle unit to attack from the “pool” of units. To achieve this, we are going to use Math.random() and Math.floor() methods.

// let's generate a number between 0 and 100
var rndNum1 = Math.floor(Math.random() * 101);

// generate number between 1 and 5
var rndNum2 = Math.ceil(Math.random() * 5);

// get random array member
var arr = ["apple", "orange", "banana"];
var rndMember = arr[Math.random() * arr.length];

The “pool” of battle units is actually an object so we have to randomly choose an object property which is a battle unit and we need to make sure not to attack ourselves. First, we are going to extract properties’ keys into an array. Then we will filter out ourselves from an array, and finally, we will choose a key (battle unit to attack).

// get properties' keys out of object
var objects = { 'u1': 'something', 'u2': 'something else', 'u3': 'another something' };
var keys = Object.keys(objects); // returns ['u1', 'u2', 'u3']

// let's filter out u2
keys = keys.filter(k => k !== 'u2');
// or with normal function if arrow functions confuse you
keys = keys.filter(function (k) {
    return k !== 'u2';
});

JavaScript modus operandi

Execution of JavaScript application is synchronous and single-threaded. This means the code will run line after line as it’s written after hoisting in one thread. Function and var declarations are moved to the top of their scope (hoisted). To artificially make our simulator application run asynchronous we will put recharging and attack logic into an interval callback function (setInterval) so every battle unit has equal processing time to recharge and attack. In essence, when you read the word “callback” this means part of the code that runs asynchronously. It is called back at some later time, usually when some event occurs. Keep in mind that interval callback functions that run too frequently (in our code every 1ms) would slow down your browser. Running the same code in NodeJS is a bit more tolerant to short interval callbacks.

We also made the “attacked” method to provide the ability for battle units to reduce each others’ health, so we don’t mess directly with health values.

Finally, when a battle unit loses all health we will consider it dead and remove interval function as well as the unit’s instance from the “pool” of units.

You can check the full code on https://github.com/zoxxx/Battle-Simulator.