A downloadable game

BALLISTAE: The Story Behind the Game

=================================

A.D. 101: Emperor Trajan begins a massive campaign against the Thracian, leading an army equipped with the most sophisticated weapons available at the time. The heavy walls of the Thracian oppida (fortified towns) are attacked with machines capable of hurling massive stone projectiles against their defenses: the ballistae. The monumental TrajanÕs Column in the center of Rome still reminds us today of the epic battles of the Roman Empire and the heroic resistance of the Thracian people.

In this game, you maneuver a ballista mounted on a wheeled chariot, a carroballista. Hidden behind a hill, the ballista must strike a distant oppidum, but only five projectiles remain. Will the Roman soldier manage to damage the enemy town?


The Game

========

BALLISTAE is a ZX Spectrum game written in the standard built-in Sinclair ZX BASIC. The whole game is contained in 10 lines of no more than 120 characters, including line numbers, and is therefore submitted to compete in the PUR-120 section of the 10liner Contest.

The game can be run on a 16/48K ZX Spectrum or any later version of SinclairÕs most popular computer, including clones and FPGA replicas. It also runs smoothly on ZX Spectrum emulators.

Commands are straightforward:

* O / P increase and decrease the power of the ballista

* Q / A increase and decrease the launch angle

* SPACE launches the projectile


The Submission

===============

The game is submitted in several formats: TAP, TZX, Z80, and BAS (generated by BasinC). This text is part of the submission.


The Development

================

This game was developed entirely on the Fuse Emulator (v. 1.5.6) coupled with BasinC (v. 1.794). During development, it was constantly tested on a ZX Spectrum 48K and on an N-GO (a ZX Spectrum Next clone).


The Code

========

Graphic objects (the carroballista, the Thracian oppidum, and the projectile) are implemented as UDGs, generated from numeric DATA (line 1). The DATA are distributed across the 10 lines (lines 4Ð10), taking advantage of the useful BASIC feature that allows DATA to be placed in different lines and read sequentially.

To detect collisions, the colour attributes (ATTR) are checked (lines 8 and 9), thanks to the different colours of the sky and the target oppidum. Note that instead of the usual (slower) INKEY$ command, the PEEK command reads memory location 23560 (variable q). To avoid using IF commands, Booleans operators are exploited to modify power and shooting angle depending on the key pressed by the player (line 4).

Unfortunately for the Roman ballista-manoeuvrer, the distance to the enemy town is random (RND; variable n), and a small hill, disposed randomly (variable m) separates the launcher from the target town, so that each game is different.

In the last two lines of the screen, the angle, power, and score are displayed. Writing to these lines, which are not normally accessible in standard BASIC, is achieved by POKEing the address 23659 (variable l).

To reduce unwanted flashing effects of the projectile, it is deleted at the same time that it is printed in its new position (variables x,y store the current coordinates; w,z the previous coordinates).

A subtle trick appears in line 8, where the projectile (o$) is drawn. Here a semicolon is inserted at the end of the PRINT command (PRINT É o$;). Although it is not necessary to preserve the cursor position, this form makes the PRINT command faster. This trick is used only on this line: the remaining PRINT commands are not repeated frequently enough to require such optimization.

To guarantee the needed realism, the velocity vector is decomposed in its orthogonal components according to an accurate physical model (line 4).

Finally simple but effective sound effects (BEEP) accompany the launch and provide a feedback when the projectile hit the target or when the game is over.

The final code is as follows:

1 LET s$=CHR$ 143: LET r$="": LET k=10: FOR x=0 TO 111: LET r$=r$+s$: READ p: POKE USR "a"+x,p: NEXT x: PAPER 5: BORDER 4: CLS : RANDOMIZE : LET l=23659: LET s$=CHR$ 32: LET o$=CHR$ 154: LET m=4+RND*8: LET a=60: POKE l,0: LET q=l-99
2 PRINT PAPER 0; INK 7;AT 22,0;"POWER";TAB 9;"ANGLE";TAB 18;"SCORE";TAB 27;"SHOTS"; INK 4;AT 18,m+1;r$( TO 5);AT 19,m;r$( TO 8)'r$( TO 64);AT 17,m+2;r$( TO 2)': POKE l,2
3 LET n=18+RND*k: LET s=0: LET t=5: LET v=2: PRINT CHR$ 156;CHR$ 144;CHR$ 145'CHR$ 157;CHR$ 146;CHR$ 147; INK 1;AT 17,n+1;CHR$ 148;CHR$ 149;AT 18,n;CHR$ 150;CHR$ 151;CHR$ 152;CHR$ 153;AT 19,n;r$( TO 4): GO SUB k
4 POKE q,0: LET a=a+2*((p=113 AND a<80)-(p=97 AND a>30)): LET v=v+((p=112)-(p=111 AND v>1))/k: LET b=a*PI/180: LET e=v*SIN b: LET d=v*COS b: DATA 0,0,0,0,0,1,131,255,7,30
5 GO SUB k: IF p=32 THEN LET t=t-1: LET x=1: LET y=18: GO SUB 8: DATA 60,124,220,156,31,255,255,255,69,130,159,130,68,56,255,255,162,65,249,65,34,28,1,3
6 IF t>0 THEN LET p=PEEK q: GO TO 4: DATA 7,15,7,7,7,15,224,240,252,248,248,152,152,248,0,0,1,3,38,174,191,191,15,135,199,231,127,123,255,255,248
7 BEEP .15,k: BEEP .15,7: BEEP .4,0: PRINT AT k,9; INK 2; PAPER 6; FLASH 1;"PRESS ANY KEY": PAUSE 1: PAUSE 0: RUN: DATA 152,152,156,252,253,253,253,168,248,80,112,120,108,94,247
8 LET w=x: LET z=y: LET e=e-.1: LET x=x+d: LET y=y-e: BEEP .01,6-y/5: PRINT AT z,w; OVER 1;o$;: IF ATTR (y,x)=40 AND y>0 AND x<31 THEN PRINT AT y,x; OVER 1;o$;: GO TO 8: DATA 0,60,106,86,106,86,60,0
9 IF ATTR (y,x)=41 THEN PRINT AT y,x; INK 2;CHR$ 155: BEEP .04,12: BEEP .04,22: BEEP .05,-8: BEEP .35,-26: PRINT AT y,x;s$: LET s=s+100: DATA 40,69,48,162,9,68,162,8,28,28,30,56,103
10 POKE l,0: PRINT PAPER 0; INK 7; BRIGHT 1;AT 22,31's$;v*k;TAB k;s$;a;TAB 18;s$;s;TAB 29;t;s$;s$: POKE l,2: PRINT AT 18,1;o$: RETURN: DATA 225,161,205,205,194,66,114,62,61,20,22

