Skip to content

Scoring the skill (Part 11)

Peter Helcmanovsky (Ped) edited this page Jun 11, 2020 · 1 revision

Scoring the skill

When player manages to jump over snowballs, we will reward him with extra score for the skill.

Pretty simple concept right? But how to do that in machine code, what kind of arithmetic formula leads to such result?

And if the player jumps over two or three snowballs with single jumps, we want to reward him even more (more than just single reward for each snowball), to complicate the matter even further.

The old arcade game used as inspiration seems to handle these too by adding extra points, showing explicit values on screen near the player, so the concept is well tried and fun (just imagine playing the same game without the extra bonus displayed, I hope we can agree the version with bonus is better).

I have to admit that usually when I watch someone's else game in action, I can pretty quickly figure out some method to mimic it quite perfectly, often including the quirks (helping me to figure out the original algorithm used), but I'm not sure till this day how the original arcade calculates (detects) these. Being a loose inspiration, this can't stop us to add something similar to our SpecBong game, right?

Detecting-jump idea

We will use a way when the player plants invisible sprite slightly ahead in the movement direction when the jump starts. This invisible sprite will use the same code as player-vs-snowballs to evaluate possible collisions with snowballs. When the snowballs hits this "detector", instead of taking life of player they add extra value to the score (based on how many collisions this detector already received) and mark the particular snowball as already added to score. The player-vs-snowballs collision is calculated still in the same way as previously, dealing with player's death, independently from this scoring system (it is possible to even receive bonus points for jump even when you jump too late and die in the next frame hitting the snowball with player).

