SpeedSki
Dub Scroggin
SpeedSki takes VIC BASIC to its limits. Like most good action games, SpeedSki is easy to learn and hard to master. What's equally impressive, the program runs extremely fast, and creates an excellent, realistic physical challenge. It sounds and feels like skiing – complete with jumps, trees, fences, and an ever-changing pathway.
Also, if you're interested in programming games in VIC BASIC, the author provides a complete explanation of how the program works. He discusses the techniques which permit such amazing execution speed.
With five skill levels, for one to four players, on any unexpanded VIC. The world's champion SpeedSkier (the author himself) has managed to achieve a score of 168 during a five-run series. Do better than that and you'll be the new record holder.
"SpeedSki" is a fast, action VIC-20 game that fits in standard memory and makes full use of the VIC's color and sound capabilities. It is controlled from the keyboard and provides up to five rounds of play for one to four players, allowing each to select from any of five skill levels.
The game was designed around one central concept – speed. Every effort, short of machine language, has been used to make the game run as fast as possible without sacrificing too much realism. The result is an exciting game requiring concentration and practice. It's easy to learn the basics at skill level one, then step gradually up to level five, but mastery will take a lot of practice.
Avoid The Hazards
The object is to steer a skier through 10 gates, while avoiding the hazards posed by trees and fences. The optional jumps will improve your time. The best possible time, about 29 seconds, can be achieved at skill level five by avoiding every hazard, hitting every gate, and taking every jump. But getting this best time is not easy, even for an expert. I've played the game several hundred times and have made a perfect score only a handful of times. And I'm the greatest SpeedSki player in the world. The fact that as I write this there are only three players in the world could have something to do with this, of course. The other two are my daughter, who is second best in the world, and a friend's son, who has played only once. My best score for a five-run series is 168. Beat that score and you'll be the world's champion SpeedSki player.
You should take the jumps whenever you can – they not only move you ahead, they also take you over trees you might otherwise hit, and increase your speed. Every time you hit a tree, you move up one line on the screen (to a limit of ten), and you have more time to react to the slope coming up from the bottom. You are also a little farther from the finish line. Whenever you hit a jump, you move down a line (to a limit of three below the center), so you are closer to the finish line, but you must also react faster.
There are a number of REMarks in the program listing as an aid to understanding, but I recommend they not be typed in because of the memory they consume.
Defining Characters
Line 10 prints the title, and line 20 sets the memory limits that are necessary in a program employing user-defined characters. Moving the end of memory indicators hides a section of memory from BASIC, so this section can be used for storing the user-defined character values.
Try this: print FRE(X) and hit RETURN. Then type POKE 56,28: POKE 55,250: POKE 52,28: POKE 51,250 and hit RETURN. Now type FRE(X) and hit RETURN, again. You'll see the difference. BASIC has been fooled into thinking the end of its memory is closer than it really is, and you appear to have lost about 260 bytes of memory. Line 20 also sets the screen and border colors to white and white, like snow.
Line 30 reads X, a memory location in the protected area set up by line 20. If X is 0, then all data has been read, and control passes to the instructions starting in line 70. Otherwise, line 40 reads the values to be placed in X and the seven following bytes, and POKEs these values in. For instance, line 30 reads "7672". Line 40 then reads "16" and POKEs 7672 to 16. Then it reads "56" and POKEs 7673 to 56, then 7674 to 56, and so on.
Control then goes back to line 30 where the next value of X is read in and tested. The final data step contains a 0 for the value of X following the eight values of Y. So when all the data has been read in, line 30 ends this part of the program.
Players And Skill Levels
Lines 70-90 print the directions. Note that the symbol "T" in line 70 means to press the Commodore flag key, and then hit the "T" to underline the title. Line 100 is used for inputting the number of players and also for rejecting bad input. A value outside the allowed range passes control back to line 70; the screen is cleared, the instructions are reprinted, and you are asked for the number of players again. Line 110 accepts the number of rounds desired and rejects bad input in the same manner as line 100.
Line 120 initializes the values of R (the number of the present round) and P (the number of the present player). Lines 130-140 prompt the player skill levels, and line 150 accepts the player choice as a string variable, A$. Lines 160-200 assign values to S$ based on the skill level input, and line 210 converts A$ to the numeric variable SK. It then uses SK to establish a value for RN, which will control the number of trees printed.
The number of trees is tied to the skill level, so that the higher the skill level, the more trees there will be. If you'd like more trees, change the "1" to a larger number, but no more than 5. If SK is not an integer, or is outside the range of 1 to 5, line 210 rejects it. Moving the cursor up ten spaces and passing control back to line 130 makes it appear that the program does nothing but sit there until a correct input is given.
Speed Versus Obstacles
Line 220 establishes a new value for SK to control the speed of the program – faster for higher skill levels. Line 230 POKEs 36869 to 255 and causes the user-defined character set to be used instead of the normal set. This may cause some problems with debugging.
If an error is present after this step, the program will stop, but all you'll see on the screen will be garbage with an occasional skier or tree thrown in. If this happens, hit the CTRL and RVS keys, then type POKE 36869, 240 and RETURN. All that garbage will suddenly make sense. Line 230 also clears the screen, sets the volume, and establishes S as the noise generator.
Line 240 prints the trees on the screen for the initial setup. Each time through this loop, a random value "L" between 1 and 19 is calculated. Then a fence section is printed on the left, a tree is printed at TAB(L), and a fence section is printed on the right.
The initial value of B is set to 7910 in line 250. This is the location of the skier in screen memory. C is the difference between the screen map position and the color code map position. F is the POKE value for the skier figure; the POKE value will be 55 when he's going to the left and 53 when he's going to the right. The last three statements of line 250 insure that the player is not faced with the no-escape situation of having trees directly in front of him at the start of a run.
Line 260 POKEs the flags of the first gate onto the screen, and line 270 prints the level that was determined in lines 160-200. Line 280 puts the line between the flags for the first gate, and line 290 sounds the warning tones to let you know it's time to start. Just after the last tone, line 300 sets the timer. Line 310 then waits for you to press a key. If you don't hit a key for a while, that's okay, but the timer is running. You should use the time that the warning tones give you to plan your course through the first gate and then take off as soon as the last tone sounds.
Line 320 starts the main program loop. If SK is not zero, then the computer counts to SK before proceeding. This time delay, remember, is tied to the skill level to start with, but it may be reduced by hitting the jumps.
Skier Movement
If F is 55 in line 330, the skier is going left, and a track is POKEd in behind him using a POKE value of 58. If not, he's going right and the track's POKE value is 59. The track is handled in line 340.
Lines 350 and 360 are the keyboard control steps. If PEEK (197) – which is the memory location that contains the current key pressed – is 29, then the key for going left has been pressed. D will later be used to produce movement to the left; F is set to the figure for going left; and S, which is the noise generator, is set to 245. If any other key is pressed, or even if no key is pressed, then the skier will be going to the right, and the values needed for D, F, and S are set by line 360. You'll notice this slight change in sound when you change directions; it should sound like wind.
Gates And The Finish Line
G is incremented in line 370. If it's less than 28, control passes to line 410, because no gate or finish line is required. Otherwise, G is reset to 0 in line 380, and E, which counts the gates, is incremented. If E is 10, a finish line is printed and control passes to 460. Line 390, which causes the program to end, is executed only if the skier is past the finish line. If E is less than 10, then a random value between 2 and 11, inclusive, is calculated. A gate is then printed starting at TAB(X), X being the random number just calculated. Control then passes to line 460.
If no gate or finish line needs printing, control passes from line 370 to line 410, skipping all the above to reduce the time required for a pass through the loop. If G is 10, then line 410 prints a jump at TAB(X), X now being a random number between 4 and 13, inclusive. Fence sections are also printed at the left and right sides of the screen.
Line 240 decides whether a tree will be printed using the value of RN that was established in line 210. For skill level five, RN will have a value of .6; if a random number is more than this, no tree is printed. This means a tree will be printed roughly 60 percent of the time. For the lower skill levels, this probability is reduced so that the lower the skill level, the fewer trees there will be. If no tree is to be printed, line 440 prints only the fence sections. Otherwise, line 430 prints a tree at TAB(L), L being a random value between 1 and 19, inclusive.
If PEEK (B) in line 450 is not 32 (a blank), then the skier has run into something and control passes to line 500 to find out what the skier has run into and what to do about it.
The Illusion Of Motion
Line 460 POKEs the skier's location blank, then calculates a new position by adding the value of D (determined in lines 350 and 360) to B, the skier's location. It then POKEs the appropriate figure into that location. Essentially, the skier is placed on a horizontal line on the screen and is allowed to move only back and forth on that line. However, the screen is scrolling upward beneath him, so the illusion of forward motion is created.
The movement taken care of, control passes back to line 320 for another pass through the main loop. This loop, lines 320-470, has been kept as small as possible in order to minimize the time required for each pass through it. I have tried to be very stingy with time in this section, figuring that even one instruction repeated a few hundred times adds a lot of potentially unnecessary time.
Flags And Fences
Line 500 is reached when line 450 detects that something has been struck. This entire section was originally a part of the main loop, but removing it from the loop and replacing it with the single statement in line 450 produced a significant increase in speed. Line 500 checks to see if a gate was hit. If so, it sounds a high tone to let you know you got credit for the gate, then increments H, the number of gates hit, and passes control back into the main loop.
Line 510 checks to see if a finish line was struck. If so, H is changed to the number of gates missed, the elapsed time is placed in TM, and control passes to line 640 to end the run.
If a flag was hit, line 520 sounds a low tone to let you know you were close but get no credit for the gate. Control then passes to line 570.
If a jump wasn't hit, line 530 transfers control to 570. Lines 540-560 handle the jumps. The skier is moved two spaces horizontally in the direction (D) that he was going, the value of G is stepped up to bring the next gate closer, the screen is skipped up ten spaces, and the value of SK is reduced, which results in a slight increase in speed. The skier is moved down one line on the screen unless he is already three lines below the center. Moving him further down makes seeing what is coming very difficult, but if you'd like to try it, one way is to put a larger negative value here in place of the -3. If, for instance, you put a -10, the skier will move down every time you hit a jump. Another way would be to start the skier at a lower position on the screen. This would require simply changing the initial value of B in line 250.
Line 570 checks to see if a fence section was hit. If so, it changes your direction and passes control to 610 for the sound effect. Getting out of the fence may take a couple of tries. If a tree was struck, then line 580 changes the figure to a cross and passes control to line 600. Line 590 POKEs S-3 to 0 in case it was set by hitting a flag in line 520, then passes you back to the main loop.
Shaking The Screen
Line 600 causes the screen to shake a bit when you hit a tree. The inner loop here counts from 3 to 7, then from 4 to 6, and stops at 5. POKEing these values into location 36864, which controls horizontal centering, shifts the screen rapidly back and forth around the normal value of 5. Line 610 increments OS, the number of objects that have been struck, and also controls both the sound effect and the changes in color of the cross in line 580. If a tree was struck, line 620 moves the skier up a line, adjusts the value of U, and checks to see if U has reached its limit of 10. If so, the run is aborted and you are given another chance. If not, line 630 passes control back to the main loop.
Line 640, the finish line sound effect, is reached only if the finish line was detected in line 510. Lines 650-660 print out the statistics on the run just completed and finish off the sound effect. Line 660 also POKEs 36869 back to its normal state so that the scores can be printed.
Line 670 computes the player's cumulative score, adding the score for the run just completed to his total from previous rounds, and also prints the round number. Line 680 then prints the cumulative scores for all the players, and line 690 reinitializes for the next run.
Downhill racing on the VIC-20 in SpeedSki.Line 700 increments the player number; if the last player hasn't gone yet, control passes back to line 130 to start another run. If the last player has just gone, line 710 increments the round number and checks to see if the game is over. If not, the player number is changed to 1 and a new round is begun. Otherwise, line 720 lets you know the game is over. It then turns the cursor white.
To rerun the program, hit RETURN, then type RUN and hit RETURN again. The reason for this odd procedure: it isn't visible because it's white on a white background, but some garbage has been picked up during the run and lies on the same line as the cursor. During the program this garbage is disposed of by the loop that rejects bad input for the skill level. There is no such loop at the end of the program, though.
Okay, time to get the program typed in, then hit the slopes. There's a world record waiting to be broken. Good luck.
Variable Listing
NP | Number of players |
NR | Number of rounds |
R | Present round number |
P | Present player number |
S$ | Slope title |
SK | Time delay factor in main loop |
RN | Controls probability of a tree being printed |
S | Noise generator (36877) |
L | Random variable used to position trees |
B | Skier's location |
C | Difference between screen map and color code map |
F | Skier figure |
TI$ | System clock |
D | Direction (1 or -1) to be added to skier's location |
G | Counts spaces between gates and jumps |
E | Counts gates |
X | Random variable used to position gates and jumps |
H | Counts gates hit |
TM | Elapsed time for run |
U | Controls vertical movement of skier on screen |
OS | Counts number of trees and fence sections struck |
SC | Player's score for a run |
Z(P) | Player's cumulative score where P is the player number |