Space Ranger

Copyright © 2017 by Víctor Parada

Space Ranger

This is a little game for the 2017 NOMAM's BASIC 10-liners Contest. This program fits in the EXTREM-256 category, and was written in TurboBASIC XL for the 8-bits ATARI XL/XE.

UPDATE: It won the 1st place of 12 entries in the category.

Description

Chase the enemies and shoot them with your short-range weapon before you run out of power.

Instructions

RANGER start Wait some seconds and your pilot cabin will appear. Press fire button to start the mission.
RANGER radar Your mission is to find and destroy the enemies' small fleet. The radar will guide you to them.
RANGER far enemy If the enemy is far away, you might see a point if it is in front of you. The more time it take you to find it, more power of your would be used.
RANGER middle distance enemy While the enemy is in front of you, you'll be able to catch it.
RANGER near enemy If the enemy is not in front of you, it might escape.
RANGER enemy in range If the enemy ship is close to you, the gun sight will blink in green. Then you can fire and destroy the enemy. Some power is used every time you fire your short range lasers. Use them only when it is required, or you would spend all your power cells.
RANGER enemy attacks Be careful! If you didn't hit it in few seconds, it would send you a wave attack that will damage your shields and requires some power to be repaired.
RANGER completed mission If you destroy the complete enemy fleet, your score is the remaining power.
RANGER enemy in range If you run out of energy, you will lose the game.

Development of the game

In a small speak with another Atari fan, I explained the restrictions I've found developing TurboBASIC tenliners. One of them was the number of "sprites" that could be managed at the same time without slowing down the game too much to make it boring or unplayable. Another one was the actual speed of the sprite: if it is slow by definition, it would be OK for a BASIC program, and one example of it was a simplified version of Star Raiders (without the stars).

Said that, I wrote a small prototype that moves an enemy ship from a far distance to a near distance using a couple of bitmaps for a player (P/M graphics). The technique I used to display the enemy was not to remove the bitmap from the previous vertical position of it over its P/M area and then put the bitmap again in the new position. Instead of that, I defined buffers with the bitmap of the corresponding image in the middle of it, and in just one instruction with a formula, select the proper source buffer based in the current distance of the enemy at an offset that will place the ship in the desired vertical position, always copying a constant number of bytes to the P/M area.

Then, I added up/down and left/right movement using a joystick. To get a better proof of concept, I drawed a very simple cabin with a front window and two more windows at the sides. I used CIRCLE and PAINT statements for that, and more CIRCLE statements for the gun sight. The idea was to have some areas of the screen where the enemy ship could hide to add difficulty to the game.

RANGER prototype 1

Space Ranger prototype.

What it is not seen in that screenshot are the lasers. To speed up things during the game, I decided to draw them during the game setup and hide them using the same color as the background. When fired, I just have to change the color register into a bright one and then to black again. That forced me to use GPRIOR hardware register to select which objects wll be in front of others. I selected the one where the order is PF 0-1, P/M 0-3, PF 2-3 and finally BAK. Using this order, I could draw the cabin in one playfield color and the sight in another, and both will be in front of the players. So, the third playfield register that was used for the lasers would be always behind the enemy ships, no mater its color (visible or not).

At that time, I had some of the game rules in mind: to have an enemy ship moving arround that must be chased. The X and Y coordinates were based on random values, but the Z coordinate for the distance was a defined as a formula: If the X and Y coordinates are small enough, the distance will decrease, but if they are not, the distance will increase. I defined that the enemy must be close enough to be destroyed, so P/M collisions could not be used. Instead, I had to check for all X, Y and Z coordinates at the moment of the fire. The score would be the time it took to destroy it, the shorter, the best.

I didn't do anything for a while, because I started another game project. When I resumed this, I went back with fresh ideas: to destroy a fleet of enemies in a sequence, limit the number of shots, limit the time, attacks from the enemies and a simple radar to help the chase. The timer used to limit the time would also used to limit the shots, and the hits from the enemies would also discount some units from the timer. Then, the score changed to be the remaining time, later renamed as "energy" and finally as "power".

At that moment, the length of the game was short enough to fit PUR-120 category, and I decided to add that features in that order trying to remain in the same category, but a graphics redesign was needed (the first prototype was ugly) and I decided to continue using circles to give a special look and feel. That included the animation of the enemy's attack: instead of the use of another player as an incoming bullet, I used the same player of the ship to simulate a kind of expanding magnetic wave.

