Dead Ducks

Date: 198x
Type: Program
Platform(s): TS 2068
Tags: Game

This program is a two-player artillery game in which players take turns firing a cannon at targets — a moon (UDG A–C), ducks (UDG D–J), and a barrel — by entering a launch angle between 45 and 85 degrees. The first 80 lines define ten custom UDG characters (A through J) using POKE USR to create sprite graphics for the moon, barrel/cannon components, and a duck shape. Projectile trajectory is computed using parabolic formulas: horizontal position as `x = INT(0.056 * i * (90 – a))` and vertical height as `y = INT(i * (100 – i) * 0.07)`, with collision detection performed via the ATTR function to test whether the plotted pixel lands on a colored cell. The game tracks hits on moons and total shots across five rounds, displaying running scores in the border area.


Program Analysis

Program Structure

The program divides into three logical phases:

  1. UDG Setup (lines 10–80): Reads bitmap data for ten custom characters (A–J) from DATA statements and POKEs them into UDG memory using USR a$ as the base address.
  2. Game Loop (lines 90–320): Iterates five rounds (FOR g=1 TO 5), placing targets at random screen positions, accepting angle input from up to two players, simulating a projectile, and checking for collision.
  3. Subroutine / End (lines 340–510): Displays a final summary and provides a “Press ENTER to continue” pause routine at line 500.

UDG Character Definitions

Lines 10–80 define UDGs A through J, each encoded as 8 bytes of bitmap data stored in the DATA statements on lines 70–80. The inner loop (FOR d=0 TO 7) POKEs each byte to USR a$+d, where a$ holds the character letter as a one-character string. The ten UDGs collectively form the graphical sprites used in the game:

UDGRole
A, B, CMoon — top, middle, bottom rows
D, E, F, G, HBarrel/cannon top assembly (five cells wide)
I, JBarrel/cannon base flanks

Trajectory Calculation

The projectile path is computed inside FOR i=0 TO 100 at lines 220–242. Two formulas drive position:

  • x = INT(0.056 * i * (90 - a)) — horizontal screen column, scaled by the complement of the angle.
  • y = INT(i * (100 - i) * 0.07) — vertical pixel height using a symmetrical parabola centered at i=50.

Each step plots a pixel with PLOT x, y and emits a tone with BEEP 0.1, f/100 where f = i*(100-i), giving a pitch that rises and falls to match the arc shape.

Collision Detection via ATTR

Line 240 converts pixel coordinates to character-cell coordinates: lin = 21 - INT(y/8) and col = INT(x/8) (note: col is derived from y in the listing — this is likely a bug where the variable name col is misleadingly computed from the vertical coordinate rather than the horizontal one). The ATTR value of that cell is then tested; if it exceeds 64 (meaning a colored, non-black attribute is present), a hit is registered at line 250.

A secondary check at line 280 tests col>d AND col<d+4 to distinguish a hit on the barrel/duck from hitting the moon (line number <= 3). This layered ATTR-plus-position check avoids storing any explicit sprite-position arrays beyond the already-known variables d, m.

Input Validation

Line 200 enforces that the entered angle a falls between 45 and 85 degrees inclusive. If the condition fails, an inline error message is printed as part of the INPUT prompt and the program loops back to line 200 with GO TO 200. The use of an expression inside INPUT (...) to show the bad value to the user is a compact Spectrum BASIC idiom.

Scoring and Display

Three counters are maintained: hd (duck hits), hm (moon hits), and s (total shots). These are displayed in the right-hand border column using AT positioning. The barrel number each round is rendered by printing CHR$(g+48) — converting the loop counter g (1–5) to its ASCII digit character — between INVERSE-on and INVERSE-off attributes, giving it a highlighted appearance on the barrel graphic.

Notable Techniques

  • Using PAPER 1: INK 7: BORDER 7 with a column of spaces printed at TAB 31 in a loop creates a right-side score panel with a contrasting background color.
  • The moon “falling” animation at lines 270–275 scrolls the UDG moon down 18 rows by reprinting it one row lower each iteration, with a descending BEEP pitch (1-z).
  • The two-player loop at line 180 (FOR b=1 TO 2) with NEXT b at line 297 gives each player one shot per round; GO TO 310 or GO TO 320 at lines 275 and 298 break out of this inner loop on a successful hit or after both players have fired.
  • GO TO VAL "200" is not used here; instead a plain GO TO 200 is used for the angle re-entry loop — no memory optimization applied to that jump.

