NanoGames: ZAX254b ShootEmUp in 254 Bytes on a c64 – Deluxe version with 384 Bytes(SizeCoding)

See also: SizeCoding/Nanogames as a method of science: https://research.swissdigitization.ch/?p=2819

What is a ShootEmUp? This question alone answers the outcome of a nanogame development. But perhaps this will only crystallize during nanogame development and the definition will have to be changed several times to make it appear feasible.

The ShootEmup – the tasks

As we all know, there are a large number of different ShootEmUps. The simplest variant remains the subgroup of Space Invaders and later Galagas. Here the management/game mechanics are mostly based on the management of sprites independently of a background (or interactions with the background, see Xevious etc.) This means that only one type of object has to be managed (see games such as Breakout).

The most important interactions/queries are collisions. There are two different types of objects.


Own/Avatar objects:

Your own objects are divided into the avatar and the shot(s).
Avatar controls: Left and right of the avatar. Limitation of left and right.
Shooting: This interaction was also replaced by autofire in the previous case (idea from a nanogame developer). Less code
Shoot: Moves up.



Enemy objects:
The classic opponents react to the shot (destruction) and the shots (are not destroyed by the shots). To simplify matters, the enemy shots have been deleted in this case (additional cases: shot does not hit shot).
Enemy objects fall downwards. Move neutrally or tend towards the avatar.



Fail condition:

The opponents come behind the avatar and/or hit the avatar. In this case, however, the second implementation was deactivated again because the game was too difficult.

A concept similar to the one presented here was also used for a 256-byte nanogame for PICO8 for the LoveByte competition 2024.

Look also at TaipDefendor_256b (256 Bytes) and here the deluxe variant ION382b (386 Bytes in pico8)

Classic implementation: separate/encapsulated code – impossible

Even with this adaptation, a classic implementation is not possible. It simply requires far too much code.

A classic implementation would be: Separation of all important parts into individual sections or functions.

At least the following events:

_Init game field (background)
_Init game objects
_In the loop
__Handling of the individual objects – Behavior
__Collision of objects (all collide with each other or with the avatar) – Score handling
__Drawing the objects
__Abort condition (GameOver)
_Score display The only option is further simplification and radical integration. This means double or triple use of existing structures.

Selection of the game medium (hardware)

Displaying in particular is a major problem in ShootEmUp, as the objects/sprites move freely – at least by default in this genre. This is a major problem, especially on older platforms, as bytes have to be shifted, the background has to be restored after use, etc. Older systems also lack the option of simply deleting everything and redrawing it again (see Pico8).

The only thing that usually helps here is to select a system that comes with the appropriate hardware (hardware sprites) and has low initial costs.

The Atari ST (no hardware simplifications at all)/Amiga (init too complicated) are additionally handicapped in that all commands consume an average of 4 bytes, leaving 64 commands for a 256 byte nanogame. The tests to try it were not very successful. Precisely because there are no default graphics (character set) that can be used. There is only one possibility left. Using the existing graphics of the start screen and working with the blitter from here. Initial experiments were promising.

Nevertheless, the promising horse here was an 8-bit computer with sprites (an 8-bit console such as Intellivision would of course also be possible): C64.

Hardware – a Challenge

The C64 is an 8-bit processor and requires an average of 2 bytes per command. One for the command and one byte for the argument.

So you have to get by with 128 commands for a game, in this case a ShootEmUp. Of course, the hardware equipment with its Petscii font/charset also helps

Two Games – 254 bytes und „Deluxe“ 384 Bytes Versions

In this case, two versions were created: a 254-byte version and an expanded version with 384 bytes. This also allows you to compare what you can do with 130 bytes more. However, the basic mechanics and programming have remained largely the same. The deluxe version is discussed at the end.

ZAX254 – 254 Bytes

The game in action

The game is a simple shooter. The white avatar can move left and right and fires a shot continuously. If an opponent is hit (C64 hardware collision detection), the shot moves back to the beginning of the screen. At the same time, a dot is printed in the background (a score-display is difficult to achieve in less than 256 bytes – see Deluxe version). It (enemey) continues to move downwards and with the counter to the right until it crosses a threshold and then flies towards the avatar.
If an opponent cannot be stopped and gets behind the avatar’s position, it is GameOver. The game than restarts and writes down many characters again. It is the gameover. And now you can score again. However, the previous game remains visible and the current score is comparable. The high score is therefore in the background. An interesting side effect is that this gives the game a background. The background is deliberately not graphically colorful (there are variations that are) so that the game remains playable.

