Paddleship
A 10-line TurboBASIC XL program for the Atari 8-bit computer
Written for 2014 NOMAM programming competition
Bill Kendrick, February 11-12, 2014
An entry for the NOMAM 2014 10-line TurboBASIC XL
game competition.
Objective
In the game, you control a ship flying in space. It must bump into another
ship, that appears randomly on the screen; doing so scores a point, and
causes a new ship to appear elsewhere on the screen. A limited amount of time
is allotted. When the time runs out, the game ends and your score is shown.
The ship is controlled with a paddle controller. The ship's direction
changes depending on the position of the paddle knob. If kept in the center,
the ship's direction does not change; otherwise, the ship will continuously
rotate in the direction the paddle is turned, and at a higher rate if the
paddle is turned further from its center position.
Pressing the fire button on the paddle causes the ship to thrust forward
in the direction it's facing. If it reaches an edge of the screen, it
stops moving in that direction (though it may continue moving perpendicular
to the edge; e.g. if you are moving towards the top-left, and hit the top edge
first, you will stop moving up, but will continue moving left until you hit the
left edge, at the top-left corner).
Development
References used:
Turbo Basic Command List,
Mapping the Atari,
De Re Atari
This game was developed in one evening, while simultaneously tending to
a 2-year-old toddler who enjoys banging on electronic devices. I followed
these steps:
- Fired up TurboBASIC XL on my Atari 1200XL computer (best. keyboard. ever.)
- Loaded and ran "FONT 3", a relatively usable font editor that I wrote
in plain Atari BASIC in 1990
- Decided to make it a paddle game, since those are rare and novel
- Began drawing a tank... no, let's make this a space game... began
drawing a ship
- Created the N- and NE-facing versions of the ship
- Struggled a little to make the NNE version of the ship
- Duplicated the N, NNE, and NE versions of the ship (using combinations
of my font editor's flip, mirror, and rotate features) to get versions
of the ship in the 13 other directions I needed
- Created a program in TurboBASIC XL that read the bytes from the font file
and saved them out to disk as a BASIC listing that could be
ENTER
-ed off of the disk.
- The raw bytes of the font/bitmap were stored in ATASCII in a string (
F$
, lines 11 and 12).
- Fortunately, no row of graphics involved a 155 (ATASCII EOL); the characters representing the other 255 bytes of ATASCII are perfectly acceptable in a string assignment!
- Began writing the main game. Couldn't decide what bitmap or text
graphics mode to use. Finally decided on
GRAPHICS 7
to
get reasonably small pixels to use as stars, in a variety of colors,
which I could toy with via palette manipulation to make them 'sparkle' (line 100)
- Consulted my print version of De Re Atari and both my print version, and the Atari Archives version of Mapping the Atari, to remind myself how to use Player/Missile Graphics
- Found, converted to 80 columns, and printed a copy of Turbo Basic Command List, to remind myself what tricks I can use, and how to copy memory blocks around (hint, it's the "
MOVE
" command)
- Got a PMG ship to appear on the screen. Began writing code to rotate and move it.
- Modified the earlier TBXL program to also dump out a string containing directional info that can be used for each of the 16 directions the ship faces:
- Store 24 values for the cos() of 0 through 540 degrees (in 22.5 degree intervals) — hint, have to use the
DEG
command to get COS()
and friends to accept degrees; by default they accept radians
- This allowed pointing 16 directions and moving horizontally (using the values at locations 1-16 as-is to get cos()) and vertically (using negative of the values at locations 5-20 to simulate sin()) — oops, I only needed to record 20 values through 450 degrees!
- Values of
COS()
were multiplied by 2, to get values between -2.0 and 2.0
- +2 was added to each value, to get values between 0.0 and 4.0
- The value was stored as an ATACII variable into a string (
D$
, line 12) (therefore, the values became integers between 0 and 4)
- Now that I had actual movement values, got the ship to move around the screen.
- Had trouble remembering that sin() values are off by cos() by 90 degrees, not 180 degrees.
- With the program's code 10 lines long, I had a framework for a game, but it wasn't a game!
- Took some photos of the game and its code, and posted them to Facebook, lamenting my situation
- Decided the objective of the game would be to fly your ship to an object. This would be "fun" (or at least a challenge), because the paddle input makes it very difficult to fly the damned thing!
- Added the other Player, collision detection, the timer and score, and game-over feature. I had a game! It was 11-12 lines long, though!
- Reorganized the code a bunch to fit everything into 10 lines. Due to this, you'll notice some cases where:
- Related code (e.g., the four versions of edge-of-screen detection) seems arbitrarily interrupted by some unrelated code (e.g., timer and game-over logic)
- Code blocks (
IF
and FOR
-loops) start on one line, and finish on the next, often followed by more unrelated code
- Created this web page, explaining what the code does, line-by-line, and describing the development process.
Line-by-line Breakdown of the Source Code
Source-code from when the ship moved, but before it was a game.
Set-up
10 GRAPHICS 23:PM=(PEEK(106)-32):POKE 54279,PM:POKE 53277,3:POKE 559,46:H=53248:DPOKE 704,16522:PM=(PM+2)*256:MS=32
- Switches to 160x96 graphics mode, with no text window (aka
GRAPHICS 7+16
)
- Refer
PM
to some space just behind RAMTOP
, and point the Player/Missile Graphics at it (PMBASE
)
- Turn on PM Graphics, by setting
GRACTL
to 3 (00000011)
- Set ANTIC to use Direct Memory Access to show PM Graphics, by setting
SDMCTL
to 46 (00101110)
- Assign the location of Player 0's horizontal location,
HPOSP0
, to a variable for easy-access
- Set the colors of Player 0 and Player 1, by POKEing into
PCOLR0
(704) and PCOLR1
simultaneously. Player 0 gets bright blue (hue=8, lum=10, 138), Player 1 gets dark red (hue=4, lum=0: 64).
- Reassign
PM
to point at the top to Player 0's graphics memory (*256 to go from a page to an actual byte location in RAM, +2 to add 512 to skip the space above Player 0: unusued, and Missile graphics)
- Assign a value to variable
MS
, which represents the "maximum speed" our ship will be able to fly
11 DIM F$(136),D$(24):F$="{80 characters of ATASCII gibberish}":F=ADR(F$)
- Allocate space for all 16 versions of the ship's bitmap data (I called it "
F$
" because I was thinking about fonts, since I used a font editor to draw the Player bitmaps), and for the directional movement data.
- Assing part of the bitmap data.
- Set a pointer to it,
F
12 F$(81)="{56 characters of ATASCII}":D$="{24 characters of ATASCII}":FOR I=0 TO 256
- Assign the rest of the ship bitmap data.
- Assign the directional movement data.
- Begin a loop that...
20 DPOKE PM+I*2,0:COLOR RAND(3)+1:PLOT RAND(160),RAND(96):PLOT I/2,0:NEXT I:X=80:Y=24:XM=0:YM=0:SC=0:D=4:T=0:COLOR 0:GOSUB 1000:V=0
- ...
- Blanks the graphics memory for Players 0 and 1
- Picks a random color, and plots it at a random location, to produce a starfield
- As plots a line at the top of the screen
- Assign the ship's starting location (
X
& Y
)
- Set the ship's motion to "not moving" (
XM
& YM) — not strictly necessary, since they'd default to 0
- Zero the score (
SC
) — again, not strictly necessary
- Reset the clock (
T
) — ditto
- Sets future drawing to use the background color (which defaulted to black, by the way)
- Jump to the routine that randomly positions and draws Player 2, the object we're to collect
- Zero out the sound effect volume, which was just set by the object-positioning routine (therefore, unlike the other zero-assignments, this is necessary) — this avoids having the "you got the object!" sound effect from playing automatically when the game first begins
Main Loop
100 POKE H,X+48:MOVE F+8+INT(D)*8,PM+16+Y,8:MOVE F,PM+8+Y,8:MOVE F,PM+24+Y,8:DPOKE 708,RAND(65536):POKE 710,RAND(255)
- Assign the horizontal position of the ship
- Draw the appropriate ship bitmap (from data in
F$
, via the pointer F
), based on the direction the ship is facing (D
), at the appropriate vertical position (Y
), within Player 0's graphics memory (PM
, +16 to position inside the overscan/border)
- Erase any possible residual shape from above (
PM+8
, aka +16-8) and below (PM+24
, aka +16+8) the ship's current position
- Randomly change the color palette of the three foreground colors (
COLOR0
, COLOR1
and COLOR2
, locations 708-710)
110 SOUND 1,100,10,V:D=D-(PADDLE(0)-128)/128:IF D<0:D=D+16:ELSE :IF D>=16:D=D-16:ENDIF :ENDIF :ID=INT(D+1):IF PTRIG(0)=0
- Play the "you got the object!" sound effect on voice #2. Since using a 'pure tone' (distortion 10), when the volume (
V
) is 0, no sound will be played.
- Adjust the ship's direction (
D
), based on the paddle input.
- Wrap the ship's direction around if it goes out of bounds (less than 0, or greater than 15), i.e. such that 0≤D<16.
- For possible use in a moment, convert the floating directional value,
D
, into an integer between 1 and 16, ID
- When the paddle firebutton is pressed...
120 XM=XM+ASC(D$(ID))-2:YM=YM-ASC(D$(ID+4))+2:SOUND 0,0,0,10:ELSE :SOUND 0,0,0,0:ENDIF :IF ABS(XM)>MS:XM=SGN(XM)*MS:ENDIF
- ...
- Adjust the ship's trajectory (
XM
and YM
) based on the direction we're currently facing (D
), by accessing values stored in D$
via ID
- Play a thruster sound effect on voice #1
- otherwise, if the paddle firebutton is not pressed, silence voice #1
- If horizontal trajectory (
XM
) is too fast (<-MS or >+MS), cap it at ±MS
130 IF ABS(YM)>MS:YM=SGN(YM)*MS:ENDIF :X=X+XM/8:Y=Y+YM/8:IF Y<0:Y=0:ENDIF :T=T+0.25:PLOT 128-T,0:IF T=128 THEN ? SC:DPOKE H,0:END
- Similarly, if vertical trajetory (
YM
) is too fast, cap it
- Adjust the ship's position (
X
and Y
) based on it's speed and trajectory (XM
and YM
)
- If the ship goes past the top of the screen (
Y<0
), stop it
- Count time (
T
)
- Erase the timer bar at the top
- If we've used up all our time, print the score (
SC
), which causes us to jump back to full-screen text mode (GRAPHICS 0
) because we have no text window. Move both Player 0 and 1 off (past the left edge of) the screen. End the program. (END
also has the side-effect of silencing any sounds.) Game over
140 IF Y>96:Y=96:ENDIF :IF X<0:X=0:ENDIF :IF X>160:X=160:ENDIF :IF PEEK(53261):SC=SC+1:GOSUB 1000:ENDIF :V=V-SGN(V):GOTO 100
- If the ship goes off the bottom of the screen (
Y>96
), stop it
- Ditto for off the left edge (
X<0
) or right edge (X>160
)
- If Player 0 touches Player 1 (via
P1PL
at 53261), increment the score (SC
) and call the routine to reposition the object.
- If the "you got the object!" sound is playing, decrease it's volume (once per loop iteration) until it reaches 0
- Iterate the loop (
GOTO 100
) — I could have used a TurboBASIC XL "DO
" on line 100, and "LOOP
" instead of "GOTO
" here.
Subroutine
1000 MOVE F,PM+16+YY+128,8:POKE H+1,RAND(160)+48:YY=RAND(96):MOVE F+8,PM+16+YY+128,8:POKE 53278,0:V=15:RETURN
- Erase the object from Player 1's graphics memory at it's current (soon to be 'old') vertical location (
YY
)
- Position Player 1 at a random horizontal location (
H
is the location for HPOSP0
, so H+1
is that of HPOSP1
)
- Pick a random vertical location for the object (
YY
)
- Draw the object at the new vertical location
- Clear the Player/Missile collision detection registers, by
POKE
-ing into HITCLR
(53278)
- Set the "you got the object!" sound effect volume (
V
) to full loudness
- Return from this subroutine
Download
- SHIP.TBS - tokenized TurboBASIC XL file
- paddleship-20140212.atr - ATR disk image containing:
- MyDOS 4.53/3 (
DOS.SYS
, DUP.SYS
)
- TurboBASIC XL (auto-run,
TBASIC.AR0
)
- The game (TBXL auto-run,
AUTORUN.BAS
; except for the filename, same as SHIP.TBS
stand-alone file, above)
To-Do
- Add credits/documentation file to the ATR disk image
- maybe a TBXL program that loads as
AUTORUN.BAS
, then does RUN "D:SHIP.TBS"
to launch the game...?
- Explain the best way to play in an emulator
- With mouse-controlled paddle in Atari800, I had to set mouse sensitivity to its highest (9), and even then it was hard to play
- People will want to know how to play in Atari800Win++ and Altirra, too
- Create a joystick-based version???
Bill Kendrick, 2014,
nbs@sonic.net,
New Breed Software
Other games I wrote for NOMAM 2014