The Fold
Somewhere to arrive: reach an empty pen and she's safe for good — a white resident in the fold, drawn on the scene's new second bitplane. Fill all five and the level is won.
Until now, the top of the screen was scenery. Now it’s home.
Walk her up to the fence and she stops — unless she’s facing an open pen, in which case she trots in and stays: a white resident appears between the fence posts, the next sheep steps up from the field, and the crossing begins again. Five pens. Fill them all and the level is won. The game has had teeth since Unit 6 and stakes since Unit 7; this is the unit it gets a point.
The Display

One home, four to go. The capture script steered a sheep up from the field, threaded all three lanes on the first attempt — determinism rewards a well-timed departure — and into the middle pen. Five icons still in hand: arriving costs nothing. The next sheep waits at the start.
The Second Bitplane
A small art problem forces a real hardware step. The resident sheep must read white, inside a band where plane-1 pixels are fence brown — one plane can’t show two colours in the same band. So the scene grows its second bitplane, and every band gains four colours:
| Pixel value | Planes | Means |
|---|---|---|
00 |
neither | the band colour (COLOR00) |
01 |
plane 1 | fence / bridge / dashes / icons — everything so far, unchanged |
10 |
plane 2 | the fold’s residents (COLOR02, white) |
11 |
both | COLOR03 — spare (set white; fence and sheep never overlap) |
That table is why the upgrade is painless: plane 1’s pixels keep their meaning exactly, because 01 is the same colour index it always was. The changes are mechanical — BPLCON0 says two planes ($2200), the Copper list gains BPL2MOD and a second pointer pair for the CPU to poke, the data section gains 10KB of plane2 — and one new habit: layers by plane. Background detail on plane 1, residents on plane 2, never interfering, erasable independently. (You’ve been using this idea all along — the sprites are layers the chip composites. Now the playfield does it too.)
The Pens Are a Table
trypen:
move.w sheepx,d0
addq.w #8,d0 ; D0 = her centre
lea pentab,a2
moveq #5-1,d6
.pen:
cmp.w (a2),d0 ; Left of this pen?
blt.s .nextpen
cmp.w 2(a2),d0 ; Right of it?
bgt.s .nextpen
tst.b 5(a2) ; Already taken?
bne.s .nextpen
; --- She's in. A resident for the fold. ---
move.b #1,5(a2)
move.w 4(a2),d0 ; Glyph byte column
and.w #$ff00,d0
lsr.w #8,d0
bsr penglyph
move.w #SHEEP_X,sheepx ; The next sheep steps up
move.w #SHEEP_Y,sheepy
move.w #PEN_BEAT,squashtimer
subq.w #1,unpenned ; A full fold wins
bne.s .out
move.w #1,won
.out:
rts
.nextpen:
addq.l #6,a2
dbf d6,.pen
rts ; Fence, post or a full pen: no way through
pentab is Unit 8’s lesson again — identity in data: each pen is the span of sheep-centre positions it accepts, the byte column its resident is drawn at, and a taken flag. trypen runs only at the fence line: find the pen she’s facing, refuse if it’s taken (or if she’s facing a post), otherwise mark it, stamp the resident glyph into plane 2, send the next sheep up, and count down unpenned — zero means the fold is full and won goes up.
Movement meets the fold in steer: the clamp that used to stop her at the screen edge now stops her at FENCE_Y, and the up branch at the fence calls trypen instead of stepping. The fence is the ceiling; pens are the only doors.
The win state mirrors game over — steer and showframe each gain a won gate — but the picture differs in the way that matters: game over is an empty farm; victory is a full fold, five white residents watching the traffic that can’t touch them any more.
Experiment: Re-Architect the Fold
- Take a pen away — set one
pentabflag to 1 at assembly time. Four homes, five sheep: the flock now has a spare. How does knowing that change how you play the last crossing? PEN_BEAT equ 50— a long, proud pause after each arrival. Does ceremony improve arriving, or slow the game down? Compare with the squash beat’s length and decide what the difference between the two should say.- Widen pen spans by 8 each side, so a sheep half-over a post still counts. Kinder — and sloppier? Where’s the line between forgiving and mushy?
- Make the resident glyphs accumulate left to right regardless of which pen she entered. Easier to read at a glance — but now the fold lies about where she went in. Which truth matters?
The Complete Code
;──────────────────────────────────────────────────────────────
; FLOCK - A sheep-crossing arcade game for the Commodore Amiga
; Unit 9: The Fold
;
; Somewhere to arrive. Reach an empty pen and the sheep trots
; in, safe for good — a white resident drawn into the fold —
; and the next sheep steps up from the field. Fill all five
; pens and the level is won. The fold's residents need white
; pixels in a band whose plane-1 colour is fence brown, so
; the scene grows its second bitplane: four colours per band.
;──────────────────────────────────────────────────────────────
;══════════════════════════════════════════════════════════════
; TWEAKABLE VALUES — Change these and see what happens!
;══════════════════════════════════════════════════════════════
; Colours are $0RGB (4 bits per component, values 0-F)
COLOUR_FOLD_GRASS equ $0480 ; The fold's pasture
COLOUR_HEDGE equ $0350 ; Hedgerow between fold and stream
COLOUR_WATER equ $036A ; The stream
COLOUR_BANK equ $0350 ; Grassy bank below the stream
COLOUR_LANE equ $0666 ; The lane's tarmac
COLOUR_VERGE equ $0350 ; Verge below the lane
COLOUR_FIELD equ $0470 ; The field where the flock waits
COLOUR_FENCE equ $0531 ; Pen walls (bitplane, fold band)
COLOUR_WOOD equ $0852 ; The footbridge (bitplane, stream band)
COLOUR_DASH equ $0EEE ; Lane markings (bitplane, lane band)
COLOUR_TUFT equ $0360 ; Spare (bitplane, grass bands)
COLOUR_WOOL equ $0EEE ; The sheep's fleece (sprite colour 1)
COLOUR_FACE equ $0210 ; Her face, ears and tail (sprite colour 2)
COLOUR_SHADE equ $0BBB ; Fleece shading (sprite colour 3)
COLOUR_TRACTOR equ $0B20 ; The tractor's bodywork (sprite colour 1)
COLOUR_TYRE equ $0210 ; Wheels and trim (sprite colour 2)
COLOUR_CAB equ $0999 ; The cab roof (sprite colour 3)
COLOUR_WOODWORK equ $0742 ; The hay cart's bed (sprites 4-5)
COLOUR_HAY equ $0C92 ; Its heaped load
COLOUR_ROVER equ $0364 ; The Land Rover's paint (sprites 6-7)
COLOUR_ROOF equ $0AAA ; Its roof panel
; Where each band begins (screen row 0-255, top to bottom)
ROW_HEDGE equ 40
ROW_STREAM equ 48
ROW_BANK equ 80
ROW_LANE equ 96
ROW_VERGE equ 160
ROW_FIELD equ 176
; Where the sheep starts, and how she moves
SHEEP_X equ 152
SHEEP_Y equ 200
STEP equ 8 ; Pixels per hop
COOLDOWN equ 6 ; Frames between hops
; The traffic: three lanes, three rhythms
TRACTOR_Y equ 98 ; Top lane
TRACTOR_SPEED equ 2 ; Steady, rightward
ROVER_Y equ 120 ; Middle lane
ROVER_SPEED equ -3 ; Fast, leftward
CART_Y equ 142 ; Bottom lane — crossed first
CART_SPEED equ -1 ; Plodding, leftward
; How long the world stops when a sheep is lost
SQUASH_BEAT equ 25 ; Frames of stillness
; The flock
FLOCK_SIZE equ 5 ; Sheep in hand at the start
; The fold's pens
PEN_ROW equ 16 ; Where a resident sheep settles
FENCE_Y equ 24 ; She stops here unless a pen is open
PEN_BEAT equ 15 ; Frames of calm after a penning
; The HUD strip at the foot of the screen
ROW_HUD equ 240
COLOUR_HUD equ $0231 ; The strip itself
COLOUR_ICON equ $0EEE ; Sheep icons (bitplane, HUD band)
;══════════════════════════════════════════════════════════════
; HARDWARE REGISTERS
;══════════════════════════════════════════════════════════════
CUSTOM equ $dff000
DMACON equ $096 ; DMA control (write)
INTENA equ $09a ; Interrupt enable (write)
INTREQ equ $09c ; Interrupt request (write)
COP1LC equ $080 ; Copper list pointer
COPJMP1 equ $088 ; Copper restart strobe
VPOSR equ $004 ; Beam position
JOY1DAT equ $00c ; Joystick, control port 2
CLXDAT equ $00e ; Collision data (read clears it!)
CLXCON equ $098 ; Collision control
BPLCON0 equ $100 ; Bitplane control
BPLCON1 equ $102 ; Scroll
BPLCON2 equ $104 ; Priority
BPL1MOD equ $108 ; Odd plane modulo
DDFSTRT equ $092 ; Display data fetch start
DDFSTOP equ $094 ; Display data fetch stop
DIWSTRT equ $08e ; Display window start
DIWSTOP equ $090 ; Display window stop
BPL1PTH equ $0e0 ; Bitplane 1 pointer (high)
BPL1PTL equ $0e2 ; Bitplane 1 pointer (low)
BPL2PTH equ $0e4 ; Bitplane 2 pointer (high)
BPL2PTL equ $0e6 ; Bitplane 2 pointer (low)
BPL2MOD equ $10a ; Even plane modulo
SPR0PTH equ $120 ; Sprite 0 pointer (high)
COLOR00 equ $180 ; Background colour
COLOR01 equ $182 ; Bitplane colour 1
COLOR02 equ $184 ; Bitplane colour 2 (plane 2)
COLOR03 equ $186 ; Bitplane colour 3 (both planes)
COLOR17 equ $1a2 ; Sprite 0/1 colour 1
COLOR18 equ $1a4 ; Sprite 0/1 colour 2
COLOR19 equ $1a6 ; Sprite 0/1 colour 3
COLOR21 equ $1aa ; Sprite 2/3 colour 1
COLOR22 equ $1ac ; Sprite 2/3 colour 2
COLOR23 equ $1ae ; Sprite 2/3 colour 3
COLOR25 equ $1b2 ; Sprite 4/5 colour 1
COLOR26 equ $1b4 ; Sprite 4/5 colour 2
COLOR27 equ $1b6 ; Sprite 4/5 colour 3
COLOR29 equ $1ba ; Sprite 6/7 colour 1
COLOR30 equ $1bc ; Sprite 6/7 colour 2
COLOR31 equ $1be ; Sprite 6/7 colour 3
ROW_BYTES equ 40 ; 320 pixels / 8
;══════════════════════════════════════════════════════════════
; CODE (Chip RAM — the Copper, planes and sprites live here)
;══════════════════════════════════════════════════════════════
section code,code_c
start:
lea CUSTOM,a5 ; A5 = custom chip base ($DFF000)
; --- Take over the machine ---
move.w #$7fff,INTENA(a5) ; Disable all interrupts
move.w #$7fff,INTREQ(a5) ; Clear pending interrupts
move.w #$7fff,DMACON(a5) ; Disable all DMA
; --- Point the Copper's bitplane MOVEs at our planes ---
lea copbpl,a1
lea plane,a0
move.l a0,d0
move.w d0,6(a1) ; Low word into the BPL1PTL move
swap d0
move.w d0,2(a1) ; High word into the BPL1PTH move
lea plane2,a0
move.l a0,d0
move.w d0,14(a1) ; And the same for plane 2
swap d0
move.w d0,10(a1)
; --- Point sprite 0 at the sheep, the rest at nothing ---
lea copsprites,a1 ; Eight pointer pairs in the list
lea sheep0,a0
move.l a0,d0
move.w d0,6(a1) ; Sprite 0 low word
swap d0
move.w d0,2(a1) ; Sprite 0 high word
lea nullspr,a0 ; Sprites 1-7: an empty sprite
move.l a0,d0
moveq #7-1,d6
.nulls:
lea 8(a1),a1 ; Next pointer pair in the list
move.w d0,6(a1)
swap d0
move.w d0,2(a1)
swap d0
dbf d6,.nulls
; --- ...except the traffic: sprites 2, 4 and 6.
; Each vehicle gets the EVEN sprite of its own pair, so
; each lives in its own collision group: tractor in 2/3
; (bit 9 against the sheep), cart in 4/5 (bit 10),
; Land Rover in 6/7 (bit 11) — and its own palette.
lea copsprites+16,a1 ; Sprite 2: the tractor
lea tractor,a0
move.l a0,d0
move.w d0,6(a1)
swap d0
move.w d0,2(a1)
lea copsprites+32,a1 ; Sprite 4: the hay cart
lea cart,a0
move.l a0,d0
move.w d0,6(a1)
swap d0
move.w d0,2(a1)
lea copsprites+48,a1 ; Sprite 6: the Land Rover
lea rover,a0
move.l a0,d0
move.w d0,6(a1)
swap d0
move.w d0,2(a1)
; --- Arm collision detection ---
move.w #$0000,CLXCON(a5) ; Even sprites always take part;
; we need nothing extra for 0-vs-2
move.w CLXDAT(a5),d0 ; Prime: reading clears the latches
; --- Draw the farmyard's detail into the bitplane ---
bsr drawfarmyard
bsr drawflock ; The flock in hand, bottom-left
; --- Place the sheep at her starting spot ---
bsr updsprite
; --- Install Copper list ---
lea copperlist,a0
move.l a0,COP1LC(a5)
move.w d0,COPJMP1(a5) ; Strobe: restart Copper from COP1LC
; --- Enable DMA ---
move.w #$83a0,DMACON(a5) ; SET + DMAEN + BPLEN + COPEN + SPREN
; === Main Loop ===
mainloop:
; Wait for vertical blank — in two phases. If we only
; waited FOR line 0, a fast loop body could finish while
; the beam is still ON line 0 and run again in the same
; frame. Wait to leave line 0 first, then to reach it.
move.l #$1ff00,d1 ; Mask: bits 8-16 of beam position
.vbleave:
move.l VPOSR(a5),d0 ; Read beam position
and.l d1,d0 ; Isolate line number
beq.s .vbleave ; Loop while still on line 0
.vbwait:
move.l VPOSR(a5),d0 ; Read beam position
and.l d1,d0 ; Isolate line number
bne.s .vbwait ; Loop until line 0 again
bsr steer ; Read the stick, maybe hop
bsr drivelanes ; All the traffic, one mover
bsr checksquash ; Did the lane win?
bsr updsprite ; Position is data: rewrite POS/CTL
bsr showframe ; Point sprite 0 at this step's image
; Check left mouse button (active low at CIAA)
btst #6,$bfe001 ; CIAA Port A, bit 6
bne.s mainloop ; Not pressed — keep going
; Button pressed — halt
.halt:
bra.s .halt
;══════════════════════════════════════════════════════════════
; STEER — read the joystick, hop the sheep
;
; JOY1DAT is control port 2. The decode is famously sideways:
; right = bit 1 left = bit 9
; down = bit 0 XOR bit 1 up = bit 8 XOR bit 9
; One XOR of the register with itself-shifted turns the two
; awkward pairs into plain testable bits.
;
; A hop is STEP pixels; COOLDOWN frames must pass between hops
; — that's what makes her *step* like a sheep rather than glide
; like a cursor.
;══════════════════════════════════════════════════════════════
steer:
tst.w won ; Fold full — nothing to steer
bne.s .frozen
tst.w gameover ; No flock, no shepherd
bne.s .frozen
tst.w squashtimer ; Mid squash-beat? She can't move
beq.s .alive
.frozen: rts
.alive:
tst.w cooldown
beq.s .ready
subq.w #1,cooldown ; Still mid-hop rhythm — wait
rts
.ready:
move.w JOY1DAT(a5),d0 ; Read the stick
move.w d0,d1
lsr.w #1,d1
eor.w d0,d1 ; Now: bit 0 = down, bit 8 = up
btst #8,d1 ; Up?
beq.s .notup
cmp.w #FENCE_Y,sheepy ; At the fence line?
bgt.s .climb
bsr trypen ; Only a pen lets her past
bra .done
.climb:
sub.w #STEP,sheepy
bra.s .stepped
.notup:
btst #0,d1 ; Down?
beq.s .notdown
add.w #STEP,sheepy
bra.s .stepped
.notdown:
btst #9,d0 ; Left?
beq.s .notleft
sub.w #STEP,sheepx
bra.s .stepped
.notleft:
btst #1,d0 ; Right?
beq.s .done ; Stick centred — no hop
add.w #STEP,sheepx
.stepped:
move.w #COOLDOWN,cooldown ; Set the hop rhythm
eori.w #1,curframe ; The other feet, next picture
; --- Hold her inside the farm ---
tst.w sheepx
bge.s .xlow
clr.w sheepx
.xlow: cmp.w #320-16,sheepx
ble.s .xhigh
move.w #320-16,sheepx
.xhigh: cmp.w #FENCE_Y,sheepy ; The fence is the ceiling;
bge.s .ylow ; pens are the only way past
move.w #FENCE_Y,sheepy
.ylow: cmp.w #ROW_HUD-16,sheepy ; The HUD strip is not a pasture
ble.s .done
move.w #ROW_HUD-16,sheepy
.done:
rts
;══════════════════════════════════════════════════════════════
; DRIVELANES — advance all the traffic
;
; Unit 5's mover, made data. Each vehicle is a row in vehtab —
; WHERE its x lives, and how fast it moves (signed: negative
; drives leftward). One loop walks the table: add the speed,
; wrap off whichever edge the speed points at, store. Adding a
; vehicle to the game is adding a row to the table.
;══════════════════════════════════════════════════════════════
drivelanes:
lea vehtab,a2
moveq #3-1,d6 ; Three vehicles
.veh:
move.l (a2)+,a0 ; A0 = where this one's x lives
move.w (a2)+,d1 ; D1 = its speed (signed)
move.w (a0),d0
add.w d1,d0
tst.w d1
bmi.s .leftward
cmp.w #320,d0 ; Rightward: clear of the right edge?
blt.s .store
move.w #-16,d0 ; Re-enter from the left
bra.s .store
.leftward:
cmp.w #-16,d0 ; Leftward: clear of the left edge?
bgt.s .store
move.w #320,d0 ; Re-enter from the right
.store:
move.w d0,(a0)
dbf d6,.veh
rts
vehtab: dc.l tractx
dc.w TRACTOR_SPEED
dc.l cartx
dc.w CART_SPEED
dc.l roverx
dc.w ROVER_SPEED
;══════════════════════════════════════════════════════════════
; CHECKSQUASH — read the collision latches, judge the lane
;
; CLXDAT accumulates collisions as Denise draws, and READING
; IT CLEARS IT — so read it exactly once per frame and keep
; the copy. Bit 9 means "sprite 0 or 1 touched sprite 2 or 3":
; our sheep met our tractor, pixel against pixel. The hardware
; compared every overlapping pixel pair for us, for free.
;══════════════════════════════════════════════════════════════
checksquash:
tst.w squashtimer ; Already mid-beat?
beq.s .watch
subq.w #1,squashtimer ; Count the stillness down
bne.s .out
move.w CLXDAT(a5),d0 ; Beat over: flush the contact
.out: rts ; that accumulated during it
.watch:
tst.w gameover ; Nothing left to lose?
bne.s .safe
move.w CLXDAT(a5),d0 ; Read once — this clears it
and.w #$0e00,d0 ; Bits 9/10/11: the sheep against
beq.s .safe ; ANY of the three vehicle groups
; --- Squashed. One fewer in hand. ---
subq.w #1,lives
bsr drawflock ; Redraw the strip
tst.w lives
bgt.s .next ; Sheep remain — send the next one
move.w #1,gameover ; The field is empty
rts
.next:
move.w #SHEEP_X,sheepx ; The next sheep steps up
move.w #SHEEP_Y,sheepy
move.w #SQUASH_BEAT,squashtimer
.safe:
rts
;══════════════════════════════════════════════════════════════
; SETPOS — pack screen (x, y) into one sprite's POS/CTL
; a0 = sprite structure d0 = x d1 = y
;
; Unit 3's packing, generalised: any sprite, any position. Beam
; coordinates: VSTART = y + $2C, HSTART = x + $80 — and the
; ninth bits ride in CTL's low flags.
;══════════════════════════════════════════════════════════════
setpos:
add.w #$2c,d1 ; D1 = VSTART (beam line)
move.w d1,d2
add.w #16,d2 ; D2 = VSTOP (16 rows tall)
add.w #$80,d0 ; D0 = HSTART (beam position)
; POS = VSTART[7:0] << 8 | HSTART[8:1]
move.w d1,d3
lsl.w #8,d3
move.w d0,d4
lsr.w #1,d4
and.w #$ff,d4
or.w d4,d3
move.w d3,(a0) ; Write POS
; CTL = VSTOP[7:0] << 8 | V8START<<2 | V8STOP<<1 | H0START
move.w d2,d3
and.w #$ff,d3
lsl.w #8,d3
btst #8,d1 ; VSTART's ninth bit
beq.s .nv8s
or.w #%100,d3
.nv8s: btst #8,d2 ; VSTOP's ninth bit
beq.s .nv8e
or.w #%010,d3
.nv8e: btst #0,d0 ; HSTART's odd-pixel bit
beq.s .nh0
or.w #%001,d3
.nh0: move.w d3,2(a0) ; Write CTL
rts
;══════════════════════════════════════════════════════════════
; UPDSPRITE — place every sprite for this frame
;
; One routine owns every position write: the sheep (both step
; images, so whichever showframe picks she stands in the same
; place) and the tractor.
;══════════════════════════════════════════════════════════════
updsprite:
lea sheep0,a0
move.w sheepx,d0
move.w sheepy,d1
bsr setpos
lea sheep1,a0
move.w sheepx,d0
move.w sheepy,d1
bsr setpos
lea tractor,a0
move.w tractx,d0
move.w #TRACTOR_Y,d1
bsr setpos
lea cart,a0
move.w cartx,d0
move.w #CART_Y,d1
bsr setpos
lea rover,a0
move.w roverx,d0
move.w #ROVER_Y,d1
bsr setpos
rts
;══════════════════════════════════════════════════════════════
; SHOWFRAME — point sprite 0 at this step's image
;
; Animation is nothing but choosing which data the channel
; fetches. The Copper list's sprite 0 pointer words are
; rewritten with whichever picture curframe names — the same
; poke the startup code did, now done every frame.
;══════════════════════════════════════════════════════════════
showframe:
lea nullspr,a0 ; Game over, or the whole flock
tst.w gameover ; home: no sheep on the move
bne.s .picked
tst.w won
bne.s .picked
lea sheep0,a0
tst.w curframe
beq.s .picked
lea sheep1,a0
.picked:
move.l a0,d0
lea copsprites,a1
move.w d0,6(a1) ; Sprite 0 low word
swap d0
move.w d0,2(a1) ; Sprite 0 high word
rts
;══════════════════════════════════════════════════════════════
; DRAW THE FARMYARD (unchanged from Unit 1)
;══════════════════════════════════════════════════════════════
drawfarmyard:
; --- The fold's pens (rows 4-35) ---
moveq #0,d0 ; x = byte 0
moveq #4,d1 ; row 4
moveq #ROW_BYTES,d2 ; full width
moveq #4,d3 ; 4 rows thick
bsr rectfill
lea penposts,a2 ; Post positions (byte columns)
moveq #6-1,d6 ; Six posts
.posts:
moveq #0,d0
move.b (a2)+,d0 ; x = next post column
moveq #8,d1 ; rows 8-35
moveq #1,d2 ; one byte wide
moveq #28,d3
bsr rectfill
dbf d6,.posts
; --- The footbridge (rows 48-79, mid-stream) ---
moveq #18,d0 ; byte 18 = pixel 144
moveq #ROW_STREAM,d1
moveq #4,d2 ; 32 pixels wide
moveq #32,d3 ; the stream's full height
bsr rectfill
; --- Lane markings: two dashed lines (rows 116, 136) ---
moveq #116,d1
bsr dashline
move.w #136,d1
; falls through
;──────────────────────────────────────────────────────────────
; dashline — a row of dashes across the lane
; d1 = starting row. 2 bytes on, 2 bytes off, 4 rows thick.
;──────────────────────────────────────────────────────────────
dashline:
moveq #0,d0 ; x = byte 0
.dash:
move.w d1,-(sp) ; rectfill trashes d1
move.w d0,-(sp) ; ...and d0
moveq #2,d2 ; 2 bytes of dash
moveq #4,d3 ; 4 rows thick
bsr rectfill
move.w (sp)+,d0
move.w (sp)+,d1
addq.w #4,d0 ; next dash 4 bytes along
cmp.w #ROW_BYTES,d0
blt.s .dash
rts
;──────────────────────────────────────────────────────────────
; rectfill — set a byte-aligned rectangle of pixels
; d0 = x (bytes) d1 = row d2 = width (bytes) d3 = height
; Trashes d1, d4, d5, a0, a1.
;──────────────────────────────────────────────────────────────
rectfill:
lea plane,a0
move.w d1,d4
mulu #ROW_BYTES,d4 ; row * 40
add.w d0,d4 ; + x
adda.w d4,a0 ; A0 = first byte of the rectangle
move.w d3,d4 ; D4 = rows to go
.row:
movea.l a0,a1
move.w d2,d5 ; D5 = bytes to go
.col:
move.b #$ff,(a1)+ ; 8 pixels on
subq.w #1,d5
bne.s .col
lea ROW_BYTES(a0),a0 ; down one row
subq.w #1,d4
bne.s .row
rts
penposts: dc.b 0,8,16,24,32,39 ; Byte columns of the six posts
even
;══════════════════════════════════════════════════════════════
; TRYPEN — at the fence, try to enter the pen she's facing
;
; Each pentab row is a pen: the span of sheep-centre x values
; it accepts, the byte column its resident glyph is drawn at,
; and a flag byte that remembers it's taken. A hit pens her:
; the resident appears (plane 2 — white among the brown
; fences), the next sheep steps up, and a full fold wins.
;══════════════════════════════════════════════════════════════
trypen:
move.w sheepx,d0
addq.w #8,d0 ; D0 = her centre
lea pentab,a2
moveq #5-1,d6
.pen:
cmp.w (a2),d0 ; Left of this pen?
blt.s .nextpen
cmp.w 2(a2),d0 ; Right of it?
bgt.s .nextpen
tst.b 5(a2) ; Already taken?
bne.s .nextpen
; --- She's in. A resident for the fold. ---
move.b #1,5(a2)
move.w 4(a2),d0 ; Glyph byte column
and.w #$ff00,d0
lsr.w #8,d0
bsr penglyph
move.w #SHEEP_X,sheepx ; The next sheep steps up
move.w #SHEEP_Y,sheepy
move.w #PEN_BEAT,squashtimer
subq.w #1,unpenned ; A full fold wins
bne.s .out
move.w #1,won
.out:
rts
.nextpen:
addq.l #6,a2
dbf d6,.pen
rts ; Fence, post or a full pen: no way through
;──────────────────────────────────────────────────────────────
; penglyph — stamp the resident-sheep glyph into PLANE 2
; d0 = x (bytes). Row is PEN_ROW; the glyph is the HUD icon.
;──────────────────────────────────────────────────────────────
penglyph:
lea plane2,a0
move.w #PEN_ROW,d4
mulu #ROW_BYTES,d4
add.w d0,d4
adda.w d4,a0
lea sheepicon,a2
moveq #8-1,d4
.row:
move.b (a2)+,(a0)
lea ROW_BYTES(a0),a0
dbf d4,.row
rts
; Per pen: centre-x span (min, max), glyph byte
; column (high byte of the word), taken flag, pad
pentab: dc.w 8,63
dc.b 4,0
even
dc.w 72,127
dc.b 12,0
even
dc.w 136,191
dc.b 20,0
even
dc.w 200,255
dc.b 28,0
even
dc.w 264,311
dc.b 36,0
even
;══════════════════════════════════════════════════════════════
; DRAWFLOCK — the sheep in hand, as icons on the HUD strip
;
; One 8x8 glyph per sheep still in hand, drawn at the bottom
; left; the strip is cleared first so a lost sheep disappears.
; The icons are bitplane pixels — the HUD band's COLOR01 makes
; them white, the same per-band trick as the fence and dashes.
;══════════════════════════════════════════════════════════════
drawflock:
; Clear the icon area (a row of byte-rectangles)
moveq #1,d0 ; From byte 1
move.w #ROW_HUD+4,d1
moveq #12,d2 ; Room for the whole flock
moveq #8,d3
bsr rectclear
; One glyph per sheep in hand
move.w lives,d7
ble.s .none ; Empty hand, empty strip
moveq #1,d6 ; First icon at byte 1
.icons:
move.w d6,d0
move.w #ROW_HUD+4,d1
lea sheepicon,a2
bsr drawglyph
addq.w #2,d6 ; Two bytes along for the next
subq.w #1,d7
bne.s .icons
.none:
rts
;──────────────────────────────────────────────────────────────
; drawglyph — copy an 8-row, 1-byte-wide glyph into the plane
; d0 = x (bytes) d1 = row a2 = glyph (8 bytes)
; Trashes d1, d4, a0.
;──────────────────────────────────────────────────────────────
drawglyph:
lea plane,a0
move.w d1,d4
mulu #ROW_BYTES,d4
add.w d0,d4
adda.w d4,a0
moveq #8-1,d4
.row:
move.b (a2)+,(a0)
lea ROW_BYTES(a0),a0
dbf d4,.row
rts
;──────────────────────────────────────────────────────────────
; rectclear — rectfill's opposite: clear a byte-aligned block
; d0 = x (bytes) d1 = row d2 = width (bytes) d3 = height
; Trashes d1, d4, d5, a0, a1.
;──────────────────────────────────────────────────────────────
rectclear:
lea plane,a0
move.w d1,d4
mulu #ROW_BYTES,d4
add.w d0,d4
adda.w d4,a0
move.w d3,d4
.row:
movea.l a0,a1
move.w d2,d5
.col:
clr.b (a1)+
subq.w #1,d5
bne.s .col
lea ROW_BYTES(a0),a0
subq.w #1,d4
bne.s .row
rts
sheepicon: dc.b %00100100 ; A sheep, in eight bytes:
dc.b %01111110 ; ears up top,
dc.b %11111111 ; a fat woolly middle,
dc.b %11111111
dc.b %11111111
dc.b %01111110
dc.b %00111100 ; tapering to
dc.b %00011000 ; a little tail
even
;══════════════════════════════════════════════════════════════
; COPPER LIST — the farmyard, plus eight sprite pointers
;══════════════════════════════════════════════════════════════
copperlist:
; --- Display setup ---
dc.w DIWSTRT,$2c81 ; Window: top-left
dc.w DIWSTOP,$2cc1 ; Window: bottom-right
dc.w DDFSTRT,$0038 ; Fetch start (lores)
dc.w DDFSTOP,$00d0 ; Fetch stop
dc.w BPLCON0,$2200 ; 2 bitplanes, colour burst on
dc.w BPLCON1,$0000 ; No scroll
dc.w BPLCON2,$0024 ; Sprites in front of playfield
dc.w BPL1MOD,$0000 ; No modulo — rows pack tight
dc.w BPL2MOD,$0000
copbpl:
dc.w BPL1PTH,$0000 ; Plane addresses, poked in
dc.w BPL1PTL,$0000 ; by the CPU at startup
dc.w BPL2PTH,$0000
dc.w BPL2PTL,$0000
copsprites:
dc.w SPR0PTH+0,$0000 ; Sprite 0: the sheep (poked in)
dc.w SPR0PTH+2,$0000
dc.w SPR0PTH+4,$0000 ; Sprites 1-7: the null sprite
dc.w SPR0PTH+6,$0000
dc.w SPR0PTH+8,$0000
dc.w SPR0PTH+10,$0000
dc.w SPR0PTH+12,$0000
dc.w SPR0PTH+14,$0000
dc.w SPR0PTH+16,$0000
dc.w SPR0PTH+18,$0000
dc.w SPR0PTH+20,$0000
dc.w SPR0PTH+22,$0000
dc.w SPR0PTH+24,$0000
dc.w SPR0PTH+26,$0000
dc.w SPR0PTH+28,$0000
dc.w SPR0PTH+30,$0000
; --- Plane-2 colours: a resident sheep is white wherever
; she settles (colour 2 = plane 2 alone; colour 3 = both
; planes — fence-and-sheep never overlap, white is safe)
dc.w COLOR02,$0EEE
dc.w COLOR03,$0EEE
; --- The sheep's colours (sprites 0-1 share 17-19) ---
dc.w COLOR17,COLOUR_WOOL
dc.w COLOR18,COLOUR_FACE
dc.w COLOR19,COLOUR_SHADE
; --- The tractor's colours (sprites 2-3 share 21-23) ---
dc.w COLOR21,COLOUR_TRACTOR
dc.w COLOR22,COLOUR_TYRE
dc.w COLOR23,COLOUR_CAB
; --- The hay cart's (sprites 4-5 share 25-27) ---
dc.w COLOR25,COLOUR_WOODWORK
dc.w COLOR26,COLOUR_TYRE
dc.w COLOR27,COLOUR_HAY
; --- The Land Rover's (sprites 6-7 share 29-31) ---
dc.w COLOR29,COLOUR_ROVER
dc.w COLOR30,COLOUR_TYRE
dc.w COLOR31,COLOUR_ROOF
; --- THE FOLD (from the top of the frame) ---
dc.w COLOR00,COLOUR_FOLD_GRASS
dc.w COLOR01,COLOUR_FENCE ; Pixels here are fence
; --- HEDGEROW (row 40) ---
dc.w $5401,$fffe ; Wait: line $2C+40 = $54
dc.w COLOR00,COLOUR_HEDGE
dc.w COLOR01,COLOUR_TUFT
; --- THE STREAM (row 48) ---
dc.w $5c01,$fffe ; Wait: line $2C+48 = $5C
dc.w COLOR00,COLOUR_WATER
dc.w COLOR01,COLOUR_WOOD ; Pixels here are bridge
; --- THE BANK (row 80) ---
dc.w $7c01,$fffe ; Wait: line $2C+80 = $7C
dc.w COLOR00,COLOUR_BANK
dc.w COLOR01,COLOUR_TUFT
; --- THE LANE (row 96) ---
dc.w $8c01,$fffe ; Wait: line $2C+96 = $8C
dc.w COLOR00,COLOUR_LANE
dc.w COLOR01,COLOUR_DASH ; Pixels here are markings
; --- THE VERGE (row 160) ---
dc.w $cc01,$fffe ; Wait: line $2C+160 = $CC
dc.w COLOR00,COLOUR_VERGE
dc.w COLOR01,COLOUR_TUFT
; --- THE FIELD (row 176, down to row 239) ---
dc.w $dc01,$fffe ; Wait: line $2C+176
dc.w COLOR00,COLOUR_FIELD
dc.w COLOR01,COLOUR_TUFT
; --- THE HUD STRIP (row 240) ---
; Row 240 is beam line $11C — past 255, which the Copper's
; 8-bit comparator can't name directly. The classic trick:
; wait for the very end of line 255, THEN wait for the low
; byte. The first wait carries you across the boundary.
dc.w $ffdf,$fffe ; To the end of line 255
dc.w $1c01,$fffe ; Then line $11C & $FF = $1C
dc.w COLOR00,COLOUR_HUD
dc.w COLOR01,COLOUR_ICON
; --- END OF COPPER LIST ---
dc.w $ffff,$fffe ; Wait for impossible position
;══════════════════════════════════════════════════════════════
; THE SHEEP — sprite 0, two step images
;
; Same sheep, two pictures. Step image 0: front-left and
; back-right feet planted. Step image 1: the other diagonal,
; tail swung the other way. Alternate them as she hops and
; she waddles. The control words are written by updsprite.
;══════════════════════════════════════════════════════════════
section data,data_c
sheep0:
dc.w 0 ; POS — written by updsprite
dc.w 0 ; CTL — written by updsprite
; plane A (fleece) plane B (face/shade/feet)
dc.w %0000000000000000,%0000100000010000 ; ..ears..
dc.w %0000000000000000,%0000011111100000 ; ..head..
dc.w %0000000000000000,%0000001111000000 ; ..face..
dc.w %0000111111110000,%0000000000000000 ; fleece ruff
dc.w %0011111111111100,%0000000000000000 ; shoulders
dc.w %0111111111111110,%1000000000000000 ; < front foot
dc.w %0111111111111110,%1001000000001000 ; < + flecks
dc.w %0111111111111110,%0000000000000000
dc.w %0111111111111110,%0000001001000000 ; shade flecks
dc.w %0111111111111110,%0000000000000001 ; back foot >
dc.w %0111111111111110,%0000100000010001 ; + flecks >
dc.w %0011111111111100,%0000000000000000 ; haunches
dc.w %0011111111111100,%0000000000000000
dc.w %0001111111111000,%0000000000000000
dc.w %0000111111110000,%0000000000000000 ; rump
dc.w %0000000000000000,%0000001100000000 ; tail, left
dc.w 0,0 ; End of sprite
sheep1:
dc.w 0 ; POS — written by updsprite
dc.w 0 ; CTL — written by updsprite
; plane A (fleece) plane B (face/shade/feet)
dc.w %0000000000000000,%0000100000010000 ; ..ears..
dc.w %0000000000000000,%0000011111100000 ; ..head..
dc.w %0000000000000000,%0000001111000000 ; ..face..
dc.w %0000111111110000,%0000000000000000 ; fleece ruff
dc.w %0011111111111100,%0000000000000000 ; shoulders
dc.w %0111111111111110,%0000000000000001 ; front foot >
dc.w %0111111111111110,%0001000000001001 ; + flecks >
dc.w %0111111111111110,%0000000000000000
dc.w %0111111111111110,%0000001001000000 ; shade flecks
dc.w %0111111111111110,%1000000000000000 ; < back foot
dc.w %0111111111111110,%1000100000010000 ; < + flecks
dc.w %0011111111111100,%0000000000000000 ; haunches
dc.w %0011111111111100,%0000000000000000
dc.w %0001111111111000,%0000000000000000
dc.w %0000111111110000,%0000000000000000 ; rump
dc.w %0000000000000000,%0000000011000000 ; tail, right
dc.w 0,0 ; End of sprite
;══════════════════════════════════════════════════════════════
; THE TRACTOR — sprite 2
;
; Big rear wheels on the left, small front wheels and the
; bonnet pointing right — the way it drives. Red bodywork,
; dark tyres, a grey cab roof. Its own palette: sprites 2-3
; share colours 21-23.
;══════════════════════════════════════════════════════════════
tractor:
dc.w 0 ; POS — written by updsprite
dc.w 0 ; CTL — written by updsprite
; plane A (body/cab) plane B (tyres/cab)
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0111110000000000 ; rear wheel
dc.w %0000000000000000,%0111110000011110 ; + front wheel
dc.w %0000000000000000,%0111110000011110
dc.w %0000001111111100,%0111110000000000 ; chassis
dc.w %0111111111111110,%0000111110000000 ; body + cab
dc.w %0111111111111110,%0000111110000000
dc.w %0111111111111110,%0000111110000000
dc.w %0111111111111110,%0000111110000000
dc.w %0000001111111100,%0111110000000000 ; chassis
dc.w %0000000000000000,%0111110000011110
dc.w %0000000000000000,%0111110000011110 ; + front wheel
dc.w %0000000000000000,%0111110000000000 ; rear wheel
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0000000000000000
dc.w 0,0 ; End of sprite
nullspr: dc.w 0,0 ; A sprite that displays nothing
dc.w 0,0
; --- The sheep's state ---
sheepx: dc.w SHEEP_X ; Screen x (0-304)
sheepy: dc.w SHEEP_Y ; Screen y (0-240)
cooldown: dc.w 0 ; Frames until the next hop
curframe: dc.w 0 ; Which step image: 0 or 1
;══════════════════════════════════════════════════════════════
; THE HAY CART — sprite 4
;
; Plods leftward: a wooden bed, a heaped load of hay, and
; wheels at the corners. Wood and hay get their own palette
; (sprites 4-5 share colours 25-27).
;══════════════════════════════════════════════════════════════
cart:
dc.w 0 ; POS — written by updsprite
dc.w 0 ; CTL — written by updsprite
; plane A (wood/hay) plane B (wheels/hay)
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0110000000000110 ; wheels
dc.w %0011111111111100,%0110000000000110
dc.w %0011111111111100,%0110000000000110
dc.w %0011111111111100,%0000111111110000 ; hay rises
dc.w %0011111111111100,%0001111111111000
dc.w %0011111111111100,%0001111111111000
dc.w %0011111111111100,%0001111111111000
dc.w %0011111111111100,%0001111111111000
dc.w %0011111111111100,%0000111111110000 ; hay falls
dc.w %0011111111111100,%0110000000000110
dc.w %0011111111111100,%0110000000000110 ; wheels
dc.w %0000000000000000,%0110000000000110
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0000000000000000
dc.w 0,0 ; End of sprite
;══════════════════════════════════════════════════════════════
; THE LAND ROVER — sprite 6
;
; The farmer's in a hurry. Boxy paintwork, a roof panel set
; back from the bonnet (it drives leftward, so the bonnet is
; the left end), wheels at the corners. Sprites 6-7 share
; colours 29-31.
;══════════════════════════════════════════════════════════════
rover:
dc.w 0 ; POS — written by updsprite
dc.w 0 ; CTL — written by updsprite
; plane A (paint/roof) plane B (wheels/roof)
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0011000000001100 ; wheels
dc.w %0111111111111110,%0011000000001100
dc.w %0111111111111110,%0011000000001100
dc.w %0111111111111110,%0000001111111000 ; roof panel,
dc.w %0111111111111110,%0000001111111000 ; set back
dc.w %0111111111111110,%0000001111111000 ; from the
dc.w %0111111111111110,%0000001111111000 ; bonnet
dc.w %0111111111111110,%0000001111111000
dc.w %0111111111111110,%0000001111111000
dc.w %0111111111111110,%0011000000001100
dc.w %0111111111111110,%0011000000001100 ; wheels
dc.w %0000000000000000,%0011000000001100
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0000000000000000
dc.w 0,0 ; End of sprite
; --- The traffic's state ---
tractx: dc.w -16 ; The tractor enters from the left
cartx: dc.w 300 ; The cart from the right
roverx: dc.w 160 ; The Rover mid-lane, flat out
; --- The squash beat ---
squashtimer: dc.w 0 ; Frames of stillness remaining
; --- The flock ---
lives: dc.w FLOCK_SIZE ; Sheep in hand
gameover: dc.w 0 ; 1 = the field is empty
won: dc.w 0 ; 1 = every pen is full
unpenned: dc.w 5 ; Pens still to fill
;══════════════════════════════════════════════════════════════
; THE BITPLANE (Chip RAM)
;══════════════════════════════════════════════════════════════
plane: ds.b ROW_BYTES*256 ; Plane 1: fence, bridge, dashes, icons
plane2: ds.b ROW_BYTES*256 ; Plane 2: the fold's residents
If It Doesn’t Work
- Residents are brown, or invisible? Plane 2’s pixels read colour 2 —
COLOR02must be set (the list sets it once at the top), and the second pointer pair must be poked or Denise fetches plane 2 from address 0. - The whole screen shifted or doubled?
BPLCON0must say two planes ($2200) andBPL2MODmust be zero — a missing modulo makes plane 2 drift against plane 1. - She walks straight through the fence? The movement clamp must use
FENCE_Y, and the up-branch must test before stepping. The order of the test and the move is the whole gate. - She can enter the same pen twice? The taken flag —
trypenmust test5(a2)before accepting, and set it after. (Five residents in one pen is a charming bug, but it isn’t a win condition.)
Try This
- A homecoming wiggle. When she pens, stamp the glyph, wait
PEN_BEAT, then re-stamp it one pixel lower. A settle. Two lines, and the fold feels alive. - Score the route. Note which lane order you cross in a winning run, then change your departure timing by ten frames and run it again. The traffic is deterministic — every winning route is a repeatable recipe. Write yours down like a speedrunner would.
- The empty-hand draw. What happens if her last life pens the last sheep? Trace the code:
wonandlives— which gate wins? Decide what should happen before you read what does.
What You’ve Learnt
- The second bitplane — four colours per band; plane 1’s pixels keep their meaning because
01is still01. Layers by plane: detail under, residents over. - Goal slots as data —
pentabcarries span, glyph position and state;trypenis one loop over it. The fold can be re-architected without touching logic. - The fence gate — clamp to the boundary, and make crossing it a test, not a step. Doors are conditions.
- Win as a mirrored state —
woncloses the same gates asgameover; the difference is what’s on screen when the world keeps rolling.
What’s Next
Safe arrivals deserve counting. In Unit 10 the HUD strip earns a second job: score — points per crossing and per penning, drawn with a CPU-stamped font. The strip stops being a hand of sheep and becomes a scoreboard.