More QBASIC: Color Tables, Font Files, and Sprite Animation

Just another update on my continuing progress with QBASIC. I’ve moved past trivial programs now and am now doing some more practical graphics programming, getting closer to something that I could turn into a neat MS-DOS game. I learned several new programming constructs, including the RESTORE function, which allows you to reuse data declared by the DATA statement. I now have an answer to the question posed in the last QBASIC post where I wanted to figure out how DATA worked. Basically, the program has a placeholder that it uses to determine what the next data item to be read is, and it starts at the first DATA statement in the program and advances by one data item with each successive call, and you can jump back to a previous position by calling RESTORE with a label number.

I wrote a program that generates a table of all 256 colors used by QBASIC in Mode 13 (I realized there were 256 after I plotted 400 of them and noticed that the pattern at the beginning repeated itself after reaching 255). I think this is the first QBASIC program I’ve written that actually has value beyond just proof-of-concept. It gives me a convenient reference to use when I want to know what code I need for a specific color.

Here is the source code for the program:


 1 SCREEN 13
 2 
 3 DIM COLORS%(255)
 4 
 5 FOR I% = 0 TO 255
 6 COLORS%(I%) = I%
 7 NEXT
 8 LET I% = 0
 9 
10 FOR Y% = 0 TO 15
11 FOR X% = 0 TO 15
12 FOR J% = X% * 20 TO X% * 20 + 20
13 FOR K% = Y% * 10 TO Y% * 10 + 10
14 PSET (J%, K%), COLORS%(I%)
15 NEXTNEXT
16 LET I% = I% + 1
17 NEXTNEXT

And here is its output:

coltab

Next I started experimenting with ways to implement sprite animation in QBASIC. I opted for just a plain face – yellow on black, two eyes, a nose, and a mouth – 9 pixels wide and 14 pixels high. Drawing the face using DATA statements was relatively easy.

At first I was going to have the sprite move by itself, with the direction it moved in determined randomly. I learned about the functions and statements necessary to do this: RANDOMIZE to seed the randomizer, TIMER for the clock time to be used as a seed, RND to actually generate a random number, and SLEEP to pause for a certain interval between frame renders. However, I couldn’t seem to get the animation to run by itself without user intervention. I found that when I did this, the animation only moved to the next frame when I hit a key. So I decided I’d figure that part out some other time, and just focus on the graphical aspect of the sprite. The sprite’s movements are now controlled by the user using the WASD keys.


 1 REM Sprite graphics demo
 2 
 3 SCREEN 13
 4 
 5  DATA 000014141414140000
 6  DATA 001414141414141400
 7  DATA 141414141414141414
 8  DATA 141414141414141414
 9  DATA 141400141414001414
10  DATA 141400141414001414
11  DATA 141414140014141414
12  DATA 141414140014141414
13  DATA 141414140014141414
14 10 DATA 141414141414141414
15 11 DATA 141400000000001414
16 12 DATA 141414141414141414
17 13 DATA 001414141414141400
18 14 DATA 000014141414140000
19 
20 LET X% = 150    ' x-coordinate
21 LET Y% = 75     ' y-coordinate
22 LET S% = 1      ' speed in pixels-per-stroke
23 
24 DO
25 
26 15 LET KEY$ = INKEY$
27 IF KEY$ = "" GOTO 15
28 IF ASC(KEY$) = 27 GOTO 16
29 
30 REM Function keys control the speed.
31 IF ASC(KEY$) = 0 THEN
32         LET C% = ASC(RIGHT$(KEY$, 1))
33         IF C% > 58 AND C% < 68 THEN
34                 LET S% = C% - 58
35         END IF
36 END IF
37 
38 REM Erase:
39 FOR YOFF% = 1 TO 14
40 FOR XOFF% = 1 TO 9
41 READ Z%
42 PSET (XOFF% + X%, YOFF% + Y%), 0
43 NEXT
44 NEXT
45 RESTORE 1
46 
47 REM New position:
48 IF KEY$ = "w" THEN
49         LET Y% = Y% - S%
50 ELSEIF KEY$ = "a" THEN
51         LET X% = X% - S%
52 ELSEIF KEY$ = "s" THEN
53         LET Y% = Y% + S%
54 ELSEIF KEY$ = "d" THEN
55         LET X% = X% + S%
56 END IF
57 
58 REM Draw:
59 FOR YOFF% = 1 TO 14
60 FOR XOFF% = 1 TO 9
61 READ Z%
62 PSET (XOFF% + X%, YOFF% + Y%), Z%
63 NEXT
64 NEXT
65 RESTORE 1
66 
67 LOOP WHILE 1 = 1
68 
69 16 END