After some hours of coding, I was reaching the limit of the program size and I had the following:

RANGER prototype 2

Space Ranger advanced prototype.

Then it happened something unexpected: Jeff Piepmeier submitted to the contest an entry called Interceptor. It was an adaptation of an old arcade game with the same name, and looked like this:

RANGER prototype 2

Interceptor by Tomohiro Nishikado (Taito, 1976) and Jeff's tenliner adaptation using TurboBASIC XL.

I was surprised on how similar to my game it looked like. But it has three enemies moving around at the same time with their own rules. It was so simple and interesting that I almost quit my project. After some days, I decided to continue with it, moving to EXTREM-256 category in order to add the remaining features from my list. I also wanted to include many enemy ships at the same time, but the change to the core of the program would be huge and most of the time only one of the enemies would be visible and the others would be out of the screen, so I discarded the idea.

My first attempt with the radar was to display a set of arrows near to the sight, pointing to the direction of the enemy ship, but I finally decided to use one of the remaining players as the background of the radar's screen, and a missile as the pointer to the enemy location. As I had to redesign the panel of the cabin to allocate the radar, and that took a big amount of code, i.e. programming space, a code optimization was needed, and it is as follows.

There were too many POKEs and MOVEs of data to initialize the P/M graphics and bitmap buffers, so I designed a way to store them as binary data in a string and programmed pair of loops that read that data and perform the required action. The first one was designed to initialize with a solid block in memory, and it is used to clear P/M memory area, to set up the block shape of three players (radar and others) and to set the horizontal position of every player and missile out of the screen and their width as normal. The second one copies chunks of data to specified places in memory, to set PMBASE, initialize the color palette, store the bitmaps in each of the assigned buffers, change some of the players widths, turn on P/M graphics, objects priority, print the game name in the title screen, modify the display list to turn the bottom bitmap lines into a text line, turn on the display and set the horizontal position of the static block-shaped players.

Some of the SFX I added with the new extra space were a blinking dot in the radar, the blinking green sight when the enemy is in range, blinking circles at the right side of the panel to signal when the enemy will fire, laser shots and the enemy explosion sequences, and many sound effects. What I couldn't add, was the blinking effect to the circles at the left side of the panel to signal when the level of power was too low, because no more room was available for the required code.

Finally, I decided to change the gameplay a bit, because it was not hard enough to hit an enemy ship. The enemy ship moves in a random 2D direction in a way that deltas for both X and Y axes could be -1, 0 or 1, and sometimes the enemy just stoped if the delta values for both axes had was 0. After the change, only -1 and 1 are the valid values, so the enemies move diagonally and never stop.

Download and try

Get the RANGER.ATR file and set it as drive 1 in a real Atari (or emulator). Turn on the computer and the game should start after loading. A joystick in port 1 is required.

The code

The abbreviated BASIC code is the following:

The full and expanded BASIC listing is the following. All the constants and expresions between constants from the compacted form has been replaced with the actual values, and the READ and DATA statements were removed.

graphics 31
Sets graphics mode 15+16, 160x192 pixels without text box.
poke 559,0
Turns off the screen.
color 3
plot 45,149
drawto 80,80
drawto 115,149
Draws the lasers using COLOR 3. When they are fired, it is only required to change the color register.
color 2
circle 80,80,1
circle 80,80,8,15
Draws the gun sight using COLOR 2. This register is modified to signal when the enemy is in range.
color 1
circle 80,176,83,25
paint 80,152
circle 80,152,30,13
paint 80,146
Draws the base of the panel using COLOR 1.
color 0
plot 66,143
drawto 94,143
drawto 94,173
drawto 66,173
drawto 66,143
paint 67,144
Removes a rectangle for the radar. All P/M graphics should appear behind the objects drawn in COLOR 1 and 2, so it is needed to clean a space to show a background done using a player.
color 1
Returns to COLOR 1 to continue with the ship.
for n=0 to 3
This is a loop that it is used to draw thick lines for the screen elements.
  t=n>1
Flag used to split the loop in two for some elements.
  circle 80,175,70+n,158-t
Draws main arc of the cabin.
  plot 159*t,n-2*t
  drawto 30+100*t,55
Draws the supports.
  plot 42+n+71*t,153
  drawto 45+(71-1)*t,151
  drawto 44+n+71*t,153
Draws the laser cannons.
  circle 80,158,27+n,17+n
