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.