initial commit

This commit is contained in:
2026-07-15 21:31:51 +02:00
commit 0fb584bcd7
12 changed files with 1605 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
.claude
*.prg
+45
View File
@@ -0,0 +1,45 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "deps",
"type": "shell",
"command": "make deps",
"problemMatcher": []
},
{
"label": "build",
"type": "shell",
"command": "make build",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
},
{
"label": "run",
"type": "shell",
"command": "make run",
"problemMatcher": []
},
{
"label": "clear",
"type": "shell",
"command": "make clear",
"problemMatcher": []
},
{
"label": "rebuild",
"type": "shell",
"command": "make rebuild",
"problemMatcher": []
},
{
"label": "rebuildrun",
"type": "shell",
"command": "make rebuildrun",
"problemMatcher": []
}
]
}
+34
View File
@@ -0,0 +1,34 @@
ASM = acme
EMU = x64sc
SRC = main.asm
SRCS = $(wildcard *.asm)
TARGET = rabbit.prg
UNAME_S := $(shell uname -s)
.PHONY: build run clear rebuild rebuildrun deps
deps:
ifeq ($(UNAME_S),Darwin)
@command -v brew >/dev/null 2>&1 || { echo "Homebrew kell ehhez. Telepitsd: https://brew.sh"; exit 1; }
brew install acme vice
else
@echo "A 'deps' target csak macOS-en (Darwin) automatikus. Detektalt OS: $(UNAME_S)"
@echo "Telepitsd kezzel: acme assembler + VICE emulator (x64sc)."
@exit 1
endif
build: $(TARGET)
$(TARGET): $(SRCS)
$(ASM) -f cbm -o $(TARGET) $(SRC)
run: build
$(EMU) $(TARGET)
clear:
rm -f $(TARGET)
rebuild: clear build
rebuildrun: clear build run
+56
View File
@@ -0,0 +1,56 @@
# Rabbit Scooter — C64
A Commodore 64 port of [rabbitroller](../rabbitroller), the Game & Watch
style LCD mini game, written in 6502 assembly (built with the ACME
assembler, using the same toolchain and layout as [c64-demo](../c64-demo)).
Rabbits hop toward you on a five-lane road. Steer your scooter left and
right to dodge them: a rabbit that reaches your lane is a **miss**, one
that passes in any other lane is a **point**. Three misses end the round.
The spawn rate and the rabbits' speed ramp up with your score, just like
in the original.
## Controls
| Input | Action |
|-------|--------|
| Joystick port 2 left/right | change lane (auto-repeats while held) |
| Fire (or `SPACE`) | start / retry |
## How it works
- PAL frame loop synced by polling the raster beam (no interrupts), one
state update per frame — title, play and game over are `updvec` states.
- The player is hardware sprite 0; up to seven rabbits live on sprites
17. Rabbits follow the road's perspective: a linear y ramp and a t²
horizontal fan-out, both precomputed into tables at assembly time
(`tables.asm`). Far rabbits use small sprite frames, near ones switch
to big frames and finally to double-sized expanded sprites.
- Random lanes and speeds come from an 8-bit LFSR stirred with the
free-running CIA timer.
- Sprite art sits below `$1000` so the VIC never sees the character ROM
shadow instead of it.
## Files
| File | Contents |
|------|----------|
| `main.asm` | BASIC stub, init, frame loop, state dispatch |
| `defs.asm` | registers, constants, zero page, macros |
| `title.asm` | title screen: logo, parked scooters, demo rabbits |
| `play.asm` | gameplay: input, spawning, scoring, HUD |
| `over.asm` | game over box + retry |
| `common.asm` | shared routines: printing, road, input, RNG, rabbit positioning |
| `tables.asm` | assembly-time perspective and speed tables |
| `sprites.asm` | sprite art (scooter, 4+4 rabbit frames) |
## Build & run
```
make deps # macOS: brew install acme vice
make build # -> rabbit.prg
make run # build + launch in VICE (x64sc)
```
The same targets are available as VS Code tasks. A test build that boots
straight into gameplay: `acme -DAUTOPLAY=1 -f cbm -o test.prg main.asm`.
+484
View File
@@ -0,0 +1,484 @@
; -----------------------------------------------------------
; common.asm - routines shared by the states: screen drawing,
; text printing, input, RNG and the rabbit sprite positioning
; -----------------------------------------------------------
; ---- clear screen RAM, fill color RAM with black ----
clear_screen:
ldx #0
cls_loop:
lda #$20 ; space
sta SCREEN,x
sta SCREEN+$100,x
sta SCREEN+$200,x
sta SCREEN+$2e8,x
lda #BLACK
sta COLRAM,x
sta COLRAM+$100,x
sta COLRAM+$200,x
sta COLRAM+$2e8,x
inx
bne cls_loop
rts
; ---- print a text line: txtlo/hi, prow, pcol, pcolor, plen ----
print_text:
ldx prow
lda mul40lo,x
clc
adc pcol
sta scrlo
sta collo
lda mul40hi,x
adc #>SCREEN ; carry still valid from the low-byte add
sta scrhi
clc
adc #$d4 ; COLRAM - SCREEN high byte distance
sta colhi
ldy #0
pt_loop:
lda (txtlo),y
sta (scrlo),y
lda pcolor
sta (collo),y
iny
cpy plen
bne pt_loop
rts
; ---- the road: two diagonals from the horizon apex down to
; the screen corners, plus a tree on the left shoulder ----
draw_road:
ldx #0
dr_loop:
txa
clc
adc #6 ; road runs from char row 6 to row 24
tay
lda mul40lo,y
sta scrlo
lda mul40hi,y
clc
adc #>SCREEN
sta scrhi
stx drtmp
lda #19 ; left edge: one column left per row
sec
sbc drtmp
tay
lda #$4e ; '/' diagonal
sta (scrlo),y
lda #20 ; right edge: one column right per row
clc
adc drtmp
tay
lda #$4d ; '\' diagonal
sta (scrlo),y
inx
cpx #19
bne dr_loop
; tree on the left shoulder
lda #$51 ; ball
sta SCREEN+10*40+3
sta SCREEN+11*40+2
sta SCREEN+11*40+3
sta SCREEN+11*40+4
lda #$5d ; trunk
sta SCREEN+12*40+3
rts
; ---- input ----
; read joystick port 2, return active-high bits in A and joyb
read_joy:
lda #$ff ; deselect every keyboard column first
sta CIA1_PA
lda CIA1_PA
eor #$ff
and #$1f
sta joyb
rts
; fire or space held? returns A != 0 when pressed
start_pressed:
jsr read_joy
and #JOY_FIRE
bne sp_done
lda #$7f ; space: keyboard column 7, row bit 4
sta CIA1_PA
lda CIA1_PB
and #$10
eor #$10
sp_done:
rts
; edge-detected start: A = 1 only on the frame fire/space goes down
start_edge:
jsr start_pressed
beq se_up
lda fheld
bne se_no
lda #1
sta fheld
rts ; A = 1: fresh press
se_up:
lda #0
sta fheld
se_no:
lda #0
rts
; ---- RNG: 8-bit LFSR stirred with the free-running CIA timer
; (the timer counts every cycle, so call timing = entropy) ----
rand8:
lda seed
asl
bcc r8_tap
eor #$1d
r8_tap:
eor CIA1_TA
sta seed
rts
; random lane 0-4
rand5:
jsr rand8
and #7
cmp #LANES
bcs rand5 ; every retry steps the LFSR, so it terminates
rts
; ---- write the per-frame sprite register shadows ----
write_shadows:
lda en_sh
sta SPR_EN
lda msb_sh
sta SPR_MSBX
lda xex_sh
sta SPR_XEX
lda yex_sh
sta SPR_YEX
rts
; ============================================================
; rab_pos - place rabbit slot X on hardware sprite X+1
; reads r_thi/r_lane, picks size stage + hop frame, writes
; the position registers and ORs the shadow bits; keeps X
; ============================================================
rab_pos:
stx rslot
lda r_thi,x
sta rt
lda r_lane,x
sta rlane
; hop offset: 8-entry wave, one hop per 64 t units
lda rt
lsr
lsr
lsr
and #7
tay
lda hoptab,y
sta rhop
; animation frame 0-3
lda rt
lsr
lsr
lsr
lsr
and #3
sta rfrm
lda #0
sta rexp
lda rt
cmp #T_BIG
bcs rp_big
; -- far: small frames --
lda #SPRP_SMALL
clc
adc rfrm
sta rptr
lda #12
sta ranchor
jmp rp_y
rp_big:
; -- near: big frames, stronger hop --
lda #SPRP_BIG
clc
adc rfrm
sta rptr
asl rhop
lda #12
sta ranchor
lda rt
cmp #T_EXP
bcc rp_y
; -- closest: the same frames double-sized --
lda #1
sta rexp
lda #24
sta ranchor
rp_y:
; sprite y: feet ride the perspective line (art bottom row)
ldy rt
lda ytab,y
sec
sbc rhop
sec
sbc #21
ldx rexp
beq rp_ydone
sec
sbc #21 ; expanded sprite is 42 tall
rp_ydone:
sta ry
; x center = ROAD_CX +/- lane offset on the t^2 curve
ldy rt
lda offtab,y
sta roff
lda #ROAD_CX
sta rxlo
lda #0
sta rxhi
ldy rlane
cpy #2
beq rp_xdone ; middle lane: stay on the center line
bcs rp_right
lda roff ; lanes 0-1: left of center
cpy #1
bne rp_lfull
lsr ; lane 1 fans out half as far
rp_lfull:
sta rtmp
lda rxlo
sec
sbc rtmp ; max offset is 107, never underflows
sta rxlo
jmp rp_xdone
rp_right:
lda roff ; lanes 3-4: right of center
cpy #3
bne rp_rfull
lsr
rp_rfull:
sta rtmp
lda rxlo
clc
adc rtmp
sta rxlo
lda rxhi
adc #0
sta rxhi
rp_xdone:
; sprite left edge = center - anchor (16 bit)
lda rxlo
sec
sbc ranchor
sta rxlo
lda rxhi
sbc #0
sta rxhi
; ---- write the hardware registers + shadow bits ----
ldx rslot
ldy sprxreg,x
lda rxlo
sta SPRPOS,y
lda ry
sta SPRPOS+1,y
lda rptr
sta SPRPTR+1,x
lda sprbit,x
ora en_sh
sta en_sh
lda rxhi
beq rp_nomsb
lda sprbit,x
ora msb_sh
sta msb_sh
rp_nomsb:
lda rexp
beq rp_done
lda sprbit,x
ora xex_sh
sta xex_sh
lda sprbit,x
ora yex_sh
sta yex_sh
rp_done:
rts
; ---- draw a text line with big 3x5 block letters ----
; txtlo/hi = text (screen codes), bigrow/bigcol = top-left cell,
; bigcolor = color, biglen = char count; each letter is 3 cells
; wide + 1 gap, 5 cells tall
draw_big:
lda #0
sta bigi
db_char:
ldy bigi
lda (txtlo),y
cmp #$20 ; space: leave the cell empty
beq db_next
tax
lda bigmap,x ; screen code -> glyph index
sta glyphbase
asl ; glyph base = index * 5
asl
clc
adc glyphbase
sta glyphbase
lda bigi ; cell x = bigcol + charindex * 4
asl
asl
clc
adc bigcol
sta charx
lda #0
sta rowidx
db_row:
lda bigrow ; row pointers from the row*40 tables
clc
adc rowidx
tax
lda mul40lo,x
sta scrlo
sta collo
lda mul40hi,x
clc
adc #>SCREEN
sta scrhi
clc
adc #$d4
sta colhi
lda glyphbase
clc
adc rowidx
tax
lda bigglyphs,x
asl ; 3-bit pattern up to bits 7-5
asl
asl
asl
asl
sta curbits
ldy charx
lda #3
sta bitcnt
db_bit:
asl curbits ; leftmost pixel first
bcc db_nobit
lda #$a0 ; inverse space = solid block
sta (scrlo),y
lda bigcolor
sta (collo),y
db_nobit:
iny
dec bitcnt
bne db_bit
inc rowidx
lda rowidx
cmp #5
bne db_row
db_next:
inc bigi
lda bigi
cmp biglen
beq db_done
jmp db_char ; too far for a relative branch
db_done:
rts
; ---- shared tables ----
; row * 40 lookup tables (25 rows)
mul40lo:
!byte $00, $28, $50, $78, $a0, $c8, $f0, $18, $40, $68
!byte $90, $b8, $e0, $08, $30, $58, $80, $a8, $d0, $f8
!byte $20, $48, $70, $98, $c0
mul40hi:
!byte $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
!byte $01, $01, $01, $02, $02, $02, $02, $02, $02, $02
!byte $03, $03, $03, $03, $03
; 3x5 block font for the big title (5 bytes per glyph,
; low 3 bits used, MSB side = left)
bigglyphs:
!byte 0, 0, 0, 0, 0 ; 0 space
!byte 2, 5, 7, 5, 5 ; 1 A
!byte 3, 4, 4, 4, 3 ; 2 C
!byte 6, 5, 5, 5, 6 ; 3 D
!byte 7, 4, 6, 4, 7 ; 4 E
!byte 7, 4, 6, 4, 4 ; 5 F
!byte 7, 2, 2, 2, 7 ; 6 I
!byte 4, 4, 4, 4, 7 ; 7 L
!byte 5, 7, 7, 5, 5 ; 8 M
!byte 6, 5, 5, 5, 5 ; 9 N
!byte 7, 5, 5, 5, 7 ; 10 O
!byte 6, 5, 6, 4, 4 ; 11 P
!byte 6, 5, 6, 5, 5 ; 12 R
!byte 3, 4, 2, 1, 6 ; 13 S
!byte 7, 2, 2, 2, 2 ; 14 T
!byte 5, 5, 2, 2, 2 ; 15 Y
!byte 3, 4, 5, 5, 7 ; 16 G
!byte 6, 5, 6, 5, 6 ; 17 B
bigmap: ; screen code (a=1..z=26) -> glyph index
!byte 0 ; (unused)
!byte 1, 17, 2, 3, 4, 5, 16, 0, 6, 0 ; a-j
!byte 0, 7, 8, 9, 10, 11, 0, 12, 13, 14 ; k-t
!byte 0, 0, 0, 0, 15, 0 ; u-z
; ---- texts (screen codes) ----
txt_score: !scr "score"
txt_miss: !scr "miss"
txt_rabbit: !scr "rabbit"
txt_scooter: !scr "scooter"
txt_press: !scr "press fire to start"
txt_info: !scr "joystick 2 moves fire starts"
txt_over: !scr "game over"
txt_retry: !scr "fire = retry"
txt_blank: !fill 19, $20
; ---- shared variables ----
framectr: !byte 0 ; frame counter, incremented by the main loop
seed: !byte $a7 ; rand8 LFSR state
fheld: !byte 0 ; fire/space edge detection state
joyb: !byte 0 ; joystick bits from read_joy (active high)
en_sh: !byte 0 ; per-frame sprite register shadows
msb_sh: !byte 0
xex_sh: !byte 0
yex_sh: !byte 0
prow: !byte 0 ; print_text: row, column, color, length
pcol: !byte 0
pcolor: !byte 0
plen: !byte 0
bigrow: !byte 0 ; draw_big: row, column, color, length
bigcol: !byte 0
bigcolor: !byte 0
biglen: !byte 0
bigi: !byte 0 ; draw_big internals
glyphbase: !byte 0
charx: !byte 0
rowidx: !byte 0
bitcnt: !byte 0
curbits: !byte 0
drtmp: !byte 0 ; draw_road internals
rslot: !byte 0 ; rab_pos internals
rt: !byte 0
rlane: !byte 0
rhop: !byte 0
rfrm: !byte 0
rexp: !byte 0
rptr: !byte 0
ranchor: !byte 0
roff: !byte 0
rxlo: !byte 0
rxhi: !byte 0
ry: !byte 0
rtmp: !byte 0
rtmp2: !byte 0
+98
View File
@@ -0,0 +1,98 @@
; -----------------------------------------------------------
; defs.asm - hardware registers, constants, zero page, macros
; included first by main.asm
; -----------------------------------------------------------
; ---- hardware registers ----
RASTER = $d012 ; current raster line
BORDER = $d020 ; border color register
BG = $d021 ; background color register
SCREEN = $0400 ; screen RAM (40x25 chars)
COLRAM = $d800 ; color RAM
SPRPTR = $07f8 ; sprite pointers (screen RAM + $3f8)
CIA1_PA = $dc00 ; CIA1 port A: keyboard columns / joystick 2
CIA1_PB = $dc01 ; CIA1 port B: keyboard matrix row read
CIA1_TA = $dc04 ; CIA1 timer A low byte: free-running entropy
; ---- sprite registers ----
SPRPOS = $d000 ; sprite 0 x; +1 = y, +2 = sprite 1 x, ...
SPR_MSBX = $d010 ; 9th bit of each sprite's x coordinate
SPR_EN = $d015 ; sprite enable bits
SPR_YEX = $d017 ; vertical expand
SPR_PRIO = $d01b ; 1 = sprite behind the characters
SPR_MC = $d01c ; multicolor enable
SPR_XEX = $d01d ; horizontal expand
SPR_COL = $d027 ; sprite 0 color, +1 = sprite 1, ...
; ---- colors ----
BLACK = 0
WHITE = 1
LGREY = 15
; ---- joystick port 2 bits (after read_joy, active high) ----
JOY_LEFT = 4
JOY_RIGHT = 8
JOY_FIRE = 16
; ---- game constants ----
LANES = 5
NRABBITS = 7 ; rabbit slots, hardware sprites 1-7
MAX_MISS = 3
ROAD_CX = 184 ; road center in sprite x coordinates
PLAYER_SY = 200 ; scooter sprite top (feet on raster line 221)
T_BIG = 112 ; rabbit distance where the big frames start
T_EXP = 192 ; ... and where the sprite gets double-sized
REP_HOLD = 12 ; frames before a held direction auto-repeats
REP_PERIOD = 6 ; frames between auto-repeat steps
RAS_SYNC = 251 ; raster line polled for the frame sync
; ---- zero page pointers (same slots as the c64-demo) ----
txtlo = $f5 ; text pointer for the print routines (2 bytes)
txthi = $f6
updvec = $f9 ; state update vector (2 bytes)
scrlo = $fb
scrhi = $fc
collo = $fd
colhi = $fe
; ---- helper macros: print a normal / big text line ----
!macro prtext .row, .col, .color, .txt, .len {
lda #<.txt
sta txtlo
lda #>.txt
sta txthi
lda #.row
sta prow
lda #.col
sta pcol
lda #.color
sta pcolor
lda #.len
sta plen
jsr print_text
}
!macro prbig .row, .col, .color, .txt, .len {
lda #<.txt
sta txtlo
lda #>.txt
sta txthi
lda #.row
sta bigrow
lda #.col
sta bigcol
lda #.color
sta bigcolor
lda #.len
sta biglen
jsr draw_big
}
; ---- set the state update vector ----
!macro setupd .addr {
lda #<.addr
sta updvec
lda #>.addr
sta updvec+1
}
+111
View File
@@ -0,0 +1,111 @@
; -----------------------------------------------------------
; main.asm - RABBIT SCOOTER for the C64
; port of the rabbitroller Game & Watch style LCD game:
; rabbits hop toward you on a five-lane road, dodge them
; on your scooter - a rabbit reaching your lane is a miss,
; one passing in another lane is a point; 3 misses = over
;
; Files:
; defs.asm - constants, zero page, macros
; title.asm - title screen: demo rabbits + press fire
; play.asm - the game itself
; over.asm - game over box + retry
; common.asm - shared routines, input, rabbit positioning
; tables.asm - perspective / speed tables (assembly-time)
; sprites.asm - sprite art (scooter, small + big rabbits)
;
; Build: acme -f cbm -o rabbit.prg main.asm
; -----------------------------------------------------------
!to "rabbit.prg", cbm
* = $0801
; ---- BASIC stub: 10 SYS 2064 ----
; so after LOAD"*",8,1 it's enough to type RUN
!byte $0c, $08 ; address of next line
!byte $0a, $00 ; line number: 10
!byte $9e ; SYS token
!byte $20, $32, $30, $36, $34 ; " 2064"
!byte $00 ; end of line
!byte $00, $00 ; end of program
* = $0810 ; = 2064 in decimal
!src "defs.asm"
start:
sei ; no KERNAL IRQ - raster polling must not jitter
; LCD look: black bezel border, pale screen, black artwork
lda #BLACK
sta BORDER
lda #LGREY
sta BG
; sprites: all black, hires, in front of the characters
ldx #7
sprcol:
lda #BLACK
sta SPR_COL,x
dex
bpl sprcol
lda #0
sta SPR_EN
sta SPR_MC
sta SPR_PRIO
sta SPR_MSBX
sta SPR_XEX
sta SPR_YEX
sta framectr
sta fheld
!ifdef AUTOPLAY { ; test builds boot straight into the game
jsr play_init
} else {
jsr title_init
}
; ============================================================
; main loop - one state update per PAL frame, synced to the
; raster beam in the lower border
; ============================================================
main:
jsr wait_frame
inc framectr
jsr rand8 ; stir the RNG once per frame
jsr call_update
jmp main
call_update:
jmp (updvec) ; each update runs exactly one frame, ends with rts
; ---- wait for raster line RAS_SYNC (leave it first, so a
; fast update cannot run twice in the same frame) ----
wait_frame:
lda RASTER
cmp #RAS_SYNC
beq wait_frame
wf_hit:
lda RASTER
cmp #RAS_SYNC
bne wf_hit
rts
; ============================================================
; sprite art first: it must stay below $1000, where the VIC
; would see the character ROM shadow instead of our RAM
; ============================================================
!src "sprites.asm"
!if * > $1000 {
!error "sprite data ran into the char ROM shadow at $1000"
}
; ============================================================
; the states and everything they share
; ============================================================
!src "title.asm"
!src "play.asm"
!src "over.asm"
!src "common.asm"
!src "tables.asm"
+112
View File
@@ -0,0 +1,112 @@
; -----------------------------------------------------------
; over.asm - game over box over the frozen playfield: final
; score + blinking retry prompt, fire restarts the round
; -----------------------------------------------------------
BOXROW = 10 ; box: rows 10-14, columns 12-27
BOXCOL = 12
BOXW = 16
over_init:
lda en_sh ; the scooter may be mid-blink: show it
ora #1
sta SPR_EN
; box interior + frame, drawn over the frozen road
ldx #0
oi_row:
txa
clc
adc #BOXROW
tay
lda mul40lo,y
clc
adc #BOXCOL
sta scrlo
lda mul40hi,y
adc #>SCREEN
sta scrhi
ldy #BOXW-1
oi_col:
jsr box_char
sta (scrlo),y
dey
bpl oi_col
inx
cpx #5
bne oi_row
+prtext BOXROW+1, 15, BLACK, txt_over, 9
+prtext BOXROW+2, 15, BLACK, txt_score, 5
; final score digits next to the label
lda sc_d0
ora #48
sta SCREEN+(BOXROW+2)*40+21
lda sc_d1
ora #48
sta SCREEN+(BOXROW+2)*40+22
lda sc_d2
ora #48
sta SCREEN+(BOXROW+2)*40+23
+setupd over_update
rts
; pick the frame/fill character for box row X, column Y (keeps Y)
box_char:
cpx #0
beq bc_top
cpx #4
beq bc_bottom
cpy #0
beq bc_side
cpy #BOXW-1
beq bc_side
lda #$20 ; interior: erase the road behind the box
rts
bc_side:
lda #$5d ; vertical bar
rts
bc_top:
cpy #0
beq bc_tl
cpy #BOXW-1
beq bc_tr
lda #$40 ; horizontal bar
rts
bc_tl:
lda #$55 ; rounded corners
rts
bc_tr:
lda #$49
rts
bc_bottom:
cpy #0
beq bc_bl
cpy #BOXW-1
beq bc_br
lda #$40
rts
bc_bl:
lda #$4a
rts
bc_br:
lda #$4b
rts
over_update:
; blink the retry prompt
lda framectr
and #32
bne ou_hide
+prtext BOXROW+3, 14, BLACK, txt_retry, 12
jmp ou_start
ou_hide:
+prtext BOXROW+3, 14, BLACK, txt_blank, 12
ou_start:
jsr start_edge
beq ou_done
jmp play_init ; its rts returns to the main loop
ou_done:
rts
+291
View File
@@ -0,0 +1,291 @@
; -----------------------------------------------------------
; play.asm - the game: lane movement with key-repeat, rabbit
; spawning with a score-driven interval, arrivals scored or
; counted as a miss, HUD updates
; -----------------------------------------------------------
; ---- start (or restart) a round ----
play_init:
jsr clear_screen
jsr draw_road
; HUD: score on the left, miss slots on the right
+prtext 0, 1, BLACK, txt_score, 5
+prtext 0, 30, BLACK, txt_miss, 4
lda #0
sta score8
sta sc_d0
sta sc_d1
sta sc_d2
sta miss
sta flash
sta spawnt
sta lcnt
sta rcnt
jsr draw_score
lda #0 ; draw_score leaves a digit code in A
ldx #NRABBITS-1
pi_clr:
sta r_act,x
dex
bpl pi_clr
lda #2 ; start in the middle lane
sta player_lane
; player sprite: scooter, fixed row, rabbits come per frame
lda #SPRP_SCOOT
sta SPRPTR
lda #PLAYER_SY
sta SPRPOS+1
lda #1
sta SPR_EN
lda #0
sta SPR_XEX
sta SPR_YEX
+setupd play_update
rts
; ---- one frame of gameplay ----
play_update:
jsr read_joy
; -- left: move on press, then auto-repeat while held --
lda joyb
and #JOY_LEFT
beq pu_lrel
inc lcnt
lda lcnt
cmp #1
beq pu_lmove
cmp #REP_HOLD+REP_PERIOD
bne pu_lend
lda #REP_HOLD
sta lcnt
pu_lmove:
lda player_lane
beq pu_lend
dec player_lane
jmp pu_lend
pu_lrel:
lda #0
sta lcnt
pu_lend:
; -- right, same dance --
lda joyb
and #JOY_RIGHT
beq pu_rrel
inc rcnt
lda rcnt
cmp #1
beq pu_rmove
cmp #REP_HOLD+REP_PERIOD
bne pu_rend
lda #REP_HOLD
sta rcnt
pu_rmove:
lda player_lane
cmp #LANES-1
beq pu_rend
inc player_lane
jmp pu_rend
pu_rrel:
lda #0
sta rcnt
pu_rend:
; -- shadow init: player sprite bit + its x MSB --
ldx player_lane
lda pxlo,x
sta SPRPOS ; sprite 0 x
lda pxhi,x
sta msb_sh ; lane 4 sits past x=255
lda #0
sta xex_sh
sta yex_sh
lda #1
sta en_sh
; -- a hit blinks the scooter for a dozen frames --
lda flash
beq pu_noflash
dec flash
lda framectr
and #4
bne pu_noflash
lda #0
sta en_sh
pu_noflash:
; -- spawn: interval = 70 - score/2, floor 22 (as the original) --
inc spawnt
lda score8
cmp #96
bcc pu_ivcalc
lda #22
jmp pu_ivgot
pu_ivcalc:
lsr
sta rtmp
lda #70
sec
sbc rtmp
pu_ivgot:
cmp spawnt
bcs pu_nospawn
lda #0
sta spawnt
jsr spawn_rabbit
pu_nospawn:
; -- advance every live rabbit --
ldx #NRABBITS-1
pu_rab:
lda r_act,x
beq pu_rnext
lda r_tlo,x ; t += speed (8.8 fixed point)
clc
adc r_splo,x
sta r_tlo,x
lda r_thi,x
adc r_sphi,x
sta r_thi,x
bcc pu_ralive
; arrived at the player line
lda #0
sta r_act,x
lda r_lane,x
cmp player_lane
beq pu_rhit
jsr add_score ; dodged: one point
jmp pu_rnext
pu_rhit:
jsr add_miss ; ran into you
jmp pu_rnext
pu_ralive:
jsr rab_pos
pu_rnext:
dex
bpl pu_rab
jsr write_shadows
; -- third miss ends the round --
lda miss
cmp #MAX_MISS
bcc pu_done
jmp over_init ; its rts returns to the main loop
pu_done:
rts
; ---- claim a free rabbit slot and roll its lane + speed ----
spawn_rabbit:
ldx #NRABBITS-1
sr_find:
lda r_act,x
beq sr_got
dex
bpl sr_find
rts ; every slot busy: skip this spawn
sr_got:
lda #1
sta r_act,x
jsr rand5
sta r_lane,x
lda #0
sta r_tlo,x
sta r_thi,x
; speed = $0260 base + random byte + score ramp (tops out at 64)
jsr rand8
clc
adc #$60
sta rtmp
lda #2
adc #0
sta rtmp2
lda score8
cmp #64
bcc sr_ramp
lda #64
sr_ramp:
tay
lda rtmp
clc
adc sptab_lo,y
sta r_splo,x
lda rtmp2
adc sptab_hi,y
sta r_sphi,x
rts
; ---- score: bump the decimal digits and the difficulty byte ----
add_score:
lda score8
cmp #$ff
beq as_digits
inc score8
as_digits:
inc sc_d2
lda sc_d2
cmp #10
bne draw_score
lda #0
sta sc_d2
inc sc_d1
lda sc_d1
cmp #10
bne draw_score
lda #0
sta sc_d1
inc sc_d0
lda sc_d0
cmp #10
bne draw_score
lda #0
sta sc_d0
; falls through: repaint the three HUD digits
draw_score:
lda sc_d0
ora #48 ; digit screen codes start at 48
sta SCREEN+7
lda sc_d1
ora #48
sta SCREEN+8
lda sc_d2
ora #48
sta SCREEN+9
rts
; ---- miss: X mark on the HUD + blink the scooter ----
add_miss:
inc miss
ldy miss ; Y: the rabbit loop still needs X
lda #24 ; 'x'
sta SCREEN+34,y ; marks land on columns 35-37
lda #12
sta flash
rts
; ---- gameplay variables ----
player_lane: !byte 2
score8: !byte 0 ; score capped at 255, drives the difficulty
sc_d0: !byte 0 ; score digits: hundreds, tens, ones
sc_d1: !byte 0
sc_d2: !byte 0
miss: !byte 0
flash: !byte 0 ; frames left of the post-hit blink
spawnt: !byte 0 ; frames since the last spawn
lcnt: !byte 0 ; held-direction counters for the auto-repeat
rcnt: !byte 0
; ---- rabbit slots (hardware sprites 1-7) ----
r_act: !fill NRABBITS, 0
r_lane: !fill NRABBITS, 0
r_tlo: !fill NRABBITS, 0 ; distance along the lane, 8.8 fixed point
r_thi: !fill NRABBITS, 0
r_splo: !fill NRABBITS, 0 ; per-rabbit speed, 8.8 fixed point
r_sphi: !fill NRABBITS, 0
+231
View File
@@ -0,0 +1,231 @@
; -----------------------------------------------------------
; sprites.asm - hires sprite art, black silhouettes in the
; spirit of the original PNGs; one !be24 row per sprite line
; (`.` = transparent, `#` = pixel), 64-byte aligned
; -----------------------------------------------------------
!align 63, 0
spr_data:
SPRP_SCOOT = spr_data DIV 64 ; sprite pointer of the scooter
SPRP_SMALL = SPRP_SCOOT + 1 ; 4 far rabbit frames
SPRP_BIG = SPRP_SCOOT + 5 ; 4 near rabbit frames
; ---- rabbit on a scooter, seen from behind ----
spr_scooter:
!be24 %.........##..##.........
!be24 %.........##..##.........
!be24 %.........##..##.........
!be24 %.........##..##.........
!be24 %........########........
!be24 %........########........
!be24 %........########........
!be24 %..........####..........
!be24 %....################....
!be24 %....##..########..##....
!be24 %........########........
!be24 %........########........
!be24 %.......##########.......
!be24 %.......##########.......
!be24 %.......##########.......
!be24 %.....##############.....
!be24 %.....##############.....
!be24 %..........####..........
!be24 %..........####..........
!be24 %..........####..........
!be24 %..........####..........
!byte 0
; ---- far rabbit, four hop frames (art hugs the bottom row) ----
spr_small0:
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %.........#..#...........
!be24 %.........#..#...........
!be24 %........######..........
!be24 %.....##########.........
!be24 %....############........
!be24 %.....##########.........
!be24 %....##...##...##........
!byte 0
spr_small1:
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %.........#..#...........
!be24 %.........#..#...........
!be24 %........######..........
!be24 %.....###########........
!be24 %....#############.......
!be24 %.....###.....###........
!byte 0
spr_small2:
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %..........##............
!be24 %..........##............
!be24 %........######..........
!be24 %.....##########.........
!be24 %....############........
!be24 %....############........
!be24 %......##.....##.........
!byte 0
spr_small3:
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %..........#..#..........
!be24 %..........#..#..........
!be24 %.........######.........
!be24 %......###########.......
!be24 %.....############.......
!be24 %....###.......###.......
!byte 0
; ---- near rabbit, four gallop frames (body shared, legs cycle) ----
spr_big0: ; stride: legs stretched wide
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........##...##.........
!be24 %........##...##.........
!be24 %.......###..###.........
!be24 %......###########.......
!be24 %...###############......
!be24 %..#################.....
!be24 %..##################....
!be24 %...################.....
!be24 %....##############......
!be24 %...####......#####......
!be24 %..####.........####.....
!be24 %..###...........####....
!be24 %.###.............###....
!be24 %.##...............##....
!be24 %##.................##...
!byte 0
spr_big1: ; gathering the legs
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........##...##.........
!be24 %........##...##.........
!be24 %.......###..###.........
!be24 %......###########.......
!be24 %...###############......
!be24 %..#################.....
!be24 %..##################....
!be24 %...################.....
!be24 %....##############......
!be24 %.....####...#####.......
!be24 %.....###.....####.......
!be24 %....###.......###.......
!be24 %....##.........##.......
!be24 %....##.........##.......
!be24 %...###.........###......
!byte 0
spr_big2: ; tucked into a ball
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........##...##.........
!be24 %........##...##.........
!be24 %.......###..###.........
!be24 %......###########.......
!be24 %...###############......
!be24 %..#################.....
!be24 %..##################....
!be24 %...################.....
!be24 %....##############......
!be24 %.....#####....####......
!be24 %......####....###.......
!be24 %......###.....###.......
!be24 %......###.....###.......
!be24 %.....####.....####......
!be24 %....#####.....#####.....
!byte 0
spr_big3: ; reaching forward again
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........................
!be24 %........##...##.........
!be24 %........##...##.........
!be24 %.......###..###.........
!be24 %......###########.......
!be24 %...###############......
!be24 %..#################.....
!be24 %..##################....
!be24 %...################.....
!be24 %....##############......
!be24 %....####......####......
!be24 %...####........###......
!be24 %..####..........###.....
!be24 %..###............###....
!be24 %..##..............##....
!be24 %.###...............##...
!byte 0
+47
View File
@@ -0,0 +1,47 @@
; -----------------------------------------------------------
; tables.asm - perspective and speed tables, generated at
; assembly time; t is the rabbit's distance (0 = horizon,
; 255 = the player's line)
; -----------------------------------------------------------
; feet y along the lane: linear from the horizon to the player
ytab:
!for i, 0, 255 {
!byte 100 + (111*i) DIV 256
}
; horizontal fan-out on a t^2 curve (full offset of the outer
; lanes; the inner lanes use half of it)
offtab:
!for i, 0, 255 {
!byte (107*i*i) DIV 65536
}
; hop wave, subtracted from the feet y (doubled for big rabbits)
hoptab:
!byte 0, 1, 2, 4, 4, 3, 2, 1
; speed ramp: score (capped at 64) -> 16-bit speed bonus
sptab_lo:
!for i, 0, 64 {
!byte <(i*13)
}
sptab_hi:
!for i, 0, 64 {
!byte >(i*13)
}
; scooter sprite x per lane (lane center - 12); lane 4 crosses
; the 8-bit boundary, pxhi is its MSB flag
pxlo:
!byte 66, 119, 172, 225, 22
pxhi:
!byte 0, 0, 0, 0, 1
; rabbit slot -> sprite register offset (sprite = slot + 1)
sprxreg:
!byte 2, 4, 6, 8, 10, 12, 14
; rabbit slot -> sprite enable/MSB/expand bit
sprbit:
!byte 2, 4, 8, 16, 32, 64, 128
+94
View File
@@ -0,0 +1,94 @@
; -----------------------------------------------------------
; title.asm - big RABBIT logo over the road, scooters parked
; on all five lanes, three demo rabbits hopping in, blinking
; "press fire to start"
; -----------------------------------------------------------
title_init:
jsr clear_screen
jsr draw_road
+prbig 1, 8, BLACK, txt_rabbit, 6
+prtext 6, 16, BLACK, txt_scooter, 7
+prtext 17, 5, BLACK, txt_info, 30
; one parked scooter per lane on sprites 0-4
ldx #4
ti_scoot:
txa
asl
tay
lda pxlo,x
sta SPRPOS,y
lda #PLAYER_SY
sta SPRPOS+1,y
lda #SPRP_SCOOT
sta SPRPTR,x
dex
bpl ti_scoot
lda #0
sta SPR_XEX
sta SPR_YEX
; demo rabbits on slots 4-6 (sprites 5-7), staggered starts
ldx #4
ti_demo:
jsr rand5
sta r_lane,x
lda demo_t-4,x
sta r_thi,x
lda #1
sta r_act,x
inx
cpx #NRABBITS
bne ti_demo
+setupd title_update
rts
title_update:
; shadows: five scooters (lane 4 needs the x MSB), no expand
lda #%00011111
sta en_sh
lda #%00010000
sta msb_sh
lda #0
sta xex_sh
sta yex_sh
; demo rabbits: constant lope, respawn on a random lane
ldx #4
tu_rab:
lda r_thi,x
clc
adc #3
sta r_thi,x
bcc tu_pos
lda #0
sta r_thi,x
jsr rand5
sta r_lane,x
tu_pos:
jsr rab_pos
inx
cpx #NRABBITS
bne tu_rab
jsr write_shadows
; blink the start line
lda framectr
and #32
bne tu_hide
+prtext 15, 10, BLACK, txt_press, 19
jmp tu_start
tu_hide:
+prtext 15, 10, BLACK, txt_blank, 19
tu_start:
jsr start_edge
beq tu_done
jmp play_init ; its rts returns to the main loop
tu_done:
rts
demo_t: !byte 0, 85, 170