Personal electronic transactions. (evaluation of Commodore 64) Marc-Thomas Clifton.
Personal Electronic Transactions
I am sure you have heard of the Commodore-64. This month we will take a closer look at Commodore's newest machine.
The 64 comes in the same case as the Vic with the case color changed to a neutral grayish-brown. The keyboard is quieter and has contoured keys and a lighter touch. This is the best Commodore keyboard that I have used. Also, the power supply is outside of the 648 greatly reducing the temptation to use it as a portable toaster. (These improvements are also on the recent Vic's.)
The 64 has two joystick ports, which can also use paddles, and one of which can also handle a lightpen. Like the Vic, the 64 has the RS-232 port combined with the parallel port. But, the memory expansion slot is totally different from that of the Vic, so none of the Vic cartridges will work on the 64. An RF modulator is built into the 648 allowing easy TV hookup.
Inside are two new chips to handle the superb graphics and sound capabilities of the 64 and 65535 bytes of memory. When you turn it on, the 64 uses an 8K Basic ROM operating system, giving the user 38911 bytes free for Basic programming. It really does come with 64K of RAM, but the RAM must be selected by POKEint into a control register.
The screen has 25 lines and 40 columns with a border. You can select from 16 colors for the border, screen, and characters. (Eight character colors and be selected directly from the keyboard.) The 64 turns on in a color combination that is similar to the Atari and which is almost illegible on many monitors.
There is already one known bug in the Basic operating system of the 64. Basic cannot handle and SPC of TAB function as the first parameter of PRINT#. For example, PRINT#1, SPC(10) will give a SYNTAX ERROR. To fix this bug, put two quote marks before the SPC or TAB. Example: PRINT#1, " TAB(15). Commodore failed to include any commands to handle graphics, sound or disk operation--just another rehash of Basic 3.0. (I would welcome a cartridge which provides these extensions.)
Border And Screen Color
See Figure 1 for a listing of interesting registers to POKE. First, let's make the screen more readable. Lookup the color chart in Figure 6, and find the numbers corresponding to green and gray#3. POKE the border register to green and the screen register to gray#3. (POKE 53280,5:POKE 53281,15). The program in Listing 1 goes through all the border and screen colors. When you run it, notice which color combinations create legible text.
A sprite is a graphics character that you can make into any shape or color you want. The sprite is made up of 21 rows of pixels (dots) by 24 columns (see Figure 4). The 64 can have up to eight sprites on the screen at once, each with independent position, color, size, and shape.
First, let's draw a picture of a sprite, and then figure out how to put it into a form that the 64 understands. Notice in Figure 2 that the columns are divided into three sections, each containing eight squares. Each of these squares represents a bit and each section of eight bits is called a byte.
So, to draw the first row of Figure 4 into the 64 requires three bytes. The second row takes up another three bytes, and so on, until row 21. So all told, a sprite takes up to 21*3 bytes, or 63 bytes. Now, even though in our picture the sprite has 21 rows, when we put it into memory, all the bytes are in sequential order. Figure 2 shows how the first two rows are translated into memory.
What values go into memory? Let's look at an individual byte. A byte has a numeric range from 0-255, which in binary, is either all the bits off (0), or all the bits on (255). Table 1 shows the decimal value for each of the bits, counting from right to left.
The decimal value is calculated by raising 2 to the power of the bit number. (This is why we start with Bit 0, 2 0=1). If you have more than just one bit set in a byte, the value of the byte is determined by adding the decimal values of Table 1 that correspond to each bit turned on. Each bit that is on in the byte corresponds to the pixel appearing on the screen after you have drawn the sprite.
For example, if you wanted the first row to be a solid black line, you would assign the first three bytes the value of 255, which in binary has all the bits on. If you wanted every other pixel on, you would give the byte a value of 170 which in binary is 10101010. (What value would 01010101 be?)
The program in Listing 2 puts a sprite on the screen. Look at the DATA statements, and compare the values of each byte with the pattern of the pixels in Figure 4. After adding up the value of each section in Figure 4, you should get the number in the data statement that corresponds to the row and column of the byte.
After calculating the decimal values of the 63 bytes that make up the sprite, we must decide where to put the bit pattern in the memory of the 64. The sprite pointer register (see Figure 5) tells the 64 where you put the data for the shape. The data address can be found by taking the sprite pointer value and multiplying it by 64. So if we give the sprite pointer a value of 13, the memory location into which we put the sprite will be 13*64, or 832.
Now let's choose a sprite to work with. By POKEing the sprite0 pointer register to 13, the 64 will look for the shape of sprite0 starting at address 832, so the next thing to do is put our data into memory. Lines 1000-1065 in Listing 2 POKE the data appropriately. Now, we have drawn a sprite, found out how to convert it into numbers, POKEd it into memory and told the 64 where it is found in RAM.
The last two things that we must do are to tell the 64 that we want sprite0 displayed on the screen and to give it a row and column position. In Figure 1, the sprite control register is used to determine which sprite is to be on the screen.
There is only one register to handle all eight sprites, so each bit in the register is assigned to a sprite. Figure 3 gives the layout of the sprite control register. To turn sprite0 on, bit 0 of the control register must be set to 1 by POKEing (sprite control register), 1. (See line 70 in Listing 2).
The sprite0 column position and sprite0 row position registers are used to place the sprite on the screen. The position of the sprite can be from 0-255 in both the column and row position. The number you POKE into the registers corresponds to the number of pixels across and down starting at the upper lefthand corner of the screen.
To put the sprite at the middle of the screen, POKE (sprite0 column position), 160 and POKE (sprite0 row position), 120. (See line 40 in Listing 2). Type the program in and run it. The sprite will appear in the middle of the screen. Now we can concentrate on moving it, and changing its color and size.
Note: whenever I say POKE (sprite0 color register), 3 or any similar statement, the label sprite0 color register must be replaced with the actual value of the sprite0 color register, or whatever register is in the parentheses. Remember, figure 1 is the list of all the registers used for sprite manipulation.
First, let's explore moving the sprite in a horizontal direction. Currently, the column position is 160. Try changing it to 159 by POKE (sprite0 column register), 159. Did you see the sprite move over a little? To move the sprite smoothly from left to right, we can write a loop that decrements the column position by one, and has a small delay loop so the sprite doesn't zip across the screen. Try this:
FOR I = 159 TO 20: POKE 53248, I:
FOR J = 1 TO 100: NEXT J, I
Watch the sprite move across the screen. By adjusting the maximum value of J (currently 100), you can make the sprite move faster or slower. Now try moving the sprite vertically. (Hint: it is exactly the same loop, with one number changed.) Now, for our color demonstration, please put the sprite back into the middle of the screen.
Each sprite has a color register which you can use to give it any one of the 16 colors. Each sprite can have a color independent of any other sprite. Find the sprite0 color register in Figure 1, and experiment by POKEing into it values from 0 to 15. Notice that the number you POKE into the color register and the color of the sprite correspond to the chart in Figure 5.
By now, you might also note that the sprite did not scroll off the screen as normal text does, nor did it appear when you cleared the screen. In fact, you can write over the sprite, and the 64 will try to merge the two together. There are two ways to remove a sprite: POKE everything back to zero, or reset the machine.
The 64 can also expand the sprite to double size, in both horizontal and vertical positions. Look up the values for the sprite column and row expand registers. Now POKE (column expand register), 1. Our sprite has just grown taller. Let's try getting it more normal looking: POKE (column expand register), 0:POKE (row expand register), 1. It's stretched the other way. Let's see . . . to get the sprite to lengthen, try the first POKE again. There, now it's twice the size it used to be. (It's up to you to look up the actual numbers this time.)
The expand registers work the same way the sprite control register works, where each bit determines the sprite number, and whether it is expanded (1), or not (0). This way you can control all eight sprite expansions with just two registers.
So far, we have used only one sprite out of the eight. Let's put another sprite on the screen giving it the same shape as sprite0. (I'm lazy), but a different color. To put sprite1 on the screen, POKE the sprite control register so both sprites 0 and 1 will be on (first two bits). Then POKE the sprite1 pointer register to the same data location as sprite0. Type in the lines in Listing 3. (Remember to look up the numbers from Figure 1.)
First notice that sprite0 is twice as large as sprite1. Now take the Basic statements we used to make sprite0 move from left to right, but modifying it so that sprite1 moves from left to right. Watch what happens.
Sprite1 passes behind sprite0. Why? Each sprite has a priority, which tells the 64 if it appears in front or behind another sprite. Sprite0 has priority over all other sprites. Sprite1 has priority over sprites 2-7, etc. Sprite priority can be used to make an airplane fly in front of the sun, but have both airplane and sun disappear behind a cloud. The program in Listing 4 demonstrates priority by putting all eight sprites on the screen at one location, and then moving them in a large circle, each with a different rate of speed.
The sprite collision register is used to determine if sprites have collided. Each bit of the collision register represents a sprite, so when our sprite0 and sprite1 collide, the collision register will contain a 3 (00000011).
Important: Whenever a collision happens, you must reset the collision register to 0, otherwise the 64 will think that the sprites are still colliding. The collision register is 0 when there are no collisions. The program in Listing 5 uses collision detection; one sprite (a large box) contains another sprite (a bouncing ball). Whenever the ball hits one of the walls, it sets the collision detection register, and new direction for the ball is chosen.
How The Programs Work
The programs in Listings 4 and 5 were suggested to me by Gregory Yob, and I thought they were excellent demonstrations, so I decided to code them. Space is limited, so these notes are rather terse.
These programs are just a sample of what can be done with sprites. I would enjoy hearing from you if you create any interesting designs, pictures, or animation.
If you write to Gregory Yob, he will pass your letters on to me.
Line 5 reserves variable space for eight pairs of xy coordinates, 0-15.
Line 6 reserves variable space for eight separate rates in which the sprites travel in the circle.
Line 7 defaults the number of iterations to 229.
Line 10 sets up the 64 with a green border and black background.
Line 15 clears the screen and chooses a character color.
Line 20, v is the start of sprite register variable.
Line 21 turns off all sprites.
Line 22 asks the user for the number of iterations; pressing carriage return defaults to 229.
Line 23 is a ratio.
In line 25, the character table address is 13: physical memory location is 13*64.
Line 31 expands all eight sprites in both horizontal and vertical directions.
Line 35 tells the 64 the location of the sprite data tables (they are all in one place).
Line 40 gives each sprite a different color (cyan through light red.)
Line 45 is a subroutine which puts each sprite in the middle of the screen.
Line 46 turns all eight sprites on.
Line 50 reprograms them by POKEing data into them.
Line 60 checks if the cycle is complete and if so, ends the program.
Line 62 is the main loop that moves all eight sprites.
Line 63 changes their polar coordinate position.
Line 65 updates the horizontal coordinate.
Line 70 updates the vertical coordinate.
Line 71 tells the 64 the new positions.
Line 75 continues with the loop.
Line 80 goes back to the beginning of the loop.
Line 85 is the subroutine that centers all the sprites on top of each other.
Lines 1000-1015 contain all the data required for the shape of a circle.
Line 5 reserves variable space for two sprites, each with a horizontal and vertical location.
Line 8 programs the 64 to play a tone.
Line 9 programs the 64 for a tone frequency.
Line 10 sets the 64 up with a green border and black background.
Line 15 clears the screen and changes the character color.
Line 20 is the start of the sprite registers.
Line 21 turns off all sprites.
Line 25 defines the start of the sprite data table in memory.
Line 31 expands the first two sprites.
Line 35 tells the 64 where the sprite data table is for sprite0 and sprite1.
Line 40 programs the sprite to a purple box and light green ball.
Line 45 puts the sprites in the middle of the screen.
Line 46 turns the sprites on.
Line 50 defines the shape of both sprites.
Line 55 chooses a random velocity for both box and ball.
Lines 60-70 move the sprites.
Line 73 checks if the box has reached one of the edges of the screen; this limits the movement of the box to within a predefined area.
Line 75 checks for a collision; if the two sprites haven't collided, the program continues to move them.
Line 76 causes a beep to be emitted, and the box to be put back to its original location if they have collided.
Line 77 puts the ball back to its original location.
Line 84 resets the sprite collision register to an "uncollided' state and goes back to choosing a new velocity for both box and ball.
Line 85 positions the box and ball in the middle of the screen.
Line 90 is a subroutine which emits a beep by turning the volume on.
Line 91 after waiting a little while, turns the volume off and returns to the main flow of the program.
Lines 95-140 are two subroutines used in choosing a velocity for the box and ball; the program checks if the velocity is too slow, and chooses another if it is.
Lines 1000-1011 contain the data for the box.
Lines 1015-1065 contain the data for the ball.
Table: Table 1.
Table: Listing 1.
Table: Figure 1.
Table: Figure 2.
Table: Figure 3.
Table: Figure 4.
Table: Listing 2.
Table: Listing 3.
Table: Figure 5.
Table: Figure 6.
Table: Listing 4.
Table: Listing 5.
Products: Commodore 64 (computer)