Surround

Copyright © 2017 by Víctor Parada

Surround

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

UPDATE: It obtained the 6th place of 19 entries in the category.

Description

Try to surround your oponent with your trail. The first one that wins 9 rounds, wins the match. For none, 1 or 2 players!

Instructions

SURROUND title screen
  • For one player against the computer, press Fire in joystick 1 or START.
  • For two players against each other, press Fire in joystick 2 or SELECT.
  • For the computer against itself (bets are open!), press OPTION.
SURROUND round begins When the round starts, the oponents starts moving at the same time, leaving a trail behind them.
SURROUND crash Use the joystick to move arround and avoid to hit the court's wall or the trails. When one player crashes, the other player earns a point.
SURROUND move You could start a round moving in any direction. Of course, no diagonals are allowed.
SURROUND-trails As the trails gets longer, the faster the movement is.
SURROUND game over The first player that wins 9 rounds, wins the match.

Development of the game

Back in 1985, I was at a friend's home with my 800XL for some days. We had many games to play in turns, and probably only one or two games that we could play against the other. We recalled the Atari 2600 VCS days when we could play Combat, Video Olympics and Surround among others. We wished we had the console there, but as we couldn't, I tried to program a simplified version of Surround using Atari BASIC. In a couple of hours, we were playing and laugthing for tons. I added an intro screen and named it "Babosa", because that is the spanish name for a slug, and slugs leave a trail behind them.

BABOSA intro

Babosa's intro screen

 
BABOSA playfield

Babosa's playfield

Last month, I was working in a small Atari project and while I was searching for an old tool I had in one of my ATR images, I accidentally found one of the few ATRs in DOS 3 format I had, and this one contained some of the games I wrote in the mid 80's, and "Babosa" was there. I took a nostalgic look at it and found the following...