I’m pretty happy that I found a use for that scan code thing I talked about back in the first QBASIC post. I noticed that when I had the sprite move one pixel at a time, the animation was painfully slow. So I added speed controls via the F1-F9 keys, which allow the user to set the number of pixels the sprite moves every time a key is pressed. I also added a control where hitting the ESC key causes the program to exit.

Next I thought I’d add text to the bottom right corner that tells you what the current speed is. I could add text for other things, but for now I just want some text indicating the speed, something like “SPEED: 5”. So I wrote a 5x6-pixel font for use in my QBASIC programs, which I think came out rather nicely. I will share the source code for my font file. I have to warn you, it’s rather cumbersome:


  1 REM Font Library for 5x6 Psychofont
  2 
  3 101 REM A
  4 DATA 0000150000
  5 DATA 0015001500
  6 DATA 1515151515
  7 DATA 1500000015
  8 DATA 1500000015
  9 DATA 0000000000
 10 
 11 102 REM B
 12 DATA 1515151500
 13 DATA 1500000015
 14 DATA 1515151500
 15 DATA 1500000015
 16 DATA 1515151500
 17 DATA 0000000000                                                         
 18 
 19 103 REM C
 20 DATA 0015151500
 21 DATA 1500000015
 22 DATA 1500000000
 23 DATA 1500000015
 24 DATA 0015151500
 25 DATA 0000000000                                                         
 26 
 27 104 REM D
 28 DATA 1515151500
 29 DATA 1500000015
 30 DATA 1500000015
 31 DATA 1500000015
 32 DATA 1515151500
 33 DATA 0000000000                                                         
 34 
 35 105 REM E
 36 DATA 1515151515
 37 DATA 1500000000
 38 DATA 1515150000
 39 DATA 1500000000
 40 DATA 1515151515
 41 DATA 0000000000                                                         
 42 
 43 106 REM F
 44 DATA 1515151515
 45 DATA 1500000000
 46 DATA 1515150000
 47 DATA 1500000000
 48 DATA 1500000000
 49 DATA 0000000000                                                         
 50 
 51 107 REM G
 52 DATA 0015151500
 53 DATA 1500000000
 54 DATA 1500001515
 55 DATA 1500000015
 56 DATA 0015151500
 57 DATA 0000000000                                                         
 58 
 59 108 REM H
 60 DATA 1500000015
 61 DATA 1500000015
 62 DATA 1515151515
 63 DATA 1500000015
 64 DATA 1500000015
 65 DATA 0000000000                                                         
 66 
 67 109 REM I
 68 DATA 0015151500
 69 DATA 0000150000
 70 DATA 0000150000
 71 DATA 0000150000
 72 DATA 0015151500
 73 DATA 0000000000                                                         
 74 
 75 110 REM J
 76 DATA 0015151515
 77 DATA 0000001500
 78 DATA 0000001500
 79 DATA 0015001500
 80 DATA 0000150000
 81 DATA 0000000000                                                         
 82 
 83 111 REM K                  
 84 DATA 1500000015
 85 DATA 1500001500
 86 DATA 1515150000
 87 DATA 1500001500
 88 DATA 1500000015
 89 DATA 0000000000                                                         
 90 
 91 112 REM L
 92 DATA 1500000000
 93 DATA 1500000000
 94 DATA 1500000000
 95 DATA 1500000000
 96 DATA 1515151515
 97 DATA 0000000000                                                         
 98 
 99 113 REM M
