Oct 4, 2026 · Updated Oct 11, 2026 · @Craig Taylor
Chapter 3 described each keyword. This chapter is about putting them together: how to write BASIC 65 programs that are easy to read, easy to change, quick to debug and fast enough. Much C64 BASIC was written as a tangle of GOTOs, because C64 BASIC offered little else. BASIC 65 has proper loops, multi-line blocks, error trapping and editing tools, and a program that uses them is both shorter and far easier to work on.
The chapter covers:
A C64 program that waits for a key typically reads like this:
10 GET A$ : IF A$ = "" THEN 10
It works, but every such line adds a jump to follow when reading the program. BASIC 65's DO ... LOOP says what it means and needs no line number:
10 DO : GET A$ : LOOP UNTIL A$ <> ""
The condition can go at either end. With WHILE or UNTIL after DO, it is tested before each pass, so the body may never run; after LOOP, it is tested after each pass, so the body always runs at least once. A DO ... LOOP with no condition repeats until an EXIT, which suits loops whose stopping point is in the middle:
10 DO
20 INPUT "COMMAND (Q TO QUIT)"; C$
30 IF C$ = "Q" THEN EXIT
40 GOSUB 1000 : REM PROCESS THE COMMAND
50 LOOP
60 PRINT "BYE"
Use FOR ... NEXT when the number of passes is known in advance, and DO ... LOOP for everything else.
On the C64, everything after THEN had to fit on one line, so longer actions meant jumping elsewhere. BEGIN and BEND let THEN and ELSE control a block of any length:
100 IF SC > HI THEN BEGIN
110 HI = SC
120 PRINT "NEW HIGH SCORE!"
130 SOUND 1, 7382, 30
140 BEND : ELSE PRINT "SCORE:"; SC
The Book notes a quirk: statements after BEND on the same line belong to the block's control flow, so keep anything that must always run on a separate line (MEGA65 Book, p. B-28).
GOSUB and RETURN remain the way to break a program into pieces. A few habits make subroutines easy to work with:
ON ... GOSUB and FGOSUB select a subroutine by number, which replaces chains of IF statements:
10 GETKEY K$ : K = INSTR("WASD", K$)
20 IF K THEN ON K GOSUB 1000, 1100, 1200, 1300
30 GOTO 10
DEF FN defines a one-line numeric function, which keeps a repeated formula in one place:
10 DEF FN RN(X) = INT(RND(0) * X) + 1 : REM RANDOM 1 TO X
20 PRINT FN RN(6), FN RN(100)
Tables of fixed values belong in DATA statements rather than in long chains of assignments. READ them into arrays at the start of the program, and use RESTORE with a line number to re-read a particular table. This keeps the logic of the program separate from its data, so changing a level layout or a list of prices does not touch the code.
BASIC 65 has editing commands that earlier Commodore BASICs lacked. Used together, they make typing in and reshaping a program much quicker.
| Task | Command |
|---|---|
| Number lines automatically while typing | AUTO 10; AUTO alone turns it off |
| Make room between lines, or tidy numbering | RENUMBER 100, 10 |
| List part of a program | LIST 500-, LIST -70,
LIST 240-350; P pauses each screenful |
| Find every use of a variable or word | FIND "HS" for a variable; FIND /PRINT/
for a keyword |
| Rename a variable throughout | CHANGE "XX$" TO "UU$" |
| Delete a block of lines | DELETE 240-350 |
| Add a library of subroutines | MERGE "LIBRARY" |
| Keep common commands on function keys | KEY 2, "DIR" + CHR$(13) |
| Bring in a program written on a PC | IMPORT "PROGRAM" from a SEQ text file |
RENUMBER updates every GOTO, GOSUB and similar reference to the
lines it moves. It cannot fix line numbers a program calculates, as with
FGOTO and FGOSUB, and it uses banks 4 and 5 as working space (MEGA65
Book, FGOTO, FGOSUB and MEM). With quotes, FIND and CHANGE search plain
text, which finds variable names but not keywords: a keyword is stored
as a token, so search for it between other delimiters, as in
FIND /PRINT/. CHANGE shows each match and asks Y, N or *
(all) before replacing it; answering * changes every later match too, so
give it a string that cannot also appear inside a longer name: changing
A would also change AB.
BASIC 65 tolerates spaces, and using them costs only a little memory. A few conventions make listings readable:
TOTAL contains TO, and SCORE
contains OR, and both cause syntax errors.DSAVE "@NAME" overwrites the previous version in one
step. For bigger changes, save under a new name first, such as
"GAME2", so there is always a working version to go back
to. The Freezer's freeze slots can also capture the whole machine,
program and all, at any moment.
When BASIC stops with an error, it names the error and the line. HELP then lists that line and highlights the statement that failed, which matters on long lines with several statements (MEGA65 Book, p. B-131).
After a program stops, its variables keep their values. PRINT them in direct mode, change them if needed, and continue with CONT, provided no program line has been edited in the meantime and the program did not stop with an error. A STOP placed in the program acts as a breakpoint:
200 IF X > 319 THEN PRINT "X OUT OF RANGE:"; X : STOP
TRON prints each line number as it runs, and TROFF turns it off. Tracing a whole program produces a flood of numbers, so turn it on just before the suspect section and off just after:
500 TRON
510 GOSUB 2000
520 TROFF
TRAP sends errors to a handler line instead of stopping the program,
and ER, EL and ERR$(ER) say what went wrong and where.
During development, a handler that prints this information and then
stops is more useful than letting errors pass silently:
10 TRAP 9000
...
9000 PRINT ERR$(ER); " IN LINE"; EL : STOP
Chapter 6 shows fuller error handling with RESUME.
| Symptom | Likely cause |
|---|---|
?SYNTAX ERROR in a line that looks correct |
A keyword hidden inside a variable name |
| A variable is unexpectedly zero | It is misspelled |
| Two variables seem to change each other | Their names share the first two characters, so BASIC treats them as one variable |
?OUT OF DATA |
A READ loop runs further than the DATA, or a RESTORE is missing |
| A disk command fails without an error | DS was not checked; print DS$ |
POKEs to $D020 or similar do nothing |
BANK is not 128 |
| A program behaves differently after editing | It was restarted with GOTO, which keeps the old variable values (editing does not clear them); use RUN |
MEGA65 mode runs at 40.5 MHz, so BASIC 65 is already far faster than C64 BASIC. When more speed is needed, these techniques help, roughly in order of effect.
TI is a timer with microsecond resolution, restarted by CLR TI. Time the part of the program in question before and after a change, rather than guessing:
10 CLR TI
20 FOR I = 1 TO 10000 : X = X + 1 : NEXT
30 PRINT TI; "SECONDS"
A variable whose name is a single letter, with or without a type symbol, is a fast variable: BASIC 65 finds it without searching (MEGA65 Book, p. B-7). Give the variables used in the busiest loops one-letter names, such as I, X, Y and C$.
Check that nothing has slowed the CPU: SPEED with no argument selects
40.5 MHz, and RSPEED reports the current speed. Programs that run in C64
mode can use POKE 0,65.
Calculate values that do not change before the loop starts, and use tables read from DATA instead of repeated arithmetic. A table of 360 sine values, read once, is much faster than calling SIN in every pass.
Several BASIC 65 commands hand work to the MEGA65's hardware or to machine code in the ROM, and one of them replaces many BASIC statements:
| Instead of | Use |
|---|---|
| A loop of POKEs to copy or fill memory | EDMA |
| PEEK, AND and POKE to change one bit | SETBIT and CLRBIT |
| Two POKEs for a 16-bit value | WPOKE |
| A POKE loop to draw on the text screen | T@& and C@&, or PRINT |
| Plotting shapes pixel by pixel | LINE, BOX, CIRCLE, PAINT |
| Moving sprites every frame | MOVSPR with a speed, which BASIC updates in the background |
Every new string value takes fresh memory, and BASIC pauses to reclaim the old ones when memory runs low. Avoid building strings a character at a time inside fast loops, and reuse string variables rather than creating many.
If a routine is still too slow after these steps, rewrite just that routine in machine language and call it with SYS, as Chapter 9 describes. Usually only a small part of a program needs this.
A program used by other people meets input and situations its author did not expect. Four habits deal with most of them:
DS$ if it is not
zero.This input routine combines several of them:
1000 REM INPUT A NUMBER FROM LO TO HI INTO N
1010 DO
1020 INPUT "NUMBER"; N$
1030 N = VAL(N$)
1040 LOOP UNTIL N >= LO AND N <= HI AND N$ <> ""
1050 RETURN
Reading the input as a string and converting it with VAL means a typed letter never produces a REDO FROM START message or stops the program.
Chapter 5 turns from BASIC to the machine underneath it.