The file size was of about 22 sectors (~2700 bytes) and the program had only 44 short lines, full of GOTOs, GOSUBs and IFs. I found it so simple that I figured out that it could be rewriten in Turbo BASIC XL using structured programming (REPEAT-UNTIL, WHILE-WEND and IF-ELSE-ENDIF instead of those, and using statements abbreviation, it could be fitted in 10 lines of code for the NOMAM's 10-liners contest.

I also decided to change some things to optimize the code. I was surprised that Babosa was using graphics mode 3 in full screen (40x24 pixels, 4 colors per pixel, 4 pixels per byte, 10 bytes per line), except for two at the bottom that were turned into graphics mode 2 (ANTIC mode 7) to display the scores. Pixels where put in screen using COLOR and PLOT statements, previously checking for a collision using LOCATE. For the new version, I decided to change that and use graphics mode 12 or ANTIC 4 (40x24 "pixels", 5 colors per "pixel", 1 "pixel" per byte, 40 bytes per line). Here, "pixels" actually are block shaped fonts of 4x8 pixels with bits patterns according to the required color. This way, I only had to PEEK and POKE the pixles in the screen RAM without performing any binary operation. After an hour, I had a working prototype that only used 5 or 6 compressed lines of code.

I wanted to add A.I. ("Artificial Intelligence") to allow a 1 player game mode. It was so simple the change that actually it becomes A.S. ("Artificial Stupidity"), but it's fun to play against it. I had to do some tunning to get enougth surprise from the automated player without it being too much stupid that surrounds itself.

As there was enougth room, I added the same intro that Babosa had, and some special effects ("blinking" and sounds) when a player hits a wall or a trail. I also included the ATRACT mode (screen saver) when the game ends, which Babosa included as it was the behaviour I recall Surround had in the VCS.

To enhance difficulty, instead of making the whole game faster like Babosa was, I decided to reduce the delay after some steps, so the game becomes faster as the trail becomes longer during a round.

Some days later, I realized that the same routine that adds A.I. to the second player could be used with the first player. I forced that action and the first run was fun! I bet for player 1 to win the match as it was already winning, but things changed after 2 rounds. Final count was 8-9, and player 2 won that exciting match. It was very simple to include that game mode as an option in the intro screen, but I had to abbreviate the name of the keys to allow all 3 of them to be displayed in one 20 chars text line.

Download and try

Get the SURROUND.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 for one player game, and another in port 2 for two players game.

The code

The abbreviated BASIC code is the following:

The full and expanded BASIC listing is:

dim p(1),q(1),r(1),s(1),c(1),j(15),t$(36)
Arrays that store the current status of each player:
p(0-1): Current position of the player in the playfield (screen).
q(0-1): Next position of the player in the playfield.
r(0-1): Current moving direction of a player.
s(0-1): Flag indicating that a player hit a wall or a trail.
c(0-1): Score counter for won rounds.
Also:
j(0-15): Constant direction values for every joystick position.
t$: Text for the title and game screens.
j(7)=1:j(11)=-1:j(13)=40:j(14)=-40
Instead of conditional check for joystick movement, we just get the bytes in screen memory that must be added to a player position for each posible joystick direction. 40 for vertical, 1 for horizontal, negative values for left and up. Omitted positions default to zero in the array.
t$="  SURROUND      STA:1P SEL:2P OPT:0P"
Initializes the text string to be used.
graphics 28
Sets the graphic mode 12. This is an ANTIC 4 screen without text window, similar to a graphics mode 0 screen but requires to use channel 6 to print text on it. COLOR statement sets the char to be used with PLOT and DRAWTO statement.
x=dpeek(560)
poke x+26,0
dpoke x+27,$607
Turns the bottom 3 lines of the screen into a blank scan line, a GR.2 text line and a GR.1 text line.
s=dpeek(88)
Gets the memory address of the top-left "pixel" of the playfield, including the wall.
move $E000,$B000,512
move adr("binary data"),$B008,24
poke 756,$B0
Copies the charset to RAM and replaces some characters with the bitmaps for the color blocks.
position 4,21
? #6;t$
Prints the text for the title screen.
plot 20,10
Sets the starting point of the random traces of the intro screen in the middle of it.
repeat
Main (endless) loop...
  c(0)=0
  c(1)=0
Resets the counters for both players.
  repeat
Waits for an option to start a game.
    if d=0
      color 32+rand(4)
      drawto rand(40),rand(21)
    endif
If it is the intro screen, a random trace is drawn using a random color.
    w=peek(53279)
Gets the CONSOL register value: 6=START, 5=SELECT, 3=OPTION.
    y=2*(strig(0)=0 or w=6)+(strig(1)=0 or w<6)
Formula to check which game mode was selected. Gives 2 for a one player game, 1 for two or no players, and 0 when nothing has been selected yet.
  until y
Repeat again until a game mode is selected
  z=y>1
Flag to say that the game mode is for one player.
  d=1
Sets a flag to mark that the intro screen has finished. The next time, the wait for a new game will be during the game over screen.
  repeat
Game loop...
    poke 77,0
Disables ATRACT mode.
    ? #6;"clear screen char"
ATASCII char 125 clears the screen when printed.
    position 3,21
    ? #6;c(0);t$(1,12);chr$(16+c(1))
Displays the game name and current scores.
    color 163
    plot O,O
    drawto 39,O
    drawto 39,20
    drawto O,20
    drawto O,O
Draws the playfield's wall. Here, the O variable is used as a constant zero (default value), because there is a 256 bytes restriction for tokenized lines, and each literal value takes 7 bytes, while a variable name only 1 byte, so we saved 36 bytes. It could be used TurboBASIC's literal %0, which also uses 1 byte, but it'd add 1 char each, obtaining a longer abbreviated line.
    p(0)=s+409
    p(1)=s+430
Sets the initial position of each player.
    poke p(0),1
    poke p(1),2
Displays each player on screen.
    r(0)=1
    r(1)=-1
Sets the initial movement direction of each player.
    pause 30
A small delay at the start of the round.
    l=7
This is the initial delay in number of jiffies between steps.
    t=0
This is the number of steps from the begining of a round.
    repeat
Round loop...
      t=t+1
Increases the step count.
      l=l-(t mod 16=0)*(l>0)
Every 16 steps, the delay is one jiffie shorter.
      pause l
Performs the delay.
      for x=0 to 1
For each player:
        if w=3 or x*z
If this player is controled by the computer:
          p=p(x)
Gets current position of the player. Just to save bytes in the following statements.
          if peek(p+r(x)) or rand(20)=0
Checks if the next stright step would hit a wall or trail, or if it's time to turn (randomly).
            b=-1^rand(2)*(41-abs(r(x)))
Randomly gets one of the posible 90 degrees turns in terms of a memory address delta.
            if peek(p+b)
Checks if the chosen position's turn is not empty.
              if peek(p-b)=0
Checks if the opposite is empty.
                r(x)=-b
              endif
Asigns the opposite turn as the current direction for the next movements.
            else
              r(x)=b
            endif
Assigns the selected turn as the current direction for the next movements.
          endif
If both possible turns where not empty, continue straight ahead.
        else
The player has a joystick in his hands.
          j=j(stick(x))
Reads the joystick position.
          if j
            r(x)=j
          endif
        endif
If the joystick has a memory delta value for the current position, asigns that delta as the next movement direction.
        q(x)=p(x)+r(x)
Gets the next position for the player in the playfield.
        s(x)=peek(q(x))>0
Checks if that position is not empty.
      next x
Repeat the same for the next player.
      sound 0,50+l*15,10,8
Enables a tone. The pitch depends on the current speed (delay between steps).
      for x=0 to 1
Repeats for each player:
        c(x)=c(x)+s(1-x)
Increases the player's counter if the other has crashed.
        poke p(x),3
Puts a trail block where the player was.
        p(x)=q(x)
Refresh the new position of the player.
      next x
Next player, please.
      poke q(0),1
      poke q(1),2
Puts both players in their new positions of the playfield.
      sound
Turns off the tone.
    until s(0)+s(1)
The round finishes when at least one of the players has hit a wall or trail.
    for e=0 to 7
      for x=0 to 1
        poke s+843+13*x,c(x)+16+64*x
        if s(x)
          sound x,90-e*5+25*x,12,8-e
          poke 708+x,16
        endif
      next x
Bells and whistles. Each player has its own audio channel, with a small difference in their pitch. The counter of the round's winner is incremented by one. The color of the player that hit a wall blinks.
      pause 2
A small delay for the SFX.
      dpoke 708,$CA28
Restores both players colors.
      pause 1
Another delay for the SFX.
    next e
End of SFX.
    sound
Turns off the sounds.
  until c(0)=9 or c(1)=9
If one of the players reaches 9 points, the match has finished!
  poke 77,255
  
Enables ATRACT mode (screen saver).
until 0
Return to the game loop.
data by,Victor,Parada,for,NOMAM,10-Liner,Contest,2017
Just a filler... :-)

Return to my 10-liners page.

© 2017 by Víctor Parada - 2017-02-01 (updated: 2017-04-23)