Practice
Exercises
Everything here runs in a stock emulator — set one up via Getting Started first. Difficulty is marked WARM-UP CHALLENGE BOSS FIGHT. Attempt each one before opening the solution — struggling first is where the learning happens.
Set A — First contact (after Lectures 01–02)
A1. Talk to the machine
In immediate mode (no line numbers), make the Apple II: (a) print your name, (b) compute the number of seconds in a year, (c) print your name 5 times using a one-line FOR loop.
SHOW SOLUTION
PRINT "GILLES"
PRINT 365 * 24 * 60 * 60
FOR I = 1 TO 5 : PRINT "GILLES" : NEXT
Note that a whole loop fits on one line with : separators — immediate mode accepts anything a program line can hold.
A2. Machine dating service
Using the Monitor (CALL -151), inspect the two reset-vector bytes at $FFFC and $FFFD. What address do they form, remembering the 6502 stores 16-bit values low byte first?
SHOW SOLUTION
]CALL -151
*FFFC.FFFD
FFFC- 62 FA
Low byte $62, high byte $FA → the reset vector points to $FA62, the Monitor's RESET routine (value varies slightly by ROM version — on an original II Plus Autostart ROM it's $FA62). Every power-on begins there.
A3. Cycle counting
Using the cycle costs from Lecture 02 (LDA #=2, STA abs=4, INX=2, BNE taken=3), how many cycles does one full pass of this loop body take, and roughly how long do all 256 iterations take at 1.023 MHz?
LOOP LDA #$A0
STA $0400
INX
BNE LOOP
SHOW SOLUTION
One iteration: 2 + 4 + 2 + 3 = 11 cycles. 256 iterations ≈ 2,816 cycles (the final not-taken BNE costs 2 instead of 3 — one cycle less, noise). At ~1,023,000 cycles/second that's about 2.75 milliseconds. For comparison, one BASIC POKE in a FOR loop costs on the order of a few thousand microseconds by itself — this is the speed gap in numbers.
Set B — Memory & soft switches (after Lecture 03)
B1. Write to the screen without PRINT
Use POKE to put a normal (non-flashing, non-inverse) letter H in the top-left corner of the screen, then an inverse I right next to it. (Screen codes: normal = ASCII + 128, inverse = ASCII − 64.)
SHOW SOLUTION
POKE 1024, 72 + 128 : REM 'H' normal = 200
POKE 1025, 73 - 64 : REM 'I' inverse = 9
$0400 = 1024 decimal is row 0, column 0; the next byte is column 1.
B2. The row-address formula
Without peeking at the lecture: compute the memory address of the first character of screen row 12, then verify by POKE-ing a byte there and seeing where it lands.
SHOW SOLUTION
Formula: address = $0400 + (row MOD 8) × $80 + (row ÷ 8) × $28.
Row 12: 12 MOD 8 = 4 → 4 × $80 = $200. 12 ÷ 8 = 1 → 1 × $28 = $28.
$0400 + $200 + $28 = $0628 = 1576 decimal.
POKE 1576, 193 : REM normal 'A' appears at row 12, column 0
B3. Keyboard spy
Write a program that reads the keyboard directly from the hardware (no GET, no INPUT): loop reading address −16384 until a value ≥ 128 appears, print the key's ASCII character, clear the strobe, and repeat. Make the Q key quit.
SHOW SOLUTION
10 K = PEEK ( - 16384)
20 IF K < 128 THEN 10
30 POKE - 16368, 0
40 C$ = CHR$ (K - 128)
50 PRINT "KEY: "; C$
60 IF C$ <> "Q" THEN 10
Bit 7 of $C000 is the "new key" flag; touching $C010 (−16368) resets it. This is exactly what GET does under the hood.
B4. Screen saver, hardware edition
Using only soft switches (no drawing commands!), write a program that flips the display between text page 1 and text page 2 once per second, forever. Pre-fill page 2 ($0800–$0BFF) with inverse characters first so you can see the difference. Bonus: explain why the characters on page 2 look like garbage before you fill it.
SHOW SOLUTION
10 REM FILL PAGE 2 WITH INVERSE '*'
20 FOR I = 2048 TO 3071 : POKE I, 10 : NEXT
30 REM FLIP FOREVER
40 POKE - 16299, 0 : REM $C055 SHOW PAGE 2
50 FOR D = 1 TO 800 : NEXT
60 POKE - 16300, 0 : REM $C054 SHOW PAGE 1
70 FOR D = 1 TO 800 : NEXT
80 GOTO 40
Before you fill it, page 2 shows whatever bytes happened to be in RAM at power-on (or leftovers from earlier programs) — the video circuit displays memory as-is; it has no concept of "initialized". That garbage pattern is RAM's power-on state made visible.
Set C — Graphics & sound (after Lecture 04)
C1. Sixteen stripes
In lo-res mode, draw all 16 colors as vertical stripes, each 2 columns wide, with the color number printed in the text window at the bottom as you draw it.
SHOW SOLUTION
10 GR
20 FOR C = 0 TO 15
30 COLOR= C
40 VLIN 0, 39 AT C * 2
50 VLIN 0, 39 AT C * 2 + 1
60 PRINT "COLOR "; C;
70 NEXT
C2. Bouncing ball
Animate a single lo-res block bouncing around the 40×40 field: move it, bounce off all four walls, erase behind it. Then answer: why does it flicker?
SHOW SOLUTION
10 GR
20 X = 20 : Y = 10 : DX = 1 : DY = 1
30 COLOR= 13 : PLOT X, Y
40 FOR D = 1 TO 20 : NEXT : REM speed limiter
50 COLOR= 0 : PLOT X, Y : REM erase
60 X = X + DX : Y = Y + DY
70 IF X = 0 OR X = 39 THEN DX = - DX
80 IF Y = 0 OR Y = 39 THEN DY = - DY
90 GOTO 30
It flickers because erase and redraw happen at different moments, and the video beam often catches the instant when the ball is erased but not yet redrawn. The fixes are (a) draw on the hidden page and flip — exercise B4's technique — or (b) go fast enough that the gap is invisible, i.e. assembly.
C3. Hi-res starburst
In hi-res, draw lines from the center (140, 96) to points spaced evenly around the screen border, cycling through HCOLOR= 1, 2, 5, 6 (green, purple, orange, blue). Look closely at the result: which lines show their color cleanly, and which come out white-ish or stair-stepped? Connect what you see to the even/odd column rule from Lecture 04.
SHOW SOLUTION
10 HGR : REM hi-res, clears to black
20 C = 1
30 FOR X = 0 TO 279 STEP 8
40 HCOLOR= C + (C > 2) * 3 : REM maps 1,2,3,4 -> 1,2,5,6... see below
50 HPLOT 140, 96 TO X, 0
60 HPLOT 140, 96 TO 279 - X, 191
70 C = C + 1 : IF C > 4 THEN C = 1
80 NEXT
(Line 40 is a compact trick: for C = 1,2 it yields HCOLOR 1,2; for C = 3,4 the expression (C>2) is 1, adding 3 → 6,7 — close enough; feel free to use an ON C GOTO or an array instead.)
What you'll see: near-vertical lines alternate colored and dark or shift by a pixel, because a colored hi-res pixel only "exists" on the correct even/odd column for its hue. Diagonals that cross both even and odd columns constantly show fringing and white segments where adjacent pixels merge. This is NTSC artifact color behaving exactly as described in Lecture 04.
C4. Play a scale
Using only PEEK(-16336) and timing loops, play something recognizable as a rising scale: 8 "notes" of increasing pitch, each lasting about half a second. Hint: pitch = how fast you toggle; duration = how many toggles. Accept that BASIC's pitch control is crude — then explain in a comment why the same program in assembly sounds pure.
SHOW SOLUTION
10 REM RISING "SCALE" - CRUDE BUT AUDIBLE
20 FOR N = 8 TO 1 STEP - 1
30 REM N = HALF-PERIOD DELAY: SMALLER = HIGHER PITCH
40 FOR T = 1 TO 400 / (9 - N)
50 X = PEEK ( - 16336)
60 FOR D = 1 TO N : NEXT
70 NEXT T
80 NEXT N
Why it sounds rough: each BASIC statement takes a variable, milliseconds-scale time to interpret, so the toggle intervals jitter and the "pitch" is really an average. Assembly toggles with cycle-exact delays (the TONE routine in Lecture 04), producing a steady square wave — a clean tone. The hardware is identical; only the timing precision differs.
Set D — BASIC programs (after Lecture 05)
D1. FizzBuzz, 1977 style
Print the numbers 1–30; for multiples of 3 print WOZ, for multiples of 5 print JOBS, for both print APPLE. (Applesoft has no MOD operator — build one from INT.)
SHOW SOLUTION
10 FOR I = 1 TO 30
20 M3 = I - INT (I / 3) * 3
30 M5 = I - INT (I / 5) * 5
40 IF M3 = 0 AND M5 = 0 THEN PRINT "APPLE" : GOTO 80
50 IF M3 = 0 THEN PRINT "WOZ" : GOTO 80
60 IF M5 = 0 THEN PRINT "JOBS" : GOTO 80
70 PRINT I
80 NEXT
D2. Reaction timer
Write a game: clear the screen, wait a random 1–4 seconds, then print GO! and beep. Time how long the player takes to press a key (count loop iterations while polling the keyboard hardware), and report a score. Detect cheaters who press before GO.
SHOW SOLUTION
10 HOME : PRINT "PRESS A KEY WHEN YOU SEE GO!"
20 POKE - 16368, 0 : REM clear strobe
30 W = INT ( RND (1) * 3000) + 1000
40 FOR D = 1 TO W
50 IF PEEK ( - 16384) > 127 THEN 200
60 NEXT
70 PRINT : PRINT "GO!" : X = PEEK ( - 16336)
80 T = 0
90 T = T + 1
100 IF PEEK ( - 16384) < 128 THEN 90
110 POKE - 16368, 0
120 PRINT "SCORE: "; T; " (LOWER = FASTER)"
130 END
200 PRINT "TOO SOON, CHEATER!" : END
The score unit is "polling loops", not milliseconds — on a 1 MHz machine with interpreted BASIC, that's the best a pure-BASIC program can do, and it's perfectly consistent between rounds on the same machine.
D3. HI-LO with a betting twist
Build a full card game: the computer shows a "card" (2–14, where 11–14 display as J/Q/K/A), the player bets some of their 100-chip stake on whether the next card is higher or lower. Equal = house wins. Handle: invalid bets, running out of chips, and a proper win screen at 500 chips. Use GOSUB subroutines for: drawing a card, taking a validated bet, and the win/lose jingles.
SHOW SOLUTION
10 REM *** HI-LO ***
20 CHIPS = 100
30 GOSUB 1000 : C1 = C
40 HOME : PRINT "CHIPS: "; CHIPS
50 PRINT "CARD: "; C$
60 GOSUB 2000 : REM take bet -> B
70 INPUT "HIGHER OR LOWER (H/L)? "; H$
80 GOSUB 1000 : C2 = C
90 PRINT "NEXT CARD: "; C$
100 W = 0
110 IF H$ = "H" AND C2 > C1 THEN W = 1
120 IF H$ = "L" AND C2 < C1 THEN W = 1
130 IF W THEN CHIPS = CHIPS + B : GOSUB 3000
140 IF NOT W THEN CHIPS = CHIPS - B : GOSUB 3100
150 IF CHIPS < 1 THEN PRINT "BUSTED!" : END
160 IF CHIPS >= 500 THEN PRINT "*** YOU BEAT THE HOUSE ***" : END
170 C1 = C2 : GOTO 40
1000 REM draw card -> C, C$
1010 C = INT ( RND (1) * 13) + 2
1020 C$ = STR$ (C)
1030 IF C = 11 THEN C$ = "J"
1040 IF C = 12 THEN C$ = "Q"
1050 IF C = 13 THEN C$ = "K"
1060 IF C = 14 THEN C$ = "A"
1070 RETURN
2000 REM validated bet -> B
2010 INPUT "YOUR BET? "; B
2020 IF B < 1 OR B > CHIPS OR B <> INT (B) THEN PRINT "1 TO "; CHIPS : GOTO 2010
2030 RETURN
3000 FOR I = 1 TO 3 : FOR J = 1 TO 30 : X = PEEK ( - 16336) : NEXT : FOR D = 1 TO 100 : NEXT : NEXT : RETURN
3100 FOR J = 1 TO 120 : X = PEEK ( - 16336) : FOR D = 1 TO 4 : NEXT : NEXT : RETURN
Structure to note: subroutines own one job each and communicate through variables (there are no parameters in Applesoft). Line 100–120's flag variable W substitutes for the missing ELSE.
Set E — Assembly (after Lecture 06)
E1. Beep from the Monitor
Using only the Monitor, enter a 3-instruction program at $0300 that calls the ROM's BELL1 routine ($FBDD) and returns. Run it. Then run it from BASIC.
SHOW SOLUTION
]CALL -151
*300: 20 DD FB 60
*300G
(beep!)
$20 = JSR, address low-byte-first ($DD $FB → $FBDD), $60 = RTS. From BASIC: CALL 768. (Yes, "3-instruction" was generous — JSR and RTS is all it takes.)
E2. Inverse video wash
Write and hand-assemble a routine at $0300 that fills the entire text page 1 with the inverse-space character ($20), turning the screen solid green. Use the four-STA pattern from Lecture 06. Verify with 300L that your bytes disassemble to what you intended.
SHOW SOLUTION
0300: A9 20 LDA #$20 ; inverse space
0302: A2 00 LDX #$00
0304: 9D 00 04 STA $0400,X
0307: 9D 00 05 STA $0500,X
030A: 9D 00 06 STA $0600,X
030D: 9D 00 07 STA $0700,X
0310: E8 INX
0311: D0 F1 BNE $0304 ; F1 = -15
0313: 60 RTS
*300: A9 20 A2 00 9D 00 04 9D 00 05 9D 00 06 9D 00 07 E8 D0 F1 60
*300G
The screen floods instantly — compare with the visible painting of the BASIC equivalent. The branch offset: from the byte after D0 F1 ($0313) back to $0304 is −15 = $F1.
E3. Typewriter
Write an assembly program: read keys with RDKEY ($FD0C) and echo each with COUT ($FDED), until the user presses Esc (RDKEY returns $9B for Esc), then return cleanly. You now have the skeleton of every text editor. Hand-assemble it — CMP immediate is $C9, BEQ is $F0.
SHOW SOLUTION
0300: 20 0C FD JSR $FD0C ; RDKEY -> A
0303: C9 9B CMP #$9B ; Esc?
0305: F0 06 BEQ $030D ; yes -> done
0307: 20 ED FD JSR $FDED ; COUT: echo it
030A: 4C 00 03 JMP $0300 ; next key
030D: 60 RTS
*300: 20 0C FD C9 9B F0 06 20 ED FD 4C 00 03 60
*300G
Every keypress appears as you type; Esc drops you back to the Monitor prompt. Note the flow: call ROM, compare, branch, loop — with the branch target computed forward this time ($0307 + 6 = $030D).
Finished everything? Head to Getting Started and set up the cross-development toolchain — then rebuild E2 and E3 with a real assembler instead of hand-typed hex, and start your own project.