S01E07 – Environmental Concerns

Welcome to episode seven of our Let’s Remake Manic Miner series. In a shorter episode for today, we are going to get back into our main source file and take a look at some environmental concerns. That is, we are going to turn our attention to the environment in which we are going to place Miner Willy.

Before We Begin

In our last episode, we created a simple program that used our Manic Miner sprite bank and saved a representation of the Central Cavern level as a Degas format screen. We will need to load that image into our game in order to make Miner Willy feel at home. One of the things that we need to remember is that we are creating a game engine, so I think we should start adding some elements to respect that. We’ll need to make some adjustments to our main game loop first. In fact, we need to build an entire new loop so that we can control the loading of level information and set up each level before the main game loop starts. We’ll also set up some placeholder subroutines to handle things like the main menu.

The above shows what our main game loop looked like previously. What we are going to do is change that drastically so that we have some game logic executed. Remove the code and replace it with the following:

gosub @LoadAssets
gosub @MainMenu
end

Let’s add a new subroutine to deal with the main menu. We’re not going to do anything here for the time being other than create this as a spaceholder that then calls our main game loop.

@MainMenu
    QUIT = false
    repeat
        gosub @NewGame
    until QUIT
    return
CommandDescription
QUIT = falseHere we are defining a new boolean variable called QUIT. We are setting it’s initial state to be false and will use this variable to determine whether to quit the main menu. Quitting the main menu will exit the loop and end the program.
REPEAT
GOSUB @NewGame
UNTIL QUIT
As we’re not actually coding a menu at the moment, we’ll simply call a new subroutine and start a new game. Ultimately we will do “stuff” in this menu loop and only call @NewGame when the option to start a new game has been selected by the player.

Notice how we don’t do UNTIL (QUIT = true)? That’s because (if you remember earlier episodes) everything evaluates to true or false. By specifying QUIT = true, we would effectively be saying UNTIL (true = true) or UNTIL (false = true). We replace this check with the QUIT variable instead, which gives us UNTIL true or UNTIL false. When Quit is false, then the statement does not equate to true, and the loop is repeated.
RETURNAs we know, it returns us to the line of code that called the GOSUB instruction.

We now need to create the @NewGame subroutine to deal with the main game play. This is where we will see more changes as we prepare our game engine for various game play elements such as finishing a level, Miner Willy being killed by falling too far, or hitting an obstacle or baddie.

@NewGame
    LVL = 1 : LIVES = 3 : GAMEOVER = false

    // Level loop
    repeat
        gosub @InitMinerWilly
        LVLDONE = false : MWDEAD = false

        // Gameplay loop
        repeat
            gosub @ReadJoystick
            gosub @DisplayMinerWilly
            gosub @WaitVBL
        until LVLDONE or MWDEAD

        if MWDEAD then dec LIVES : GAMEOVER = (LIVES = 0)
        if LVLDONE then inc LVL
    until GAMEOVER
    return
InstructionDescription
LVL = 1We introduce a new variable, LVL, to hold the current level that we are playing. Because this is a newly initialised game, we set the level to 1.

We could employ a cheat at some point too that allows us to start at a different level number. That would come in handy when testing further level structures.
LIVES = 3We introduce the LIVES variable to hold the number of lives that we have left.

We start with three lives, and each time Miner Willy dies, we decrease the number of lives we have.
GAMEOVER = falseOur final new variable holds whether it’s game over or not.

This works hand in hand with the LIVES variable, and when that reaches zero, we set GAMEOVER to true.
REPEATNow that we have initialised our new variables, we enter what I call the level loop. It is within this loop that we prepare the environment, load the level graphics, and reset Miner Willy to his starting positions.
GOSUB @InitMinerWillyThis is our call to the @InitMinerWilly subroutine that we previously had earlier in the code. We’ve moved it within this loop to make sure Miner Willy is initialised each time a level starts. We can do this here as one of the features of Manic Miner is that when Miner Willy dies, the whole level is reset back to the beginning.
LVLDONE = falseWe introduce the LVLDONE variable. This is used to determine if the level is done or not.

Initially, the level is not done because we need to complete it.
MWDEAD = falseThis variable holds whether Miner Willy is dead. It will be set when we collide with something that we shouldn’t or fall too far.
REPEAT
GOSUB @ReadJoystick
GOSUB @DisplayMinerWilly
GOSUB @WaitVBL
UNTIL LVLDONE OR MWDEAD
This is the same game loop that we had before; we’ve just moved it’s location.

We’ve also changed the UNTIL false to be UNTIL LVLDONE OR MWDEAD, which allows us to set either of these variables to break out of the game loop.
IF MWDEAD THEN DEC LIVES : GAMEOVER = (LIVES = 0)Once we are outside the game loop, we then check the MWDEAD variable to see if the reason we dropped out of the loop was because Miner Willy died. If so, we use the DEC instruction to decrease the value of the LIVES variable, and then we set the GAMEOVER variable by evaluating whether LIVES = 0.

Remembering that all expressions evaluate to true or false, GAMEOVER = (LIVES = 0) is the same as saying IF LIVES = 0 THEN GAMEOVER = true. We have therefore achieved the same result without having to perform additional costly calculations.
IF LVLDONE THEN INC LVLIf we have quit the game loop because the level has been completed, we increase LVL so that next time the game loop triggers, we will be on the next level.
UNTIL GAMEOVERKeep executing the repeat loop until the GAMEOVER flag has been set to true.

