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 NEXT: NEXT
16 LET I% = I% + 1
17 NEXT: NEXT
And here is its output:
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 1 DATA 00, 00, 14, 14, 14, 14, 14, 00, 00
6 2 DATA 00, 14, 14, 14, 14, 14, 14, 14, 00
7 3 DATA 14, 14, 14, 14, 14, 14, 14, 14, 14
8 4 DATA 14, 14, 14, 14, 14, 14, 14, 14, 14
9 5 DATA 14, 14, 00, 14, 14, 14, 00, 14, 14
10 6 DATA 14, 14, 00, 14, 14, 14, 00, 14, 14
11 7 DATA 14, 14, 14, 14, 00, 14, 14, 14, 14
12 8 DATA 14, 14, 14, 14, 00, 14, 14, 14, 14
13 9 DATA 14, 14, 14, 14, 00, 14, 14, 14, 14
14 10 DATA 14, 14, 14, 14, 14, 14, 14, 14, 14
15 11 DATA 14, 14, 00, 00, 00, 00, 00, 14, 14
16 12 DATA 14, 14, 14, 14, 14, 14, 14, 14, 14
17 13 DATA 00, 14, 14, 14, 14, 14, 14, 14, 00
18 14 DATA 00, 00, 14, 14, 14, 14, 14, 00, 00
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 00, 00, 15, 00, 00
5 DATA 00, 15, 00, 15, 00
6 DATA 15, 15, 15, 15, 15
7 DATA 15, 00, 00, 00, 15
8 DATA 15, 00, 00, 00, 15
9 DATA 00, 00, 00, 00, 00
10
11 102 REM B
12 DATA 15, 15, 15, 15, 00
13 DATA 15, 00, 00, 00, 15
14 DATA 15, 15, 15, 15, 00
15 DATA 15, 00, 00, 00, 15
16 DATA 15, 15, 15, 15, 00
17 DATA 00, 00, 00, 00, 00
18
19 103 REM C
20 DATA 00, 15, 15, 15, 00
21 DATA 15, 00, 00, 00, 15
22 DATA 15, 00, 00, 00, 00
23 DATA 15, 00, 00, 00, 15
24 DATA 00, 15, 15, 15, 00
25 DATA 00, 00, 00, 00, 00
26
27 104 REM D
28 DATA 15, 15, 15, 15, 00
29 DATA 15, 00, 00, 00, 15
30 DATA 15, 00, 00, 00, 15
31 DATA 15, 00, 00, 00, 15
32 DATA 15, 15, 15, 15, 00
33 DATA 00, 00, 00, 00, 00
34
35 105 REM E
36 DATA 15, 15, 15, 15, 15
37 DATA 15, 00, 00, 00, 00
38 DATA 15, 15, 15, 00, 00
39 DATA 15, 00, 00, 00, 00
40 DATA 15, 15, 15, 15, 15
41 DATA 00, 00, 00, 00, 00
42
43 106 REM F
44 DATA 15, 15, 15, 15, 15
45 DATA 15, 00, 00, 00, 00
46 DATA 15, 15, 15, 00, 00
47 DATA 15, 00, 00, 00, 00
48 DATA 15, 00, 00, 00, 00
49 DATA 00, 00, 00, 00, 00
50
51 107 REM G
52 DATA 00, 15, 15, 15, 00
53 DATA 15, 00, 00, 00, 00
54 DATA 15, 00, 00, 15, 15
55 DATA 15, 00, 00, 00, 15
56 DATA 00, 15, 15, 15, 00
57 DATA 00, 00, 00, 00, 00
58
59 108 REM H
60 DATA 15, 00, 00, 00, 15
61 DATA 15, 00, 00, 00, 15
62 DATA 15, 15, 15, 15, 15
63 DATA 15, 00, 00, 00, 15
64 DATA 15, 00, 00, 00, 15
65 DATA 00, 00, 00, 00, 00
66
67 109 REM I
68 DATA 00, 15, 15, 15, 00
69 DATA 00, 00, 15, 00, 00
70 DATA 00, 00, 15, 00, 00
71 DATA 00, 00, 15, 00, 00
72 DATA 00, 15, 15, 15, 00
73 DATA 00, 00, 00, 00, 00
74
75 110 REM J
76 DATA 00, 15, 15, 15, 15
77 DATA 00, 00, 00, 15, 00
78 DATA 00, 00, 00, 15, 00
79 DATA 00, 15, 00, 15, 00
80 DATA 00, 00, 15, 00, 00
81 DATA 00, 00, 00, 00, 00
82
83 111 REM K
84 DATA 15, 00, 00, 00, 15
85 DATA 15, 00, 00, 15, 00
86 DATA 15, 15, 15, 00, 00
87 DATA 15, 00, 00, 15, 00
88 DATA 15, 00, 00, 00, 15
89 DATA 00, 00, 00, 00, 00
90
91 112 REM L
92 DATA 15, 00, 00, 00, 00
93 DATA 15, 00, 00, 00, 00
94 DATA 15, 00, 00, 00, 00
95 DATA 15, 00, 00, 00, 00
96 DATA 15, 15, 15, 15, 15
97 DATA 00, 00, 00, 00, 00
98
99 113 REM M
100 DATA 15, 00, 00, 00, 15
101 DATA 15, 15, 00, 15, 15
102 DATA 15, 00, 15, 00, 15
103 DATA 15, 00, 00, 00, 15
104 DATA 15, 00, 00, 00, 15
105 DATA 00, 00, 00, 00, 00
106
107 114 REM N
108 DATA 15, 00, 00, 00, 15
109 DATA 15, 15, 00, 00, 15
110 DATA 15, 00, 15, 00, 15
111 DATA 15, 00, 00, 15, 15
112 DATA 15, 00, 00, 00, 15
113 DATA 00, 00, 00, 00, 00
114
115 115 REM O
116 DATA 00, 15, 15, 15, 00
117 DATA 15, 00, 00, 00, 15
118 DATA 15, 00, 00, 00, 15
119 DATA 15, 00, 00, 00, 15
120 DATA 00, 15, 15, 15, 00
121 DATA 00, 00, 00, 00, 00
122
123 116 REM P
124 DATA 15, 15, 15, 15, 00
125 DATA 15, 00, 00, 00, 15
126 DATA 15, 15, 15, 15, 00
127 DATA 15, 00, 00, 00, 00
128 DATA 15, 00, 00, 00, 00
129 DATA 00, 00, 00, 00, 00
130
131 117 REM Q
132 DATA 00, 15, 15, 15, 00
133 DATA 15, 00, 00, 00, 15
134 DATA 15, 00, 00, 00, 15
135 DATA 15, 00, 15, 00, 15
136 DATA 00, 15, 15, 15, 00
137 DATA 00, 00, 00, 00, 15
138
139 118 REM R
140 DATA 15, 15, 15, 15, 00
141 DATA 15, 00, 00, 00, 15
142 DATA 15, 15, 15, 15, 00
143 DATA 15, 00, 00, 00, 15
144 DATA 15, 00, 00, 00, 15
145 DATA 00, 00, 00, 00, 00
146
147 119 REM S
148 DATA 00, 15, 15, 15, 15
149 DATA 15, 00, 00, 00, 00
150 DATA 00, 15, 15, 15, 00
151 DATA 00, 00, 00, 00, 15
152 DATA 15, 15, 15, 15, 00
153 DATA 00, 00, 00, 00, 00
154
155 120 REM T
156 DATA 15, 15, 15, 15, 15
157 DATA 00, 00, 15, 00, 00
158 DATA 00, 00, 15, 00, 00
159 DATA 00, 00, 15, 00, 00
160 DATA 00, 00, 15, 00, 00
161 DATA 00, 00, 00, 00, 00
162
163 121 REM U
164 DATA 15, 00, 00, 00, 15
165 DATA 15, 00, 00, 00, 15
166 DATA 15, 00, 00, 00, 15
167 DATA 15, 00, 00, 00, 15
168 DATA 00, 15, 15, 15, 00
169 DATA 00, 00, 00, 00, 00
170
171 122 REM V
172 DATA 15, 00, 00, 00, 15
173 DATA 15, 00, 00, 00, 15
174 DATA 15, 00, 00, 00, 15
175 DATA 00, 15, 00, 15, 00
176 DATA 00, 00, 15, 00, 00
177 DATA 00, 00, 00, 00, 00
178
179 123 REM W
180 DATA 15, 00, 00, 00, 15
181 DATA 15, 00, 00, 00, 15
182 DATA 15, 00, 00, 00, 15
183 DATA 15, 00, 15, 00, 15
184 DATA 00, 15, 00, 15, 00
185 DATA 00, 00, 00, 00, 00
186
187 124 REM X
188 DATA 15, 00, 00, 00, 15
189 DATA 00, 15, 00, 15, 00
190 DATA 00, 00, 15, 00, 00
191 DATA 00, 15, 00, 15, 00
192 DATA 15, 00, 00, 00, 15
193 DATA 00, 00, 00, 00, 00
194
195 125 REM Y
196 DATA 15, 00, 00, 00, 15
197 DATA 00, 15, 00, 15, 00
198 DATA 00, 00, 15, 00, 00
199 DATA 00, 00, 15, 00, 00
200 DATA 00, 00, 15, 00, 00
201 DATA 00, 00, 00, 00, 00
202
203 126 REM Z
204 DATA 15, 15, 15, 15, 15
205 DATA 00, 00, 00, 15, 00
206 DATA 00, 00, 15, 00, 00
207 DATA 00, 15, 00, 00, 00
208 DATA 15, 15, 15, 15, 15
209 DATA 00, 00, 00, 00, 00
210
211 200 REM 0
212 DATA 00, 15, 15, 15, 00
213 DATA 15, 00, 00, 00, 15
214 DATA 15, 00, 15, 00, 15
215 DATA 15, 00, 00, 00, 15
216 DATA 00, 15, 15, 15, 00
217 DATA 00, 00, 00, 00, 00
218
219 201 REM 1
220 DATA 00, 00, 15, 00, 00
221 DATA 00, 15, 15, 00, 00
222 DATA 00, 00, 15, 00, 00
223 DATA 00, 00, 15, 00, 00
224 DATA 00, 15, 15, 15, 00
225 DATA 00, 00, 00, 00, 00
226
227 202 REM 2
228 DATA 00, 15, 15, 15, 00
229 DATA 15, 00, 00, 00, 15
230 DATA 00, 00, 00, 15, 00
231 DATA 00, 00, 15, 00, 00
232 DATA 15, 15, 15, 15, 15
233 DATA 00, 00, 00, 00, 00
234
235 203 REM 3
236 DATA 00, 15, 15, 15, 00
237 DATA 15, 00, 00, 00, 15
238 DATA 00, 00, 15, 15, 00
239 DATA 15, 00, 00, 00, 15
240 DATA 00, 15, 15, 15, 00
241 DATA 00, 00, 00, 00, 00
242
243 204 REM 4
244 DATA 00, 00, 00, 15, 00
245 DATA 00, 00, 15, 15, 00
246 DATA 00, 15, 00, 15, 00
247 DATA 15, 15, 15, 15, 15
248 DATA 00, 00, 00, 15, 00
249 DATA 00, 00, 00, 00, 00
250
251 205 REM 5
252 DATA 15, 15, 15, 15, 15
253 DATA 15, 00, 00, 00, 00
254 DATA 15, 15, 15, 15, 00
255 DATA 00, 00, 00, 00, 15
256 DATA 15, 15, 15, 15, 00
257 DATA 00, 00, 00, 00, 00
258
259 206 REM 6
260 DATA 00, 00, 15, 15, 00
261 DATA 00, 15, 00, 00, 00
262 DATA 15, 15, 15, 15, 00
263 DATA 15, 00, 00, 00, 15
264 DATA 00, 15, 15, 15, 00
265 DATA 00, 00, 00, 00, 00
266
267 207 REM 7
268 DATA 15, 15, 15, 15, 15
269 DATA 00, 00, 00, 15, 00
270 DATA 00, 00, 15, 00, 00
271 DATA 00, 15, 00, 00, 00
272 DATA 00, 15, 00, 00, 00
273 DATA 00, 00, 00, 00, 00
274
275 208 REM 8
276 DATA 00, 15, 15, 15, 00
277 DATA 15, 00, 00, 00, 15
278 DATA 00, 15, 15, 15, 00
279 DATA 15, 00, 00, 00, 15
280 DATA 00, 15, 15, 15, 00
281 DATA 00, 00, 00, 00, 00
282
283 209 REM 9
284 DATA 00, 15, 15, 15, 00
285 DATA 15, 00, 00, 00, 15
286 DATA 00, 15, 15, 15, 15
287 DATA 00, 00, 00, 00, 15
288 DATA 00, 15, 15, 15, 00
289 DATA 00, 00, 00, 00, 00
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:
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 1 DATA 00, 00, 14, 14, 14, 14, 14, 00, 00
8 2 DATA 00, 14, 14, 14, 14, 14, 14, 14, 00
9 3 DATA 14, 14, 14, 14, 14, 14, 14, 14, 14
10 4 DATA 14, 14, 14, 14, 14, 14, 14, 14, 14
11 5 DATA 14, 14, 00, 14, 14, 14, 00, 14, 14
12 6 DATA 14, 14, 00, 14, 14, 14, 00, 14, 14
13 7 DATA 14, 14, 14, 14, 00, 14, 14, 14, 14
14 8 DATA 14, 14, 14, 14, 00, 14, 14, 14, 14
15 9 DATA 14, 14, 14, 14, 00, 14, 14, 14, 14
16 10 DATA 14, 14, 14, 14, 14, 14, 14, 14, 14
17 11 DATA 14, 14, 00, 00, 00, 00, 00, 14, 14
18 12 DATA 14, 14, 14, 14, 14, 14, 14, 14, 14
19 13 DATA 00, 14, 14, 14, 14, 14, 14, 14, 00
20 14 DATA 00, 00, 14, 14, 14, 14, 14, 00, 00
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:
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.
Old-style BASIC games from the 80s — I remember using some of these book, now available as free PDFs…
https://usborne.com/browse-books/features/computer-and-coding-books/
LikeLiked by 1 person
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.
LikeLike