The idea behind this trick (?) is like exploring alternative future: "what if the player did not jump, how many snowballs would kill him?" By counting them and adding bonus points to the score you are rewarding the evasion skill of player (plus the system doesn't give bonus points for just climbing onto the ladder, which some different designs may do, if we would for example try to detect only snowballs having position under player. It's surely possible to fine-tune also some alternative system, but the invisible detector will use 95% of collision detection code, so we can focus development time on the graphical effect of bonus and score calculation, without work on one more detection scheme.

Cleaning up the code first

(here is a good spot to open the SpecBong.asm and try to match the source with this text)

(you can also check the total difference between "Part 11" and "Part 10")

First we will refactor SnowballvsPlayerCollision to be less hard-coded to handle only "player sprite" vs snowballs case and remove the "sparks" effect from snowballs hitting the player. There will remain only the palette-offset change of player sprite for testing purposes (so we can still see when player is hit by a snowball). So the routine will first reset any previous palette offset to give player regular colours when not hit.

Then all the math code calculating collision against snowballs is extracted to standalone routine SnowballvsSpriteCollision which takes as argument the main sprite to evaluate collision against and address of "collision handler" routine (PlayerVsBallCollisionHandler for player) which will execute upon each hit detected and deal with stuff like starting death sequence of player (later in final game, so far we will keep him invulnerable).

The separated routine will also return the number of hits in register C which is still stored in CollisionFxCount variable, although it is not used any more (after the "sparks" effect is removed from snowballs).

The new PlayerVsBallCollisionHandler will just increase the player sprite palette offset for every hit, so the player will become gradually more funky coloured with each snowball hitting him at the same frame (this is just debug measure to see the handler is being called as designed).

The "new" SnowballvsSpriteCollision routine is main body of original SnowballvsPlayerCollision, but with some modifications. First it does process the arguments provided by caller (self-modifying the call <collision_handler> instruction to point to the provided handler).

Then the code dealing with effect-sprites is deleted completely, instead of creating "FX" sparks the collision handler is called, leaving any effects onto the caller of the routine. Also the routine doesn't modify the player palette offset any more (the caller SnowballvsPlayerCollision with its new handler resolves that, it is not responsibility of the snowball-vs-sprite collision detection code any more).

There are few more minor changes in the code grouped in the first commit after "Part 10" source code, setting the Next speed back to 14MHz (it's more than enough for SpecBong), adjusting the initial position of the snowballs, making all snowballs roll down the stage (removing the last static one at bottom), removing call to the random score addition, and adjusting few comments and constants.

Implementing the jump detector

Instead of "FX sparks" sprites having reserved range after the player (to be shown above everything), we will reserve first 16 sprites for displaying the bonus effect - originally I was thinking about adding explicit "100", "200", ... sprites, but that would need work on sprites graphics and would need to redraw them every time the bonus values did change, so I instead drawn five-pointed yellow sprite in the spredit dot-command tool included in the NextZXOS distro. These stars will be emitted flying around to signal the bonus to the player, so I reserved 16 of them at the beginning of sprite range, to make them render below everything (least intrusive while playing the game).

There is also one more sprite-attribute structure reserved in memory outside of the 128 sprites used as HW-buffer-mirror under label JumpBonusDetection, this one will be not uploaded to FPGA, but it will be used to hold the coordinates of the detector and passed to the collision detection code. The "new life" init code will reset x coordinate to zero, which will be used as test if some jump detector is active during the game-loop processing.

The new routine JumpBonusLogic is called from game-loop after the player controls were processed. The routine will first go over the sprite array in the range reserved for the jump-bonus stars, and for each with "visible" bit set it will make it fly up at average of 0.5px per frame and modify it's mirror and rotate flags every fourth frame and turn the star invisible again after the mirror + rotate bits reach zero state again. This adds some kind of animation to the star turning it around a lot and also works as counter - there are three mirror+rotate bits (allowing for eight different bit combinations) being changed every fourth frame, so the life-cycle duration of single star is about 8 * 4 = 32 frames (0.64s in 50Hz mode).

Unfortunately "incrementing" the mirror + rotation flags bits doesn't naturally lead to some regular effect like "rotating the star clockwise", the transformations are "chaotic" if examined in slow motion, but luckily the player does see only the effect in full speed, where it looks good-enough, so we will stick with the simple "increment flags" code.

This animation/update of stars happens every frame independently whether the detector itself is currently deployed.

After this loop the detector JumpBonusDetection.x x-coordinate is checked, zero value just ending the routine, as no detector is currently deployed in the stage.

If the detector is deployed somewhere in the stage, the old familiar SnowballvsSpriteCollision routine is called with the JumpBonusDetection as sprite address (this is the outside-of-HW sprite storing the detector position) and JumpBonusCollisionHandler routine address to handle any detected collision. And because that is all to do in JumpBonusLogic routine, it will technically not call the collision detection, but instead jump into it with jr instruction. This will cause the ret instruction at end of SnowballvsSpriteCollision to return into main game-loop, doing the "return from routine" job also for JumpBonusLogic (saving about 15T of CPU time and showing the "tail call optimization" technique in practice).

How comes this is full logic of jump bonus? It is not, the other major part of it is in the collision handler JumpBonusCollisionHandler, which gets called when particular snowball hits the detector.

When jump detector collides

The JumpBonusCollisionHandler is called when particular snowball has collision with the invisible jump bonus detector.

The handler checks first if this particular snowball was already added to the bonus, this is done by checking byte array at address JumpBonusHitBy with register B used as index into array. The B is used by SnowballvsSpriteCollision as count-down counter going from SNOWBALLS_CNT down to 1 and that's what is used to index the array (note this is not C-like 0..SNOWBALLS_CNT-1 indexing, but 1..SNOWBALLS_CNT indexing, so the byte array must account for the access at [SNOWBALLS_CNT] position by reserving enough memory).

If the snowball was not added to bonus yet, it gets marked for next frame check as added (to skip this snowball next time).

Then the bonus score is added to the player score, doubling the previous bonus score or starting with score 1 (100 visually for player) if this is the first snowball for this particular jump. This means the scores added are 1, 2, 4, 8, ... resulting in total bonus being 1, 3, 7, 15, ... visually totalling for player as 100, 300, 700, 1500, ... bonus points. Depending on how many snowballs are "avoided" with single jump.

Finally the new "star" effect sprite will be set up to display, first the reserved area of sprites for the stars is scanned for free slot (if no slot is free, the handler returns without initializing new star, but under regular conditions 16 slots should not fill up).

If free slot is found, it is set to be visible with the five-point star sprite, zero mirror/rotate flags and same position as the jump detector itself has. While zeroing the mirror/rotate flags, the amount of score bonus is used as the palette offset, turning the star from bright yellow to more orange/bronze one, then toward to somewhat green. This is like super-cheap coloring of bonus stars to make them somewhat differ, but nothing extra polished or thought-through, I guess for higher aspiring game title you would use maybe some table of designed values or even distinct sprite patterns.

And that was all the jump-bonus code, right?

No. The jump detector has to be deployed upon jump, so few more pieces of code are needed.

In the Player1MoveByControls routine the code "landing at platform" has extra instruction to switch detector off.

Then the "start a new jump sequence" after player does press jump button has extra code to clear the JumpBonusScore value and JumpBonusHitBy array (to make all snowballs eligible for bonus points at start of new jump and start from value "100"). And few lines later when the jump direction is known, it is used to advance the current player position in that direction by four pixels, and used to set detector new coordinates.

And that's it, now every time player starts a new jump, the bonus-hit-by array is zeroed, jump-bonus score is zeroed, detector is placed into stage and activated and the rest is dealt with by game-loop calling JumpBonusLogic every frame - animating the visible "stars" sprites and detecting collisions of snowballs with the active jump-bonus-detector.

Now build the fresh NEX file, and try out how the detector works in action...

Jump over snowballs with score effect

Are we ready to turn this finally into a game? If you will notice my "TODO plan" in the assembly file from "Part 11" commit, there are only very few things left, adding code hurling new snowballs at player and dealing with player finishing stage or losing life/all lives. Feels like another 10-30min job and we are done...