Dec 2017 (updated 26 Jan 2018)

Let's code a battle system

I needed one for my game, so looked around the web for existing free scripts - you know, the amazing amount of free code people share? - but surprisingly, found very little. Only one on Github, but I couldn't get it working. So I gave up and tried creating one from scratch. It may not be perfect, but maybe someone will find it useful.

Disclaimer: this tutorial is written for absolute beginners, but I'm also just a hobbyist myself. So apologies for the cringy bits, experts, and if you see any parts that could be explained or coded better, please let me know!

For users of Mysidia framework: please see this guide for integration into your game. Many more fun things can be done with PHP, involving pet stats, items and custom abilities.

Setting the scene

Firstly, optional, but worth it: set up a clear battlefield layout. You can copy this HTML/CSS template if you want.

We use variables for generating and holding data - stat numbers, attack modifiers, random enemy names or quotes, anything. Any of these variables can be kept 'behind the scenes', or made public, with player seeing them update in real time. Doing that with each side's HP is essential, for example. There will be moves later that change stats, so let's do it with them too. For a variable to visibly update we must create a unique div to put it in. 'hpdiv', 'strengthdiv' and so on.

We place all JavaScript code within SCRIPT tags. First let's declare (create) some variables. There are two kinds of 'scope' you can use variables in: global or local.
Global variables are declared outside any function, you can use (or change) them inside any functions, and they'll update outside the function too. We definitely need these during a battle, to keep track of stats, from start to finish!
Local variables are ones you create inside a certain function, and they only exist inside that function. Basically they're temporary, they vanish after the function ends. We can use these for little random numbers to add flavour to the main attack formulae, for example. They'll be re-generated every time and are not so important that we need to hold onto them afterwards.

A couple of good, basic guides: Variables, Scope

Sometimes you'll see the word var before a variable name. It doesn't matter if you add 'var' when creating global variables, but it makes a difference to variables created inside functions. If no 'var' they'll become global! I like to keep local stuff local, it feels tidy, so you'll notice a lot of 'var' around.
Try to be careful with variable names. Don't accidentally re-declare a global one locally (such as writing var ghp), that might overwrite it and cause Bad Stuff (never tried it so can't elaborate). You can create many local variables with the same names inside different functions though. They can't 'see' the local vars inside each other.
<script>

ghp = 180;
ehp = rand between 120,240;
gstrength = sth;
gspeed = sth;
gfocus = sth;
estrength = rand;
espeed = sth;
efocus = sth;

</script>
So, those are our global stat-vars. Some are fixed numbers/text, some are numbers randomly generated within a range, and one is a text string chosen from an array, to add variety to each battle. This generation only happens once, when page first loads.

Using those stats

Anyway, how do things visibly update on the page? We can use jQuery to change HTML elements in all sorts of ways. Hide/show them, animate, change their CSS, replace or add to their content... let's look at 4 snippets:
$('#enemyhealth').html('Health: 0');
First select the element to be changed - in this case the div called 'enemyhealth' where their HP number shows - and replace its HTML with that bit of text. Normally you'd put a variable where the 0 is; see below.
$('<br>Boss cast a healing spell on himself, recovering ' +heal+ ' HP!<br>').appendTo('#reports');
Again the message to add, but this time it's written first, and appends to the div called 'reports'. If you want each round's message to begin on a new line or paragraph, wrap the message in BR or P tags. Notice the +heal+ part in between the quoted bits? That's how you include a JS variable.
$('#victory').show();
At some points in a battle - most obviously at the end - you may want new things to appear on the page. In this example, a div called 'victory' which contains a congratulations message (and on my PHP game, a link to a prize-generating page). Its CSS is set to 'display:none' at first.
$('#actionbox').hide();
And this is the opposite, making an element disappear. In this case, the div 'actionbox' which contains the action buttons. The battle has ended so you don't want the user to keep doing things. Alternatively you could use this to 'disable' certain actions, for example the user has already used X attack enough times, has lost too much strength, or is under a status effect.

I'm not using any other CSS-changing stuff in this system, so let's move on. Code!

<script>

// global vars
// up here!