Finally, by replacing each BASIC token with the corresponding key, each line in the resulting source code does not exceed the 120-character limit:

1ls$=U143:lr$="":lk=10:fx=0T111:lr$=r$+s$:Ap:oL"a"+x,p:nx:C5:b4:v:ll=23659:ls$=U32:lo$=U154:lm=4+T*8:la=60:ol,0:lq=l-99
2t:pC0;X7;I22,0;"POWER";p9;"ANGLE";p18;"SCORE";p27;"SHOTS";X4;I18,m+1;r$(T5);I19,m;r$(T8)'r$(T64);I17,m+2;r$(T2)':ol,2
3ln=18+T*k:ls=0:lt=5:lv=2:pU156;U144;U145'U157;U146;U147;X1;I17,n+1;U148;U149;I18,n;U150;U151;U152;U153;I19,n;r$(T4):hk
4oq,0:la=a+2*((p=113Ya<80)-(p=97 Ya>30)):lv=v+((p=112)-(p=111Yv>1))/k:lb=a*M/180:le=v*qb:ld=v*Wb:D0,0,0,0,0,1,131,255,7
5hk:up=32 Glt=t-1:lx=1:ly=18:h8:D30,60,124,220,156,31,255,255,255,69,130,159,130,68,56,255,255,162,65,249,65,34,28,1,3
6ut>0Glp=Oq:g4:D7,15,7,7,7,15,224,240,252,248,248,152,152,248,0,0,1,3,38,174,191,191,15,135,199,231,127,123,255,255,248
7Z.15,k:Z.15,7:Z.4,0:pIk,9;X2;C6;v1;"PRESS ANY KEY":m1:m0:r:D152,152,156,252,253,253,253,168,248,80,112,120,108,94,247
8lw=x:lz=y:le=e-.1:lx=x+d:ly=y-e:Z.01,6-y/5:pIz,w; N1;o$;:uL(y,x)=40Yy>0Yx<31GpIy,x;N1;o$;:g8:D0,60,106,86,106,86,60,0
9uL(y,x)=41GpIy,x;X2;U155:Z.04,12:Z.04,22:Z.05,-8:Z.35,-26:pIy,x;s$:ls=s+100:D40,69,48,162,9,68,162,8,28,28,30,56,103
10ol,0:pC0;X7;B1;I22,31's$;v*k;pk;s$;a;p18;s$;s;p29;t;s$;s$:ol,2:pI18,1;o$:y:D225,161,205,205,194,66,114,62,61,20,22

In the previous text, letters refer to the original ZX Spectrum keyboard (for example "p" for PRINT and "P" for TAB).

Have fun with BALLISTAE!


Download

Download
BALLIST.BAS 1.8 kB
Download
BALLIST.TAP 2.5 kB
Download
BALLIST.TZX 2.5 kB
Download
BALLIST.Z80 5.2 kB
Download
Ballistae-intro.pdf 4.4 MB
Download
howtorun.txt 171 bytes
Download
intro.txt 7.3 kB
Download
tokens.txt 2.9 kB

Install instructions

Install Instructions

==================

BALLISTAE Ð A ZX Spectrum Game by Massimiliano Arca, 2026

To start the game:

1. Open the file BALLIST.Z80 in an emulator.

OR

2. Open the tape file BALLIST.TAP in your preferred emulator.

OR

3. Open the tape file BALLIST.TZX in your preferred emulator. 

Comments

Log in with itch.io to leave a comment.

Wow, it also reminds me of Worms. Very good!

Love this. The gameplay reminds me of Worms!