JavaScript Broughlike Tutorial Previously: Monsters

Stage 4 - Monsters Part 2

Since the monsters will be attacking the player and vice versa, let's first draw the HP for each.
monster.js
Monster HP now displays nicely.

The idea here is to draw one HP pip sprite for each unit of HP the monster has. We can't just draw each pip in the same spot; you would only see the top one in that case. We've got a bit of funky math to layout all the pips:

The result means pips are drawn first left to right offset by 5 pixels each and then stacked vertically offset by 5 pixels for each row.

Attacking

Now onto attacking. We can let the monsters attack the player and vice versa with a small addition to
tryMove
.
monster.js
We're comparing
isPlayer
flags to make sure monsters don't attack each other.

When an attack is successful, that triggers
hit
, applying damage to the target monster's HP and if they run out of HP, they
die
. When dying, the monster sprite is set to index
1
(our player corpse). This will only apply to the player since we earlier wrote code to delete monsters as soon as they are
dead
.

Cool beans! Attacking is in the game. If you let the monsters kill you, you'll notice that you're still able to move around the map as a corpse. We'll tackle that later.

Monsters are working as expected, but with identical behavior. Here's the plan for making each one unique:

Snake

Since
Bird
is already done, let's start with
Snake
. Make sure to test out each monster after updating their code. While testing this code, it may be easier to temporarily modify the
spawnMonster
code to only generate a specific kind of monster (left as an exercise for the reader). Or you can just refresh a bunch of times.
monster.js
Rather simple. The Snake can move twice, move and attack, but not attack twice (that's overpowered!). We need one tie-in within tryMove to set
attackedThisTurn
to true upon attacking.
monster.js

Tank

While working on the Tank, we'll introduce a
stunned
flag. When a monster is stunned, they'll be unable to react until the next turn.

We'll be able to use this flag in multiple ways: to stun monsters whenever they are hit by the player or hit by certain spells and to pause the action of monsters like the Tank.
monster.js
When monsters are attacked, they get
stunned
, making it easier for the player to take on tough monsters.
monster.js
If the
stunned
flag is true, we reset it to false and do a
return
which exits the function and prevents the monster from doing anything until next turn.
monster.js
Here, the
Tank
monster stuns itself if it wasn't already
stunned
at the beginning of the turn. Effectively, this results in action only every other turn.

Eater

Then comes the
Eater
. Before doing normal monster behavior, this guy is going to check for any nearby walls and eat them for health! Each wall will grant half a health point (our
drawHp
method only draws whole points though).
monster.js
First, we need to get all the nearby walls using
getAdjacentNeighbors
and only include tiles that are not
passable
(indicating a wall) and are also
inBounds
(so the outer wall doesn't get destroyed).

If walls are found, we're going to call two new methods. The
replace
method is replacing a
Wall
tile with a
Floor
tile. The
heal
method adds half a hitpoint to the monster. If no walls are found, we'll simply do the normal monster behavior.

Now let's implement those methods.
monster.js
This method
heal
is a one-liner. Add some amount of healing "damage" without going over some global
maxHp
, which we'll need to define next. We don't want our monsters to gain infinite health!
index.html
Next up is
replace
.
tile.js
You can use
replace
any time one tile type changes into another type. Here it's a wall replacing a floor, but imagine if a water tile replaced a floor!

One thing that's not coded here is to copy over monsters and items present on the old tile to the new tile. Keep that in mind for future additions.

Jester

The last monster is the
Jester
and it's able to move randomly simply by trying to move to the first neighbor returned by the (pre-shuffled)
getAdjacentPassableNeighbors
.
tile.js
With those enemy behaviors in place, our little broughlike is starting to feel like... a game. 😍 In the next section, we'll turn this thing into a proper game with a title screen, multiple levels, and victory and failure conditions.