$(function () {

// action function here { ... }

// another function here {
...
}

// and another {...}

}
</script>
This is the overall structure for the battle script. The bolded line is just setting up, and remember the closing bracket at the end. All functions need own opening and closing { } - forgetting one is a great way to break everything, so pay attention! - but notice how they can be all on a line, or spread out. JavaScript doesn't care where they are, as long as they're in order.
Now, an attack!
$('#Bite').on('click', function () {
We select a button with the ID 'Bite' and when we click it, we want to begin the following function:
var rand1 = Math.floor(Math.random() * 15) + 1;
var rand2 = Math.floor(Math.random() * 15) + 1;
var griffbite = gstrength + gspeed + rand1;
var enemybite = estrength + espeed + rand2;
Attacks would be boring if they always used the same fixed stat numbers, so let's generate 2 random numbers - rand1, rand2 - between 1 and 15, and add them to each side's attack. Bite's formula is very simple: strength + speed.
Note the var when creating these temporary variables.
ghp = ghp - enemybite;
ehp = ehp - griffbite;
Update both sides' HP variables to take away points from those attacks.
$('#enemyhealth').html('Health: <b>' +ehp+ '</b>');
$('#griffhealth').html('Health: <b>' +ghp+ '</b>');
$('<br>{$gname} sharply bites for ' +griffbite+ ' damage! Enemy hits back for ' +enemybite+ ' damage!<br>').appendTo('#reports');
Now let's display those changes. Update the two divs that show HP, and append the message to 'reports'.
Important note: be careful with long messages! Multi-line stuff will probably break. Keep it short for now. I'll expand on this issue later.
var latest = document.getElementById('reports');
latest.scrollTop = latest.scrollHeight;
This bit is optional but highly recommended. It automatically scrolls to the bottom of the 'reports' div, so you can see latest message.
if (ehp <= 0 && ghp > 0){

$('#enemyhealth').html('Health: <b>0</b>');
$('#actionbox').html('You defeated ' +ename+ '! Congratulations!');
$('#victory').show();

}
if (ghp <= 0 && ehp <= 0){

$('#griffhp').html('Health: <b>0</b>');
$('#enemyhealth').html('Health: <b>0</b>');
$('#actionbox').html('The battle was a draw!');
$('#drawn').show();

}
if (ghp <= 0 && ehp > 0){

$('#griffhp').html('Health: <b>0</b>');
$('#actionbox').html('Unfortunately you lost the battle.');
$('#lost').show();

}
}
Finish with 3 checks: if you still have HP but enemy doesn't, if both have none, or if you don't but they do. If any are true, update the relevant divs to show 0 (to hide ugly negative numbers), replace the action buttons with a message, and display the final div with prize info, link to next page or whatever. If none of these conditions apply, nothing happens and the battle continues to next round.
Finally, end this whole attack function with another bracket.

Just one attack isn't much of an option! So let's add another, called Kick, using a different formula. Here's the whole function:

$('#Kick').on('click', function () {

var rand1 = Math.floor(Math.random() * 70) + 1;
var rand2 = Math.floor(Math.random() * 70) + 1;
var griffkick = gstrength + gfocus + rand1;
var ekick = estrength + efocus + rand2;
ehp = ehp - griffkick;
ghp = ghp - ekick;
$('<p>{$gname} kicks, dealing ' +griffkick+ ' damage! Enemy strikes back for ' +ekick+ ' damage!</p>').appendTo('#reports');
var latest = document.getElementById('reports');
latest.scrollTop = latest.scrollHeight;

if (ehp <= 0 && ghp > 0){

$('#enemyhealth').html('Health: <b>0</b>');
$('#actionbox').html('You defeated ' +ename+ '! Congratulations!');
$('#victory').show();

}
if (ghp <= 0 && ehp <= 0){

$('#griffhp').html('Health: <b>0</b>');
$('#enemyhealth').html('Health: <b>0</b>');
$('#actionbox').html('The battle was a draw!');
$('#drawn').show();

}
if (ghp <= 0 && ehp > 0){

$('#griffhp').html('Health: <b>0</b>');
$('#actionbox').html('Unfortunately you lost the battle.');
$('#lost').show();
}

});
Pretty much the same as Bite but with less predictable numbers.

Now we have two damaging attacks, but there's still not much strategy. Let's add a move that halves our HP in order to double two other stats:
$('#Risk').on('click', function () {

var rand1 = Math.floor(Math.random() * 10) + 1;
var rand2 = Math.floor(Math.random() * 25) + 1;
var gbuff = 30 + rand1;
var ebuff = {$ebuff} + rand2;
ghp = (ghp - gbuff) - ebuff;
gfocus = gfocus * 2;
gspeed = gspeed * 2;
$('<p>{$gname} loses ' +gbuff+ ' HP to double STR, SPD! Meanwhile, enemy hits for ' +ebuff+ ' damage!</p>').appendTo('#reports');
var latest = document.getElementById('reports');
latest.scrollTop = latest.scrollHeight;

if (ehp <= 0 && ghp > 0){

$('#enemyhealth').html('Health: <b>0</b>');
$('#actionbox').html('You defeated ' +ename+ '! Congratulations!');
$('#victory').show();

}
if (ghp <= 0 && ehp <= 0){

$('#griffhp').html('Health: <b>0</b>');
$('#enemyhealth').html('Health: <b>0</b>');
$('#actionbox').html('The battle was a draw!');
$('#drawn').show();

}
if (ghp <= 0 && ehp > 0){

$('#griffhp').html('Health: <b>0</b>');
$('#actionbox').html('Unfortunately you lost the battle.');
$('#lost').show();
}

});
We have a very simple but functioning battlefield! If everything's gone correctly, you should have something like this:
Demo 1
..
menu {text-alit:350px;over
kkloo iii