Rounds the radar screen.
next n
color 0
for n=0 to 2
  t=20+10*n
  for m=0 to 1
    a=170-n*2
    circle t,a,2+n
    paint t,a
    t=159-t
  next m
next n
Draws 3 circles at every side of the radar. Behind them there will be another player at each side.
dim w(1)
w(0)=3
w(1)=-6
This array is used to update the distance of the enemy on every loop. It gets closer quickly than it goes further. It is also used to get the sign for the enemy movement.
a=adr("{data}")
Lots of data for POKE and MOVE used to initialize graphics, P/M, colors, etc.
b=dpeek(a)
while b
  poke b,peek(a+2)
  move b,b+1,dpeek(a+3)
  a=a+5
  b=dpeek(a)
wend
This routine clears or fills portions of memory using the first part of the binary data. Blocks are composed by a memory address word, a byte value and a length word. It uses MOVE to propagate the first byte the required times. The loop ends when the address for a next block is zero.
a=a+2
Skips the 1st end-of-data mark.
b=peek(a)
while b
  move a+3,dpeek(a+1),b
  a=a+b+3
  b=peek(a)
wend
This routine moves blocks of data from the second part of the binary data. These blocks are composed by a length byte, an address word and the data itself.
dpoke $9BFF,$1010
This is a special DPOKE. It couldn't be stored inside the binary data, because the address has the byte $9B which is the end-of-line character CHR$(155). The program couldn't be ENTERed because that line would be splitted.
d=adr("{data}")
Binary data with messages for the score line.
do
Begin of the main loop.
  while strig(0)
  wend
Waits for the joystick trigger to be pressed.
  k=5
Sets the number of enemies as 5.
  e=999
Sets the initial power as 999.
  c=0
Initializes the global counter. Many features depends on this counter.
  f=0
Disables the enemy's attack.
  move d,$BCD0,20
Puts the initial counters on screen.
  poke 708,$86
Sets the light blue color of the ship. When it is out of energy, it becames dark blue. Also, when the ship was shoot by the enemy, it blinks to yellow.
  while 1-strig(0)
  wend
Waits for the joystick trigger to be released.
  poke 77,0
Disables the attract mode.
  while k and e>0
Game loop. The game will be over when there are no more enemies or when there is no more energy.
    poke $BCD8,208+k
Updates the remaining targets' counter.
    x=rand(400)-200
    y=rand(400)-200
    z=399
Sets the initial position of a target.
    h=0
Resets the flag for a destroyed enemy.
    repeat
Begin of the round loop.
      if rand(15-f*2)=0
        u=sgn(w(rand(2)))
        v=sgn(w(rand(2)))
      endif
Changes the target's direction of movement at a random moment. It gets more frequent a change as the shoot timer rises.
      c=c+1
Increases the global counter.
      e=e-(c mod 10=0)
Decreases the energy level every 10 loops.
      s=stick(0)
      x=((x-(s&8=0)*2+(s&4=0)*2+u+600) mod 400)-200
      y=((y-(s&2=0)*2+(s&1=0)*2+v+600) mod 400)-200
Calculates the new X and Y coordinates of the enemy according to its current coordinates, the joystick position and the current speeed of the enemy. This allows enemies to warp around at the limits. The range is -200 to +200 on both X and Y axes, while the screen displays only -79 to +80 respectively.
      z=z+w(abs(x)<20)+w(abs(y)<20)
      z=(z-8*(z>=400))*(z>0)
Calculates the new distance of the enemy using its current distance and its coordinates. It gets closer if X or Y is within a range for the respective axis. Then it restricts the distance to be positive and not larger than the max (400).
      sound 1,70-2*s,8,2+(s<15)*2
Plays a continuous sound based on the joystick position. It is louder when the joystick is not in the released position.
      if abs(x)<80 and abs(y)<88
If the enemy is within the visible range:
        t=$9890+y+256*(z div 100)
Calculates the vertical position of the enemy.
        pause 0
Waits for a VBLANK to avoid glitches.
        poke $D003,x+125
Sets the horizontal position of the player.
        move t+32,$9720,176
Sets the vertical position of the player.
        poke 707,15+(c mod 16)*16
Sets the next color of the enemy. It rotates over all HUEs with the brightest LUM.
      else
If the enemy is out of the screen.
        poke $D003,0
Remove the player from the visible area.
      endif
      t=$93BE-y div 15