100 DATA 1500000015
101 DATA 1515001515
102 DATA 1500150015
103 DATA 1500000015
104 DATA 1500000015
105 DATA 0000000000                                                         
106 
107 114 REM N
108 DATA 1500000015
109 DATA 1515000015
110 DATA 1500150015
111 DATA 1500001515
112 DATA 1500000015
113 DATA 0000000000                                                         
114 
115 115 REM O
116 DATA 0015151500
117 DATA 1500000015
118 DATA 1500000015
119 DATA 1500000015
120 DATA 0015151500
121 DATA 0000000000                                                         
122 
123 116 REM P
124 DATA 1515151500
125 DATA 1500000015
126 DATA 1515151500
127 DATA 1500000000
128 DATA 1500000000
129 DATA 0000000000                                                         
130 
131 117 REM Q
132 DATA 0015151500
133 DATA 1500000015
134 DATA 1500000015
135 DATA 1500150015
136 DATA 0015151500
137 DATA 0000000015                                                    
138 
139 118 REM R
140 DATA 1515151500
141 DATA 1500000015
142 DATA 1515151500
143 DATA 1500000015
144 DATA 1500000015
145 DATA 0000000000                                                     
146 
147 119 REM S
148 DATA 0015151515
149 DATA 1500000000
150 DATA 0015151500
151 DATA 0000000015
152 DATA 1515151500
153 DATA 0000000000
154 
155 120 REM T
156 DATA 1515151515
157 DATA 0000150000
158 DATA 0000150000
159 DATA 0000150000
160 DATA 0000150000
161 DATA 0000000000
162 
163 121 REM U
164 DATA 1500000015
165 DATA 1500000015
166 DATA 1500000015
167 DATA 1500000015
168 DATA 0015151500
169 DATA 0000000000
170 
171 122 REM V
172 DATA 1500000015
173 DATA 1500000015
174 DATA 1500000015
175 DATA 0015001500
176 DATA 0000150000
177 DATA 0000000000
178 
179 123 REM W
180 DATA 1500000015
181 DATA 1500000015
182 DATA 1500000015
183 DATA 1500150015
184 DATA 0015001500
185 DATA 0000000000
186 
187 124 REM X
188 DATA 1500000015
189 DATA 0015001500
190 DATA 0000150000
191 DATA 0015001500
192 DATA 1500000015
193 DATA 0000000000
194 
195 125 REM Y
196 DATA 1500000015
197 DATA 0015001500
198 DATA 0000150000
199 DATA 0000150000
200 DATA 0000150000
201 DATA 0000000000
202 
203 126 REM Z
204 DATA 1515151515
205 DATA 0000001500
206 DATA 0000150000
207 DATA 0015000000
208 DATA 1515151515
209 DATA 0000000000
210 
211 200 REM 0
212 DATA 0015151500
213 DATA 1500000015
214 DATA 1500150015
215 DATA 1500000015
216 DATA 0015151500
217 DATA 0000000000                                                       
218 
219 201 REM 1
220 DATA 0000150000
221 DATA 0015150000
222 DATA 0000150000
223 DATA 0000150000
224 DATA 0015151500
225 DATA 0000000000
226 
227 202 REM 2
228 DATA 0015151500
229 DATA 1500000015
230 DATA 0000001500
231 DATA 0000150000
232 DATA 1515151515
233 DATA 0000000000
234 
235 203 REM 3
236 DATA 0015151500
237 DATA 1500000015
238 DATA 0000151500
239 DATA 1500000015
240 DATA 0015151500
241 DATA 0000000000
242 
243 204 REM 4
244 DATA 0000001500
245 DATA 0000151500
246 DATA 0015001500
247 DATA 1515151515
248 DATA 0000001500
249 DATA 0000000000
250 
251 205 REM 5
252 DATA 1515151515
253 DATA 1500000000
254 DATA 1515151500
255 DATA 0000000015
256 DATA 1515151500
257 DATA 0000000000
258 
259 206 REM 6
260 DATA 0000151500
261 DATA 0015000000
262 DATA 1515151500
263 DATA 1500000015
264 DATA 0015151500
265 DATA 0000000000
266 
267 207 REM 7
268 DATA 1515151515
269 DATA 0000001500
270 DATA 0000150000
271 DATA 0015000000
272 DATA 0015000000
273 DATA 0000000000
274 
275 208 REM 8
276 DATA 0015151500
277 DATA 1500000015
278 DATA 0015151500
279 DATA 1500000015
280 DATA 0015151500
281 DATA 0000000000
282 
283 209 REM 9
284 DATA 0015151500
285 DATA 1500000015
286 DATA 0015151515
287 DATA 0000000015
288 DATA 0015151500
289 DATA 0000000000