You can give this code a run, but it will just look the same as it did before. Miner Willy will still be able to walk and jump, but at least we have everything safely wrapped up in our menu, level, and game play loops now, with the appropriate breaks defined and used.

The Level Graphic

We’ve got our nice graphic for our level, so let’s add that to the equation and implement the code that will load the image and display it in our level loop before the main game loop starts. To do this, we are going to employ the use of a STOS memory bank. In STOS, screen memory banks come in two different flavours: a screen or a datascreen.

Screens are temporary memory banks that are predefined at the correct size for holding an ST screen, no matter what the resolution. When the program has finished running, the memory used for the screen is released, and the memory bank is deleted. This means you have to always reserve the memory bank and load your assets into it during the operation of your program.

Datascreens are the same as screen banks; however, they are permanent, and when the program finishes executing, the memory bank remains in memory and is available to be used again. You can load assets into a datascreen from the STOS command line rather than as code within your program.

We are going to use a screen because we want to use as little memory as possible, and we will load our level images as we require them. Add the following line of code at the beginning of the program.

key off : curs off : click off : flash off : mode 0 : hide on : auto back off : anim off : synchro off
palette $000,$777,$005,$007,$500,$700,$504,$707,$050,$070,$055,$077,$550,$770,$555,$777
erase 10 : reserve as screen 10
InstructionDescription
ERASE 10Here we are erasing any databank that we may already have defined within bank 10 of STOS.

This call is actually redundant at this point as the program has only just executed, and in theory there should be no memory banks defined at all. However, I’m always in the habit of erasing memory banks, and it really makes no difference; it just makes me feel better.
RESERVE AS SCREEN 10This is the instruction that signals to STOS that we want to create a temporary memory bank to hold ST screen data.

We’ve specified that we want this bank to be allocated to the memory bank 10 slot.

Once the above has been executed, we now have enough memory space allocated to load our level. We do this in our level loop by adding the following code:

repeat
        load "LEVEL01.PI1",10
        screen copy 10 to physic : screen copy 10 to back

        gosub @InitMinerWilly
        LVLDONE = false : MWDEAD = false
InstructionDescription
LOAD “LEVEL01.PI1”,10The LOAD command does just that; it loads our named asset “LEVEL01.PI1” and places it into memory bank 10. Because we are using known bank numbers and known file formats, STOS will handle all of this for us.
SCREEN COPY 10 TO physicThe SCREEN COPY instruction in STOS has many different forms. Here we are using quite possibly the simplest variation that copies an entire screen of data from one memory address to the other (bank 10 to the physical screen).
SCREEN COPY 10 TO backThe same as above, but this time we are copying the image to the background screen.

We do this because STOS automatically saves the background, and if we don’t, when Miner Willy walks over the landscape, it will be blanked out. Try it and see what happens.

You can run that now and see what the results are. We should have an environment that doesn’t get overwritten when Miner Willy moves around it.

Finally, let’s make the level loading a little more dynamic. Rather than having the hard-coded “LEVEL01.PI1” image load, let’s calculate the filename based on the LVL variable. This could be done in a number of ways, but we’ll just do it in a simple way that shows some of the string manipulation instructions in STOS.

Update the LOAD “LEVEL01.PI1” line of code to the following:

LVL$ = str$(LVL) - " " : if len(LVL$) = 1 then LVL$ = "0" + LVL$
load "LEVEL" + LVL$ + ".PI1",10
InstructionDescription
LVL$ = STR$(LVL) – ” “The STR$ instruction converts a number into a text string. After this call, LVL$ holds the current level as a text string.

One thing to note, though, is that STOS always adds a leading space to any number that it converts to a text string. Therefore, with our LVL set to 1, the resulting string is ” 1″.

We can then simply subtract spaces from the string by using – ” “.

Nice!
IF LEN(LVL$) = 1 THEN LVL$ = “0” + LVL$Here we are just checking if the resulting string is only 1 character in length.

If so, we are adding a 0 to the front of it to pad anything less than 10 to be 01, 02, 03, etc.
LOAD “LEVEL” + LVL$ + “.PI1”,10Finally, we construct the full filename by adding all the elements together and load the image into screen bank 10.

Ok, that’s all we’ve got time for today folks – I said it was going to be a shorter one! As always, the source files can be found in the GitHub repository.

In our next episode, we will start to look at how to add some basic collision detection so Willy can be stopped from walking off the screen and falling into oblivion. Until then… Happy STOSing.

         

About author View all posts Author website

Neil Halliday

Neil started coding in STOS in 1989 just after it was released in the UK.

During those 31 years he has written numerous demo screens, routines, games and extensions, most of which are now lost due to a massive hard disk crash. What remains on floppy disk is still being discovered and posted on the STOS Coders website and stored in the cloud for everybody to enjoy (or laugh at).

Neil is the author of the GBP Extension which added some pretty cool commands to STOS, along with the "Development" extension that enabled enhanced STE functionality, including probably one of the simplest hardware scrolling routines around.

Along with Bruno Azzara, Geoff Harrison and Mike Halliday we had loads of fun back in the day trying to push STOS to it's limits. We are all now enjoying bringing our knowledge to a new generation of STOS Coders.

Leave a Reply

Your email address will not be published. Required fields are marked *