Potential Bugs and Anomalies

  • col computed from y: Line 240 sets col = INT(y/8) rather than INT(x/8). This means the column variable used in the duck-hit test at line 280 is based on the vertical coordinate of the projectile, not the horizontal one, making the barrel hit detection potentially incorrect.
  • lin computed from y: Similarly, lin = 21 - INT(y/8) is the screen row, which is correct for vertical position. The naming of col for the second INT(y/8) expression (same computation) means both lin and col carry the same base value, which appears unintentional.
  • Two players, one shot each, but shared score: Both b=1 and b=2 shots increment the shared s counter, so shots are counted globally rather than per player.

Content

Appears On

Related Products

Related Articles

Related Content

Image Gallery

Dead Ducks

Source Code

   10 FOR c=1 TO 10
   20 READ a$
   30 FOR d=0 TO 7
   40 READ x: POKE USR a$+d,x
   50 NEXT d
   60 NEXT c
   70 DATA "A",1,6,14,28,28,60,60,120,"B",120,120,248,248,248,248,120,120,"C",120,60,60,28,28,14,6,1
   80 DATA "D",0,0,0,15,15,0,0,0,"E",60,18,124,252,254,255,255,255,"F",0,0,0,0,0,240,255,255,"G",0,0,0,0,0,0,240,255,"H",0,0,0,8,16,32,64,128,"I",1,1,3,7,7,3,3,1,"J",255,255,255,254,252,248,224,128
   90 LET hd=0: LET hm=0: LET s=0
  100 FOR g=1 TO 5
  110 CLS : PAPER 1: INK 7: BORDER 7: FOR i=0 TO 20: PRINT TAB 31;" ": NEXT i
  120 LET d=INT (RND*16+12): LET d2=2*d
  130 LET m=INT (RND*7+12): LET m2=2*m
  140 PRINT AT 0,0;"Barrel "' FLASH 1;"Angle=? "; FLASH 0;AT 0,25;hm;" moons";AT 2,25;s;" shots"
  150 PRINT BRIGHT 1; INK 4;AT 1,m;"\a";AT 2,m;"\b";AT 3,m;"\c"
  160 PRINT INK 3;AT 21,0;"\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\.."
  170 PRINT BRIGHT 1; INK 6;AT 20,d;"\d\e\f\g\h";AT 21,d;"\i\::"; INVERSE 1;CHR$ (g+48); INVERSE 0;"\j"
  180 FOR b=1 TO 2
  190 PRINT AT 0,6;b; FLASH 1;AT 1,0;"Angle=? ": INPUT "Enter angle of fire: ";a
  200 LET a=INT a: IF a<45 OR a>85 THEN INPUT (a;" is not between 45 and 85."'"Reenter angle: ");a: GO TO 200
  210 PRINT AT 1,0;"Angle= ";a
  220 FOR i=0 TO 100
  230 LET x=INT (0.056*i*(90-a)): LET f=i*(100-i): LET y=INT (f*0.07): PLOT x,y
  235 BEEP 0.1,f/100
  240 LET lin=21-INT (y/8): LET col=INT (y/8): IF ATTR (lin,col)>64 THEN GO TO 250
  242 NEXT i
  245 PRINT AT 20,d-4;"Miss": GO TO 295
  250 IF lin>3 THEN GO TO 280
  260 LET hm=hm+1: PRINT AT 1,25;hm
  270 FOR z=1 TO 18: PRINT INK 4;AT z,m;" ";AT z+1,m;"\a";AT z+2,m;"\b";AT z+3,m;"\c": BEEP 0.1,1-z: NEXT z
  275 GO TO 310
  280 IF col>d AND col<d+4 THEN GO TO 300
  290 PRINT AT 20,d-4;"Ouch!";: BEEP .5,5: BEEP .5,3
  295 LET s=s+1: PRINT AT 2,25;s
  297 NEXT b
  298 GO TO 320
  300 LET hd=hd+1: PRINT AT 0,25;hd: PRINT AT 20,d-4;"DEAD": BEEP 1,-10: BEEP .5,-10: BEEP 1,-10
  310 LET s=s+1: PRINT AT 2,25;s: GO SUB 500
  320 NEXT g
  340 PRINT FLASH 1;AT 10,11;"The end";AT 12,3;"You killed ";hd;" ducks"
  350 STOP 
  500 INPUT "Press ENTER to continue ";z$
  510 RETURN 

Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

People

No people associated with this content.

Scroll to Top