Now when I want to print characters from the font, I include the file in my source code with the $INCLUDE metacommand. To print a character I will jump to its address with RESTORE XXX, where XXX is the three-digit number preceding the REM statement denoting the character in the font file. Then I simply print the character using READ and PSET as before. I decided to use a system where different categories of characters have different first digits. So far I only have two categories: 100’s for capital letters and 200’s for digits. I think this makes the numerical codes easier to remember.

Here’s a screenshot of what the font looks like:

pfont5x6

I incorporated this font into my original sprite program so that it shows the speed in pixels/keystroke in the bottom right corner. I also changed it a little bit so that it isn’t case-sensitive when taking the WASD keys as input. It’s unfortunate that you can’t enter a DATA region from a subroutine, because it would be so much easier if I could just write a subroutine to automate the text-writing process. Also, the code shown below isn’t exactly what I used because for some reason QBASIC wouldn’t let me use the $INCLUDE directive (I guess it’s only available in later versions or something, or it’s an add-on that you have to install), so I had to write a quick C program to concatenate the program file with the font file before being able to use it. I’m showing the program below instead of what I actually used because it’s far more elegant that way.


  1 REM Sprite graphics demo
  2 
  3 REM $INCLUDE PFONT5X6.BAS
  4 
  5 SCREEN 13
  6 
  7  DATA 000014141414140000
  8  DATA 001414141414141400
  9  DATA 141414141414141414
 10  DATA 141414141414141414
 11  DATA 141400141414001414
 12  DATA 141400141414001414
 13  DATA 141414140014141414
 14  DATA 141414140014141414
 15  DATA 141414140014141414
 16 10 DATA 141414141414141414
 17 11 DATA 141400000000001414
 18 12 DATA 141414141414141414
 19 13 DATA 001414141414141400
 20 14 DATA 000014141414140000
 21 
 22 LET X% = 150    ' x-coordinate
 23 LET Y% = 75     ' y-coordinate
 24 LET S% = 1      ' speed in pixels-per-stroke
 25 
 26 REM Draw:
 27 FOR YOFF% = 1 TO 14
 28 FOR XOFF% = 1 TO 9
 29 READ Z%
 30 PSET (XOFF% + X%, YOFF% + Y%), Z%
 31 NEXT
 32 NEXT
 33 
 34 REM Write SPEED text:
 35 LET XBASE% = 275
 36 LET YBASE% = 185
 37 RESTORE 119
 38 FOR YOFF% = 1 TO 6
 39 FOR XOFF% = 1 TO 5
 40 READ Z%
 41 PSET (XOFF% + XBASE% + 0, YOFF% + YBASE%), Z%
 42 NEXT
 43 NEXT
 44 RESTORE 116
 45 FOR YOFF% = 1 TO 6
 46 FOR XOFF% = 1 TO 5
 47 READ Z%
 48 PSET (XOFF% + XBASE% + 6, YOFF% + YBASE%), Z%
 49 NEXT
 50 NEXT
 51 RESTORE 105
 52 FOR YOFF% = 1 TO 6
 53 FOR XOFF% = 1 TO 5
 54 READ Z%
 55 PSET (XOFF% + XBASE% + 12, YOFF% + YBASE%), Z%
 56 NEXT
 57 NEXT
 58 RESTORE 105
 59 FOR YOFF% = 1 TO 6
 60 FOR XOFF% = 1 TO 5
 61 READ Z%
 62 PSET (XOFF% + XBASE% + 18, YOFF% + YBASE%), Z%
 63 NEXT
 64 NEXT
 65 RESTORE 104
 66 FOR YOFF% = 1 TO 6
 67 FOR XOFF% = 1 TO 5
 68 READ Z%
 69 PSET (XOFF% + XBASE% + 24, YOFF% + YBASE%), Z%
 70 NEXT
 71 NEXT
 72 RESTORE 201
 73 FOR YOFF% = 1 TO 6
 74 FOR XOFF% = 1 TO 5
 75 READ Z%
 76 PSET (XOFF% + XBASE% + 36, YOFF% + YBASE%), Z%
 77 NEXT
 78 NEXT
 79 
 80 RESTORE 1
 81 
 82 DO
 83 
 84 15 LET KEY$ = INKEY$
 85 IF KEY$ = "" GOTO 15
 86 IF ASC(KEY$) = 27 GOTO 16
 87 
 88 REM Function keys control the speed.
 89 IF ASC(KEY$) = 0 THEN
 90         LET C% = ASC(RIGHT$(KEY$, 1))
 91         IF C% > 58 AND C% < 68 THEN
 92                 LET S% = C% - 58
 93                 IF C% = 59 THEN
 94                         RESTORE 201
 95                 ELSEIF C% = 60 THEN
 96                         RESTORE 202
 97                 ELSEIF C% = 61 THEN
 98                         RESTORE 203
 99                 ELSEIF C% = 62 THEN