Calculates the vertical position of the point in the radar.
      poke $D004,127+x div 15
Sets the horizontal position of the point in the radar.
      move $93AE,$93AF,32
      poke t,85*(c mod 10>5)
Cleans the missile column's portion over the player, then puts a point only for 4 of every 10 cycles, so it seems to be blinking in the radar.
      f=(z<100)*(f+(c mod 6=0))
If the enemy is close enough, increases the enemy timer every 6 cycles. If the enemy leaves the range, the counter resets to zero.
      if f
If the timer is greater than zero:
        if f<6
If the timer hasn't reach the limit yet:
          sound 2,78-f*10,10,f*2*(c mod 6=0)
Plays a sound in channel 2 for 1 cycle every 6, so it sounds like a single "beep". The pitch and volume depends on the value of the counter.
          poke 706,$AA-2*66*(1-c mod 2)*(f>3)
Changes the color of the 3 circles at the right of the console when the timer is half the way, creating a blinking effect as an attack alarm.
        else
If the timer has reached the highest value, performs an attack if the enemy:
          for n=0 to 15
Loof for the attack sequence:
            sound 2,200-n*10,6,n
Plays a tone and then it increases the pitch an volume on every cyccle.
            poke 707,15+n*16
Changes the color of the enemy.
            pause 0
Waits for VBLANK to avoid glitches.
            t=y+6*n+6
            if abs(t)<80
              dpoke $9770-t,$817E
            endif
Draws an arc over the enemy if it would be inside the screen.
            t=y-6*n-6
            if abs(t)<80
              dpoke $9770-t,$7E81
            endif
Draws an arc under the enemy if it would be inside the screen.
          next n
End of the animation.
          sound 2,1,8,14
Plays an impact sound.
          poke 708,47
Changes the color of the ship to a light color.
          e=(e-50)*(e>50)
Decreases the energy in 50 units. Set it to zero if it was less than 50.
          f=0
Resets the attack timer.
          pause 2
          sound 2,100,8,10
Waits a moment and then changes the impact sound.
          pause 0
          poke 708,$86
          sound 2,0,0,0
Waits a bit more and then restores the ship color and turns off the impact sound.
        endif
      else
        sound 2,0,0,0
      endif
Turns off the beep of the timer if the enemy is no longer in range.
      poke 709,40+(162+4*(c mod 2))*(z<100)
Changes the sight color to a blinking green if the enemy is close enough. Else, restore it to red.
      if strig(0)=0 and e>24
If there is enough energy, fire the lasers if the trigger is pressed:
        for n=0 to 15
Loop to animate the lasers:
          poke 710,225-15*n
Change the color of the COLOR 3 register from a bright one down to black depending of the loop step.
          sound 0,n*13,10,15-n
Plays a tone from a high to a low pitch, from a high volume to mute.
        next n
        e=e-25
Decreases the enery counter in 25 units.
        if abs(x)<4 and abs(y)<3 and z<100
The enemy was hit if it is close enough and within the range of its size. Then:
          sound 0,97,8,8
Plays an explosion sound,
          move $9C8F+y,$9700,256
Changes the bitmap of the player in the screen into an explosion.
          h=1
Sets the flag for the impact to an enemy.
          k=k-1
Decreases the number of remaining enemies.
          for t=0 to 7
            pause 0
            poke 707,rand(16)*16+14-2*t
          next t
Loop to animate the explosion. It just selects a random color within the loop and assigns it to the player, but with a decay in the bright.
          poke $D003,0
Moves the player out of the screen.
          sound
Turns off all the sounds.
        endif
      endif
      t=e
      for n=0 to 2
        poke $BCE3-n,208+t mod 10
        t=t div 10
      next n
This routine prints the current 3 digits of the power. It is not a single PRINT because I want it in another color.
    until h or e=0
The round ends when an enemy is destroyed or when there is no more energy.
  wend
End of game loop.
  t=k>0
  move d+20+9*t,$BCD0+10*t,10
  poke 708,$86-4*t
  poke 709,202-164*t
  sound 1,50+t*150,12,10
  pause 80
  sound
Depending of the number of remaining targets (enemies), prints the corresponding message, changes the color of the ship and the sight, and plays a buzzer of different pitch for a moment.
loop
Returns to main loop.

Return to my 10-liners page.

© 2017 by Víctor Parada - 2017-04-04 (updated: 2017-05-11)