Copyright © 2018 by Víctor Parada
This is a little game for the 2018 NOMAM's BASIC 10-liners Contest. This program fits in the "stock" PUR-80 category, and it was written using Atari BASIC for the 8-bits ATARI XL/XE. Development started on 2018-02-05, and it took 1+1 days. The final version's date is 2018-02-06.
UPDATE: It obtained the 10th place of 24 entries in the category.
Just repeat what Uncle Simon says and beat him in his 9 rounds, each harder than the previous one.
Uncle Simon waits for you to be ready. Press the joystick button when you are. | |
Uncle Simon plays a single tone and waits for you to repeat it. Use the joystick and move it in the proper direction once. | |
Each time you repeat his tones, he adds another one to the sequence. You must remember the complete tune. | |
The first round's tune has only 5 tones. When you complete the round, a new round begins. | |
Every round is longer and faster than the previous one. You must be aware! | |
If you forget a tone, you lose the game. You can see how far did you go in the challenge. Press the joystick button to face all the challenge again, with new tunes. If you hold the button, the first tone will appear immediately. | |
Only if you could follow all 9 tunes, you'll beat Uncle Simon and win the challenge. Press the button to try to beat him again. |
The new rules for the PUR-80 category in the 2018 edition of the contest says that only "stock" BASIC dialects could be used. For the Atari XL/XE meant that Atari BASIC must be used. Neither TurboBASIC XL nor any other flavor of BASIC are allowed for this category.
TurboBASIC XL is an upgraded Atari BASIC, faster than it and with many featured added, like automatic initialization of variables and arrays at start, structured programming using REPEAT
-UNTIL
, WHILE
-WEND
and IF
-ELSE
-ENDIF
, memory management using MOVE
or DPOKE
and other simple statements that performs a small task which in Atari BASIC many statements are required, like a single SOUND
without parameters to turn off all the audio channels and PAUSE
to give accurate timming to a game. Line numbers are required to enter the code, but it is possible to write full programs without a GOTO
statement.
Without TurboBASIC XL, it looks like instead of 800 bytes (10 lines of 80 characters), only 300 are available. And 10 lines seem a not enought number as destinations of the flow control in a language dominated by the GOTO
statement. This turns into a very different challenge. Good bye high quality games! Good bye player/missile animations!
I was tempted to try some ideas to replicate advanced features of TurboBASIC XL in Atari BASIC. The first one was to change internal memory pointers of Atari BASIC to make it believe that strings were in any place of the RAM/ROM and then to allow fast memory movements using string variables manipulation like substring assignment, but that required too many statements and expressions that took more than 25% of the available space just to initialize that. Take in count that a single A=DPEEK(88)
statement in TurboBASIC XL needs to be written as A=PEEK(88)+PEEK(89)*256
in Atari BASIC, requiring 23 bytes instead of 11. A single DPOKE
statement has to be replaced by two POKE
statements in Atari BASIC, each with an ugly expression to find their respective byte value as a parameter.
To reduce the number of statements required to be the first statement of a line when it is the destination of a GOTO
, FOR
-NEXT
loops could be used for iterations, and ON
-GOTO
could be used instead of IF
-THEN
, which enables a simulation of the ELSE
of TurboBASIC XL instead of another split of a line and the use many lines for those contitions with two alternative blocks of statements.
As a proof of concept, I started to rewrite COPIÓN, an old game I wrote in the 80's, which is just a colorfull SIMON-like game.
COPIÓN, a SIMON like game
As graphics mode 2 (ANTIC mode 7) provides texts in 4 colors over a background of a 5th color, it seems to be the most practical example of a simple game to program. Also, as a tenliner is a kind of mini-game, I thought about some simple rules change to make it dynamic and interesting, introducing rounds starting short and slow but with increasing length and speed, with an end. A whole gameplay should not last more than 5 minutes, where you can win the challenge or lose if you fail... no fails are allowed!
The first thing I discarded was the redefined charset. In TurboBASIC XL only 2 MOVE
and a single POKE
statements are required to enable it, but in Atari BASIC two slow FOR
-NEXT
loops with POKE
and PEEK()
took valuable space in the source code. So I decided to distribute the text of the game in 4 regions in the screen and give a different color to each region. I also decided NOT to use POKE
statements in this game, forcing me to use, for instance, SETCOLOR
instead of to modify the hardware PF color registers.
The PF register for each text had to be selected in a convenient way. For example, the number of tones of the current round must be printed and could have more than 1 character, so the register for the default color for numbers should be assigned to that text group. The other important text group was the one with the round number, which could be printed using an expression to change the number into another ATASCII code, and that must be the one which uses the fewest number of chars in the expresions. The remaining ones, i.e. the title and the credits, were assigned randomly as they were static. Then, the respective position of the PF registers in screen are:
2 1 0 3
The name of the game for the title changed from "COPION" to "SIMON", then to "MINI SIMON", and it finally stayed as "UNCLE SIMON" to define a clear objetive and be consistent with the rules of the game. For the credits, I just left "NOMAM 2018".
As there were many text strings to be printed in different screen positions, I decided to use a FOR
-NEXT
loop and read texts and positions from DATA
. DATA
statements has the advantage in Atari BASIC that it could be placed anywhere in the listing, buat it will always be read in sequential order as expected.
The next step was to create the different game loops. I decided to start the main loop at the beginning of a line, as the game flow might return from different points of the code with GOTO
or THEN
and it might also abort when the player fails.
The first working version of the game required more than 1000 bytes of abbreviated code, but as it was in development stage, each statememt has its own line, including the data. After some rework, I could reduce it to about 850 bytes, but 12 lines of code were needed.
First working prototype of the game
One of the way to reduce the number of lines in conditional loops without the use of GOTO
is using this special form of FOR
-NEXT
loop:
FOR X=0 TO 0 STEP 0 ... X=(condition) NEXT X
When the condition is true, the loop finishes. This works like the REPEAT
-UNTIL
block of code, except that it needs more bytes in the listing, but it is posible to use more than one loop on a single line or to span a big loop in many lines of code. I used this technique 3 times in the code: One to wait for a valid player's movement, one to wait for the joystick to be released, and one to wait for the joystick button to start a game. Then I put a 4th one as the main loop of the game, saving another line number.
More bytes were saved by removing some of the messages in the screen, like the initial values of the round and tones counters (both as zero), and by a merge of both effects routines in just one subroutine with a parameter to identify the kind of action to perform. Then I found that the same subroutine could be used to initialize the screen colors at startup, saving some bytes more.
As I could reduce the code to 10 lines and also went below 780 bytes, I decided to add a simple end of game message in addition to the sounds. As there was only one point of the code where the message would be printed at the end of the game, I set it up the string variable with the pesimistic word which would be printed somewhere in the screen when the player loses, but if the player wins, then the string should be changed to the optimistic text. Initially I set up the variable using a direct assign, the pesimistic one before the main loop and optimistic one just after the loop. I decided to print the message in the center of the screen, so I had to move all the elements some spaces away from the center.
I had to accomodate the DATA
statements again to fill free space at the end of the lines. While doing that, I noticed that the 4th loop using the special form of loop could be changed back to the BASIC standard because the first statement was already at the start of a line. The only problem was that the optimistic message was asigned as the last statement of a line just after the NEXT
statement, but that could be solved using ON
-condition-GOTO
, which lets BASIC continue executing statements after it in the same line if the condition is false.
Finally, in a new organization of the data, I decided to remove those POP
statements I added to clean the FOR
-NEXT
stack and are left behind when the player failed a tone and a GOTO
transfers the control outside of the nested loops, because the following statement would be a RUN
, which includes the reset of that stack. I also changed the direct assignment of the end of the game messages to more READ
statements, just to put all the text in the form of DATA
.
When the game was ready, I noticed something that I forgot. As the game uses only the joystick as an input device, it is possible for the attract mode to start in the middle of a game. The only way to disable that is by storing a 0 at a given memory location, so I had to include POKE 77,0
even when I said I won't include POKE
statements.
An extra activity was to write an AUTORUN.SYS file to automatically load and run the game at boot time, because DOS 2.5 does not have the ability to process AUTURUN.BAS files for Atari BASIC. There are many solutions out there, but I wanted to keep it simple ;-)
Get the SIMON.ATR file and set it as drive 1 in a real Atari (or emulator). Be sure that the BASIC cartridge is installed (no OPTION key on XL/XE computers) and then turn on the computer. The game should start after loading. A joystick in port 1 is required.
The abbreviated BASIC code is the following:
The full and expanded BASIC listing is in freeform text. All DATA
statements were packed and moved somewhere else in the code to fit the required number of lines and columns in the abbreviated form.
0 |
LINE 0: Initialization of the screen and game variables. |
graphics 18 |
Sets graphics mode 2+16, a screen of 20x12 big 4 colours text without a textbox in the bottom. There are four elements in the game and each has its own index value from 0 to 3. |
dim c(3),s(3),t$(5),m(15) |
Defines de required arrays and string: C(0-3) has the color of every tone's text. S(0-3) has the pitch of every tone. T$ contains a piece of text to be displayed in screen. N(0-15) is the list of tones played in a round. |
e=0 |
E is the effect flag for the effects subroutine. E=8 plays a sound and brightens its text and E=0 turns them off (default). |
for r=0 to 3 read c,s c(r)=c s(r)=s gosub 8 next r |
Reads the list of colors and pitchs for the 4 elements of the game, then calls the effects routine to set the default color of each element, based on the value of the R variable. |
data 8,121 data 5,85 data 2,60 data 13,173 |
List of colors and pitchs for each of the elements. NTSC and PAL look very different! |
1 |
LINE 1: Places the elements in the screen, initializes the counters and waits to start. |
for z=0 to 5 read a,b,t$ position a,b ?#6;t$ next z |
Reads the 6 messages to be displayed on screen from DATA and prints them in the specified position. |
data 8,1,UNCLE data 8,2,SIMON data 2,5,ROUND data 14,5,TONES data 8,9,NOMAM data 8,10,/2018 |
For each message, the data is horizontal position, vertical position and text. Messages already contains the respective playfield color information for the respective PF register changing some bits in the ATASCII code, as it could be seen at the abbreviated listing. |
read t$ |
Reads the default end of game's text, which is the pesimistic one. |
data LOST!,-WIN- |
End of game messages. The optimistic one is read only when the player wins the challenge. As well as other messages, they also contain the PF information to select a different color register for each of them. |
gosub 9 |
Calls the routine to wait for the player. |
n=1 p=181 |
Initializes the game counters: N is the current round. P is the delay between notes. |
2 |
LINE 2: Main game loop. |
position 4,6 ? #6;chr$(n+16) |
Prints the round number in the proper color. |
for x=0 to n+3 |
Loop for the tune. |
m(x)=int(rnd(0)*4) |
Asigns randomly the next tone of the tune. |
position 16,6 ? #6;x+1;" " |
Prints the current tones number in the tune. |
3 |
LINE 3: Simon plays the tune. |
for z=0 to 99 next z |
A small pause before Simon start the tune. |
for y=0 to x |
Tune loop. |
r=m(y) e=8 |
Gets the current tone in R and sets up E to show the effect. |
gosub 8 |
Calls the routine to perform the effect. |
for z=0 to p next z |
Makes a pause while the tone is playing. The lower is the P counter, the shorter is the pause. |
gosub 8 |
Calls the effects routine again, but this time it will turn off the tone. |
for z=0 to p/9 next z |
Makes a pause between tones. It is proportional to the length of the tone. |
next y |
End of Simon's tune loop. |
4 |
LINE 4: The player copies the tune. |
for y=0 to x |
Tune loop. |
for z=0 to 0 step 0 j=stick(0) z=(j=7)+(j=11)*2+(j=14)*3+(j=13)*4 next z |
Waits for a joystick movement. Only horizontal and vertical movements are allowed. Each direction returns a specific value: element index plus one. |
r=z-1 |
R has the user's tone. |
5 |
LINE 5: continuation of the player's tune code. |
e=8 |
Sets up variable E to perform an effect. |
gosub 8 |
Calls the effects routine to show the player's selection. |
for z=0 to 0 step 0 z=stick(0)=15 next z |
Waits for the player to release the joystick. |
gosub 8 |
Calls the effects routine again, but to stop the effect. |
on r<>m(y) goto 7 |
If the played tone by the player is not the expected one, breaks the loops and goto the end of the game. |
next y |
End of the player's tune loop. |
next x |
End of the round loop. |
6 |
LINE 6: Whistles! The round was complete. Go to the next one if the player hasn't won yet. |
for y = 200 to 0 step -x sound 0,y,10,8*(y>x) next y |
Plays a whistle. It will be faster at the end of each round. The 8*(Y>X) expression at the end of the SOUND statement turns the sound off in the last iteration of the loop, saving an extra SOUND 0,0,0,0 statement. |
n=n+1 p=p-20*(p>20) |
Update the counters: sets next round number and decreases the pause length for that round. |
on n<10 goto 2 |
If it was not the last round, go to the game loop again. |
read t$ |
Only when the last round was finished, the default end of game text is changed to the optimistic one. |
7 |
LINE 7: End of game routine. |
sound 0,40,12,8*(n<10) |
Plays a bell only if the last round was not finished. This is controlled by the expression in the last parameter of the SOUND statement. |
position 8,6 ?#6;t$ |
Prints the current end of game text (pesimistic or optimistic). |
for z=0 to 500 next z |
A pause to let the bell ring. |
sound 0,0,0,0 |
Turns off the bell (if it was ringing). |
gosub 9 |
Call the subroutine to wait for the player. |
run |
Restart the game. Using RUN statement, the pending loops (FOR -NEXT cycles) of the game when the player selected a wrong tone are discarded. |
8 |
LINE 8: Effects subroutine. |
sound 0,s(r),10,e |
Turns on/off the sound using the pitch for the current element specified by R based on the E effects flag: 8=tone (at volume level 8), 0=silence. |
setcolor r,c(r),4+e |
Highlights or restores the element specified by R, setting his corresponding color register to its defined HUE and the selected bright based on the E flag: 4+8=12=light, 4+0=4=dark (default). |
e=0 |
Just after an effect is applied, the E flag is reset, so the next call is by default to disable the effect for the element R. |
return |
End of the subroutine. |
9 |
LINE 9: Subroutine to wait for a player. |
for z=0 to 0 step 0 z=strig(0)=0 next z |
Loop to wait for the trigger of the joystick in port 1. |
poke 77,0 |
Resets the counter for the attract mode, disbling it if it was activated. |
return |
End of the subroutine. |
Return to my 10-liners page.
© 2018 by Víctor Parada - 2018-02-05 (updated: 2020-08-15)