GameObject/Sprite management: Sprites – the data structure

Sprites are used as the data structure. These have predefined and used memories. All management and movement is done directly here. The coordinates are changed and updated immediately. Sprites are also cpu cost-neutral, they are drawn last on the screen and leave nothing in the memory that needs to be cleaned up.

The division of the sprites is static and looks like this:

This makes it easy to distinguish between your own and your opponents (>1).

The coordinates are stored in the memory one after the other from address $d000 (always first the X and then the Y coordinate.

Code „explained“

All variables are not contained in the program but are fixed addresses in free memory.

// VARS (direct in memory)

.var tmpx = $3000
//tmpx: .byte 0 // x position of avatar
.var tmpy = tmpx +1 
//tmpy: .byte 0 // x position of avatar

.var tmp = tmpx +2
// tmp: .byte 0

.var col = tmpx +3
// col: .byte 0 

.var ind = tmpx +4
// ind: .byte 0

.var counter = tmpx +5 
// counter: .byte 0

Flow

The code follows the following sequence

First, the most important things are initialized:

_ The screen is cleared with a BasicRom function
_ The colors are set to black

The code for this:

start:  
    // --------------------------
    // clear screen
    // --------------------------
    jsr $E544 

restart:
start_all:
        // ----------------------
        // back to black
        // ----------------------
        lda #0 // color black
        sta $D020 // screen border register
        sta $D021 // screen border register
        

The code follows the following sequence

In a first Loop/ForNext, the following tasks are processed in a loop y 0 > 201:

_Init of the sprites (8) at the top edge (sta $d000,y)
_Set the address of the graphic data of the sprites (sta $07f8,y)

_Write stripe pattern to the memory of the graphic data (sta $2000,y)
_Print the intermediate characters (jsr print)

At the end, all sprites are activated (lda #%11111111 / sta $d015)
Avatar gets a new Y-coordinate (sta $d001)

        ldy #0 // counter
       
    sprites:


        lda #$80 // sprite data here
        sta $07f8,y // store +1 memory block 

        lda #57
        sta $2000,y 

        // store for
        cpy #16
        bcs nothere
            sta $d000,y
        nothere:     
        
        lda #44// #-34 // #44 // #47  // -58 // 44 // #45 // 252
        jsr print
        
        iny        
        cpy #201
        bne sprites

    // enable sprite 0
    lda #%11111111
    sta $d015 // all eight sprites on (all bits set)

     // ship only        
     lda #shipy
     sta $d001 // use x value ...

All sprites are then activated.

The ship is the only sprite to be moved downwards.

Gameloop

The large gameloop then contains the rest of the game from Loop – jmp Loop.

The loop is divided into:
_ Vsync, wait until the cathode ray is at the beginning of the screen

vsync:
          lda #$FF    // load 255 into A
        cmp $D012    //look if current rasterline equals 255
        bne vsync     // if no, goto wait


Read out the collision flags in the variable “col”

         // ----------------
        // collision
        // ----------------
        // 01 000100
        lda $d01e // collision read and clear at the same time ...            
        sta col

Behavior&Collision of the objects (see below in detail)

Counter, which always counts from 0 to 256 in steps of 7.

        // ----------------------
        // counter for all 
        // and border
        // ----------------------
        // <----spread----->
        lda counter
        adc #7
        cmp #255-4-40  // more than 250 ... 
        bcc ttt
          lda #50+40
            // > 251
        ttt:       
        sta counter


Interaction part (movement of the avatar using the joystick)



        // ----------------------
        // JOYSTICK
        // -----------------------        
        // LEFT&RIGHT        
        lda #JOY_RIGHT                // mask left movement (8 == bit 4 == %0000 1000)
        bit $DC01               // bitwise "and" with joystick port 1
        bne joystick_notright   // if not active (==1), go to .input_up_check
            lda $d000
            adc #4
            sta $d000
joystick_notright:
        lda #JOY_LEFT             // mask left movement (8 == bit 4 == %0000 1000)
        bit $DC01               // bitwise "and" with joystick port 1
        bne joystick_notleft   // if not active (==1), go to .input_up_check
            lda $d000
            sbc #4
            sta $d000
joystick_notleft:

Sprit management: Behavior&Collision of the objects

The register x counts up from 0 – 14, so go through all sprite addresses times 2.

        ldx #0
        
        // 010001 < 
        lda #1
        sta ind

behaviors:

[..]

        // ----------------
        // next
        // ----------------
            // x=x+2 
            inx 
            inx 
            // 00010000 < shifting
            asl ind

        cpx #spritesmax*2
        bne behaviors

Collision detection is also being worked on at the bottom. This makes it possible to clarify whether the shot has collided with an opponent.

Movement

The behaviors of the objects are designed in terms of movement:

                // -----------------
                // shoot & enemies                
                // -----------------

                cpx #2
                bcc behavior_enemies_end
  

                    // always
                    // +
                    adc #1
                    
                    // 4+
                    cpx #4
                    bcs third
                        // -
                        sbc #shoot_speed+1
                        // -
                        cmp #50+60
                        bcs third
                            lda $d000
                            sta tmpx
                            lda #shoot_start
                    third:

If the shot is below 50+60, it is returned to the avatar and flies off again.

Game Over?

If an opponent flies further than the Y-position 245, the program jumps back to the beginning and restarts.

gameover:
                     cmp #245
                     bcs restart
               
               

Init new enemy

If the opponent is still at the top, it is moved to the right by a counter until it goes down. This is a replacement for random numbers that are difficult to generate (maybe use basic random? Does it need fewer bytes?)

                    // enemies
                    // flying down ... 
                    cmp #50+25 // 204
                    bcs nnn 
                        lda counter
                        sta tmpx                                            
                        // inc $d020                            
                    nnn:

Enemy „hits“ Shot – Pts

If the opponent is still at the top, it is moved to the right by a counter until it goes down. This is a replacement for random numbers that are difficult to generate (maybe use basic random? Does it need fewer bytes?) And finally, the system also checks whether an opponent has touched the shot. To do this, the collision flag saved above is used and compared with the current sprite. If the sprite collides with the shot, a dot is printed (jsr print)

The shot is placed in front of the avatar (lda $d000 / sta $d002 / #shoot_start / sta $d003). “The avatar shoots”.

                    lda ind
                    ora #%00000010 
                    sta tmp
                    lda col
                    and tmp
                    cmp tmp // invers!
                    bne hit

 
                    
                         // check if actual ..
                         lda $d003
                         cmp tmpy
                         bcs hit

                         lda #0 // take print value!
                         sta tmpy 


                         lda #252 // 252
                         jsr print


                         // ----------------
                         // reset shot
                         // ----------------
                         lda $d000
                         sta $d002 // shot                         
                         lda #shoot_start
                         sta $d003 // shot

                      
                       hit:

Release:

lovebyte.party Feb 2025 (unfortu. they dont have anymore a category for nanogames!)

ZAX254 – The whole code and exe (prg) you find here, after release.

Try it out yourself? Download the zip and unzip it. .PRG is the actual program. In emulators such as VICE (attention must be configured) the PRG can simply be dropped or you can use an online emulator such as 111mb.de, where you simply have to upload it. And then you can try out the whole thing and see if the game has anything to do with what is described above. .-)

ZAX384

ZAX384 is a revised version.

The 130 extra bytes were used for:

Title

The game has a visible title. With a Petscii-X.

Score

The game has a classic flashing score. This uses the print routine of the basic. The maximum score is 255, but it is almost impossible to score 255 points as the player will lose concentration at some point.

„More beautiful and ambiguous sprites“

The sprites are more detailed and an if condition makes it possible to insert a different graphic at the bottom. This allows the sprites to be used three times. If they fall downwards, they look like they are landing (landing ships). If they are a shot upwards, the shape is perceived as a shot forwards. And when cut off, the front part looks like a gun.

Background

The sprites are more detailed and an if condition makes it possible to insert a different graphic at the bottom. This allows the sprites to be used three times. If they fall downwards, they look like they are landing (landing ships). If they are a shot upwards, the shape is perceived as a shot forwards. And when cut off, the front part looks like a gun.

Game Over

The gameover now pauses the game, you can memorize the score and you have to restart with Fire.

All in all, the game now feels more like an arcade or action game. A one-screen action game. The setting also tells a (very stereotypical) story.

Release

mountainbytes.ch mid of Feb 2025

Prg & Sources

Optimization options

256b version: variant with a score could probably also be created with a lot of savings. However, the score then requires a variable that counts up and a display. It might also be possible to write this directly into the screen memory so that you don’t have to use the complex SetPos and Print.

Of course, this is not the end of the story (and there is probably already something much more interesting and smaller out there .-) and other, newer concepts will also make other, hopefully better ShootEmUps possible. In any case, it was an interesting game to realize something like this here.

Dieser Beitrag wurde unter Uncategorized veröffentlicht. Setze ein Lesezeichen auf den Permalink.