Adding and Taking Away
The most ordinary thing of all, finally: maths. inc, dec, add and sub on a value — and the carry flag, the machine's way of telling you a byte just overflowed.
You can move values, compare them, loop, branch, point and call — and yet you've never done the most ordinary thing a program does: arithmetic. Before you build a game, the last pieces of plain fluency. This one is the most everyday of all: LET x = x + 1.
On the Z80 that's a single instruction:
inc a— add 1 toA.dec atakes 1 away.add a, n— addntoA.sub ntakesnaway.add a, b— add registerBtoA(add two values together).
There's a catch you already half-know: A holds eight bits, 0–255. Add past 255 and it can't hold the answer — it wraps round to 0 and sets the carry flag. That's the carry we borrowed in Test, Then Jump; here's where it comes from. (More in the Try this.)
What you'll see by the end
A cyan border — colour 5. But we never loaded 5; we computed it: start at 2, count up twice, add one. In a real game the numbers are a score or a position you don't know ahead of time. Here they're constants so you can check the sum yourself.
Computing a value
; ============================================================================
; PRIMER — Beat 12: Adding and Taking Away
; ============================================================================
; You've moved values, compared them, looped and branched -- but never done
; the most ordinary thing of all: maths. BASIC's `LET x = x + 1` becomes one
; instruction here.
;
; inc a -- add 1 to A (dec a takes 1 away)
; add a, n -- add n to A (sub n takes n away)
; add a, b -- add register B to A
;
; A holds 8 bits, 0-255. Add past 255 and it wraps round to 0 and sets the
; CARRY flag -- the machine's "it overflowed" signal (the carry we borrowed
; back in Test, Then Jump). We meet it properly in the Try this.
;
; Here we COMPUTE the border colour rather than just loading it: start at 2,
; count up twice, add one -- 5, cyan. In a game the numbers are a score or a
; position you don't know in advance; here they're constants so you can check.
; ============================================================================
org 32768
start:
ld a, 2 ; start at 2
inc a ; + 1 -> 3
inc a ; + 1 -> 4
add a, 1 ; + 1 -> 5 (cyan)
out ($FE), a ; show the computed result
.loop:
halt
jr .loop
end start
2, inc to 3, inc to 4, add a, 1 to 5 — then show it. Same shape as loading the border, but the value is worked out instead of written down. That's the whole difference between a constant and a calculation.
Assemble and run
pasmonext --sna arithmetic.asm primer.sna
A cyan border, arrived at by adding.
Try this: past the edge of a byte
Replace the body with ld a, 255 then add a, 1, and watch A in the register view: it reads 0, not 256. A byte can't hold 256, so it wraps to 0 — and the carry flag is set, the machine flagging "that overflowed." A jr c would fire on it. This is how you'd catch a score rolling over, and how numbers bigger than a byte are built (next unit but one).
Try this: taking away
Swap in sub: ld a, 6 then sub 2 leaves 4 (green). sub past zero wraps the other way — ld a, 0 then sub 1 gives 255, and sets carry again (this time as a "borrow"). Predict each before you run it.
When it's wrong, see why
pasmonexterrors onadd 1. The 8-bit add names its target: it'sadd a, 1, notadd 1. (sub, oddly, is justsub 1—Ais assumed.)- The border colour is "wrong". It isn't — the border shows only the low 3 bits, so any result is shown mod 8. Check the value in the register view for the true answer.
- You expected 256 and got 0. That's the lesson, not a bug: a byte stops at 255.
What you've learnt
inc/add add and dec/sub take away — and a result past 255 (or below 0) wraps and sets the carry flag, the machine's overflow signal.
What's next
Sometimes you don't want to change a whole number — just one bit of it. Next — Working With Bits — and, or and xor let you set, clear and flip individual bits, like switching a single attribute's BRIGHT on without touching its colour.