100                         RESTORE 204
101                 ELSEIF C% = 63 THEN
102                         RESTORE 205
103                 ELSEIF C% = 64 THEN
104                         RESTORE 206
105                 ELSEIF C% = 65 THEN
106                         RESTORE 207
107                 ELSEIF C% = 66 THEN
108                         RESTORE 208
109                 ELSEIF C% = 67 THEN
110                         RESTORE 209
111                 END IF
112                 FOR YOFF% = 1 TO 6
113                 FOR XOFF% = 1 TO 5
114                 READ Z%
115                 PSET (XBASE% + XOFF% + 36, YBASE% + YOFF%), Z%
116                 NEXT
117                 NEXT
118                 RESTORE
119         END IF
120 END IF
121 
122 REM Erase:
123 FOR YOFF% = 1 TO 14
124 FOR XOFF% = 1 TO 9
125 READ Z%
126 PSET (XOFF% + X%, YOFF% + Y%), 0
127 NEXT
128 NEXT
129 RESTORE 1
130 
131 REM New position:
132 IF KEY$ = "W" OR KEY$ = "w" THEN
133         LET Y% = Y% - S%
134 ELSEIF KEY$ = "A" OR KEY$ = "a" THEN
135         LET X% = X% - S%
136 ELSEIF KEY$ = "S" OR KEY$ = "s" THEN
137         LET Y% = Y% + S%
138 ELSEIF KEY$ = "D" OR KEY$ = "d" THEN
139         LET X% = X% + S%
140 END IF
141 
142 REM Draw:
143 FOR YOFF% = 1 TO 14
144 FOR XOFF% = 1 TO 9
145 READ Z%
146 PSET (XOFF% + X%, YOFF% + Y%), Z%
147 NEXT
148 NEXT
149 RESTORE 1
150 
151 LOOP WHILE 1 = 1
152 
153 16 END

Here’s a GIF animation I made of the program running:

sprite

Next I think I’ll add some coin sprites that the player character can move around and collect, and a Gold counter for the amount of gold the player has. After that I think I’ll add in some enemy sprites, kinda like the ghosts in Pac-Man, which will chase the player character as he’s collecting gold. Not a very interesting game, but a game nonetheless, and the possibilities for ways I could make it more interesting are endless.

Advertisements

2 thoughts on “More QBASIC: Color Tables, Font Files, and Sprite Animation

    1. Nice. I have a lot of old computer books, both in PDF form and in physical form (bought from used book stores). I love reading them, even if they’re totally irrelevant today.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s