initial commit

This commit is contained in:
2026-07-18 18:34:28 +02:00
commit e9c69864f1
18 changed files with 2472 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
*.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": []
}
]
}
+35
View File
@@ -0,0 +1,35 @@
steps:
- name: version
image: alpine
commands:
- 'apk add --no-cache make'
- 'make ci-version'
- name: build
image: debian:stable-slim
commands:
- 'apt-get update && apt-get install -y --no-install-recommends make acme'
- 'make ci-build'
- name: artifact
image: alpine
environment:
DROPAREA_HOST: vps.teletype.hu
DROPAREA_PORT: 2223
DROPAREA_TARGET_PATH: /home/drop
DROPAREA_USER: drop
DROPAREA_SSH_PASSWORD:
from_secret: droparea_ssh_password
commands:
- 'apk add --no-cache make openssh-client sshpass'
- 'make ci-artifact'
- name: update
image: alpine
environment:
UPDATE_SERVER: https://games.vps.teletype.hu
UPDATE_SECRET:
from_secret: update_secret_key
commands:
- 'apk add --no-cache make curl'
- 'make ci-update'
+91
View File
@@ -0,0 +1,91 @@
# -----------------------------------------
# Makefile C64 project builder (ACME + VICE)
# -----------------------------------------
PROJECT = labyrinth
ASM = acme
EMU = x64sc
SRC = main.asm
SRCS = $(wildcard *.asm)
TARGET ?= $(PROJECT).prg
METADATA = metadata.json
UNAME_S := $(shell uname -s)
# CI/CD variables
VERSION_FILE = .version
DROPAREA_HOST ?= vps.teletype.hu
DROPAREA_PORT ?= 2223
DROPAREA_TARGET_PATH ?= /home/drop
DROPAREA_USER ?= drop
UPDATE_SERVER ?= https://games.vps.teletype.hu
all: build
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) $(PROJECT)-*.prg $(PROJECT)-*.metadata.json $(VERSION_FILE)
rebuild: clear build
rebuildrun: clear build run
# -----------------------------------------
# CI/CD Pipeline targets
# -----------------------------------------
ci-version:
@VERSION=$$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' $(METADATA) | head -n 1); \
if [ -z "$$VERSION" ]; then \
echo "ERROR: no \"version\" field in $(METADATA)!"; \
exit 1; \
fi; \
BRANCH=$${CI_COMMIT_BRANCH:-$${WOODPECKER_BRANCH}}; \
BRANCH=$$(echo "$$BRANCH" | tr '/' '-'); \
if [ "$$BRANCH" != "main" ] && [ "$$BRANCH" != "master" ] && [ -n "$$BRANCH" ]; then \
VERSION=dev-$$VERSION-$$BRANCH; \
fi; \
echo "VERSION is: $$VERSION"; \
echo $$VERSION > $(VERSION_FILE)
ci-build: build
@VERSION=$$(cat $(VERSION_FILE)); \
echo "==> Creating versioned files for $$VERSION"; \
cp $(TARGET) $(PROJECT)-$$VERSION.prg; \
cp $(METADATA) $(PROJECT)-$$VERSION.metadata.json; \
ls -lh $(PROJECT)-$$VERSION.*
ci-artifact:
@VERSION=$$(cat $(VERSION_FILE)); \
echo "==> Uploading artifacts for version $$VERSION"; \
SCP_TARGET="$(DROPAREA_USER)@$(DROPAREA_HOST):$(DROPAREA_TARGET_PATH)/"; \
sshpass -p "$(DROPAREA_SSH_PASSWORD)" scp -o StrictHostKeyChecking=no -P $(DROPAREA_PORT) \
$(PROJECT)-$$VERSION.prg \
$(PROJECT)-$$VERSION.metadata.json \
$$SCP_TARGET
ci-update:
@VERSION=$$(cat $(VERSION_FILE)); \
echo "==> Triggering update for version $$VERSION"; \
curl "$(UPDATE_SERVER)/update?secret=$(UPDATE_SECRET)&name=$(PROJECT)&platform=c64&version=$$VERSION"
.PHONY: all deps build run clear rebuild rebuildrun ci-version ci-build ci-artifact ci-update
+47
View File
@@ -0,0 +1,47 @@
# labyrinth
Two-player maze duel for the Commodore 64 (ACME + VICE), in the same
spirit as [rabbitc64](../rabbitc64).
A fresh maze is carved every round on a 39x23 lattice where the walls
are exactly as wide as the corridors. **Player 1** (joystick 2, or
`w a s d`) runs the corridors toward the green diamond exit while
dodging the roaming colored enemies. **Player 2** (joystick 1, or
`i j k l`) rides the corners of the walls with a blinking cursor -
fire (or `return`) spins the four wall pieces around the cursor by
90 degrees, reshaping the maze in real time. A spin is denied while
anyone is standing in one of the four slots.
Screens: TTG intro (ported from rabbitc64) -> main menu
(play / rules / controls / credits) -> game -> win (back to the menu)
or game over (fire retries with a fresh maze, `m` backs out to the
menu).
## Build & run
```
make deps # macOS: brew install acme vice
make build # -> labyrinth.prg
make run # build + launch in x64sc
```
Test builds can boot straight into one state:
```
acme -DAUTOPLAY=1 -f cbm -o labyrinth.prg main.asm
# also: AUTORULES, AUTOCONTROLS, AUTOCREDITS, AUTOWIN, AUTOLOSE
```
## Files
- `main.asm` - entry point, BASIC stub, per-frame state machine
- `defs.asm` - registers, constants, zero page, macros
- `intro.asm` - TTG boot logo with the gold color wash
- `menu.asm` - main menu card
- `rules.asm` - how to play
- `controls.asm` - joystick + keyboard mappings
- `credits.asm` - the crew
- `play.asm` - maze generation (recursive backtracker) + the game
- `win.asm` / `over.asm` - end screens
- `common.asm` - shared routines: printing, big block font, input, RNG
- `sound.asm` - single-voice SID SFX driver
+393
View File
@@ -0,0 +1,393 @@
; -----------------------------------------------------------
; common.asm - routines shared by the states: screen drawing,
; text printing, big block font, input and the RNG
; (ported from rabbitc64, trimmed to what this game needs)
; -----------------------------------------------------------
; ---- 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
; ---- set scrlo/hi + collo/hi to the start of char row X ----
row_ptr:
lda mul40lo,x
sta scrlo
sta collo
lda mul40hi,x
clc
adc #>SCREEN
sta scrhi
clc
adc #$d4 ; COLRAM - SCREEN high byte distance
sta colhi
rts
; ---- input ----
; joystick 1 shares the CIA port B lines the keyboard rows use,
; so before any key scan we note which rows the stick itself is
; grounding (jmask) and treat those rows as released - a held
; joystick 1 direction must not read as a phantom keypress
; read joystick port 2 merged with w/a/s/d + space (the runner),
; active-high bits in A and joyb
read_joy:
lda #$ff ; deselect every keyboard column first
sta CIA1_PA
lda CIA1_PB
eor #$ff
and #$1f
sta jmask ; rows grounded by joystick 1 right now
lda CIA1_PA
eor #$ff
and #$1f
sta joyb ; joystick 2 bits
lda #$fd ; keyboard column 1: w / a / s
sta CIA1_PA
lda CIA1_PB
ora jmask
and #$02 ; 'w' = up: row bit 1
bne rj_now
lda joyb
ora #JOY_UP
sta joyb
rj_now:
lda CIA1_PB
ora jmask
and #$04 ; 'a' = left: row bit 2
bne rj_noa
lda joyb
ora #JOY_LEFT
sta joyb
rj_noa:
lda CIA1_PB
ora jmask
and #$20 ; 's' = down: row bit 5
bne rj_nos
lda joyb
ora #JOY_DOWN
sta joyb
rj_nos:
lda #$fb ; 'd' = right: keyboard column 2, row bit 2
sta CIA1_PA
lda CIA1_PB
ora jmask
and #$04
bne rj_nod
lda joyb
ora #JOY_RIGHT
sta joyb
rj_nod:
lda #$7f ; space = fire: keyboard column 7, row bit 4
sta CIA1_PA
lda CIA1_PB
ora jmask
and #$10
bne rj_nosp
lda joyb
ora #JOY_FIRE
sta joyb
rj_nosp:
lda #$ff
sta CIA1_PA
lda joyb
rts
; read joystick port 1 merged with i/j/k/l + return (the wall
; spinner), active-high bits in A and joyb2
read_joy2:
lda #$ff ; no column selected: port B = joystick 1
sta CIA1_PA
lda CIA1_PB
eor #$ff
and #$1f
sta joyb2 ; joystick 1 bits
sta jmask ; ... which also ground the key rows below
lda #$ef ; keyboard column 4: i / j / k
sta CIA1_PA
lda CIA1_PB
ora jmask
and #$02 ; 'i' = up: row bit 1
bne rk_noi
lda joyb2
ora #JOY_UP
sta joyb2
rk_noi:
lda CIA1_PB
ora jmask
and #$04 ; 'j' = left: row bit 2
bne rk_noj
lda joyb2
ora #JOY_LEFT
sta joyb2
rk_noj:
lda CIA1_PB
ora jmask
and #$20 ; 'k' = down: row bit 5
bne rk_nok
lda joyb2
ora #JOY_DOWN
sta joyb2
rk_nok:
lda #$df ; 'l' = right: keyboard column 5, row bit 2
sta CIA1_PA
lda CIA1_PB
ora jmask
and #$04
bne rk_nol
lda joyb2
ora #JOY_RIGHT
sta joyb2
rk_nol:
lda #$fe ; return = fire: keyboard column 0, row bit 1
sta CIA1_PA
lda CIA1_PB
ora jmask
and #$02
bne rk_noret
lda joyb2
ora #JOY_FIRE
sta joyb2
rk_noret:
lda #$ff
sta CIA1_PA
lda joyb2
rts
; fire held on either side? (stick fire, space or return)
; returns A != 0 when pressed
start_pressed:
jsr read_joy
and #JOY_FIRE
bne sp_done
jsr read_joy2
and #JOY_FIRE
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
; ---- 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 titles (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
!byte 5, 5, 5, 5, 2 ; 18 V
!byte 5, 5, 7, 5, 5 ; 19 H
!byte 5, 5, 5, 5, 7 ; 20 U
!byte 5, 5, 7, 7, 5 ; 21 W
bigmap: ; screen code (a=1..z=26) -> glyph index
!byte 0 ; (unused)
!byte 1, 17, 2, 3, 4, 5, 16, 19, 6, 0 ; a-j
!byte 0, 7, 8, 9, 10, 11, 0, 12, 13, 14 ; k-t
!byte 20, 18, 21, 0, 15, 0 ; u-z
; ---- shared texts (screen codes) ----
txt_title: !scr "labyrinth"
txt_ttgname: !scr "teletype games"
txt_presents:!scr "presents"
txt_return: !scr "fire = return"
txt_blank: !fill 16, $20
washtbl: ; gold glow ramp (16 entries, cyclic)
!byte $09, $02, $08, $0a, $07, $07, $01, $01
!byte $01, $01, $07, $07, $0a, $08, $02, $09
; ---- shared variables ----
framectr: !byte 0 ; frame counter, incremented by the main loop
washphase: !byte 0 ; gold wash offset on the big letters
seed: !byte $a7 ; rand8 LFSR state
fheld: !byte 0 ; fire/space edge detection state
joyb: !byte 0 ; player 1 input bits from read_joy
joyb2: !byte 0 ; player 2 input bits from read_joy2
jmask: !byte 0 ; key rows grounded by joystick 1 this scan
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
rtmp: !byte 0 ; scratch
rtmp2: !byte 0
+52
View File
@@ -0,0 +1,52 @@
; -----------------------------------------------------------
; controls.asm - who steers what: joystick and keyboard
; mappings for both players, one static card; fire returns
; -----------------------------------------------------------
controls_init:
jsr clear_screen
+prbig 1, 4, WHITE, txt_controls, 8
+prtext 8, 9, LGREEN, txt_c1, txt_c1e-txt_c1
+prtext 10, 6, WHITE, txt_c2, txt_c2e-txt_c2
+prtext 13, 6, LBLUE, txt_c3, txt_c3e-txt_c3
+prtext 15, 6, WHITE, txt_c4, txt_c4e-txt_c4
+prtext 17, 10, WHITE, txt_c5, txt_c5e-txt_c5
+prtext 20, 5, LGREY, txt_c6, txt_c6e-txt_c6
+setupd controls_update
rts
controls_update:
; blink the return prompt
lda framectr
and #32
bne co_hide
+prtext 22, 13, YELLOW, txt_return, 13
jmp co_input
co_hide:
+prtext 22, 13, YELLOW, txt_blank, 13
co_input:
jsr start_edge
beq co_stay
jmp menu_init
co_stay:
rts
; ---- controls texts ----
txt_controls: !scr "controls"
txt_c1: !scr "player 1 - the runner"
txt_c1e:
txt_c2: !scr "move: joystick 2 or w a s d"
txt_c2e:
txt_c3: !scr "player 2 - the wall spinner"
txt_c3e:
txt_c4: !scr "move: joystick 1 or i j k l"
txt_c4e:
txt_c5: !scr "spin: fire or return"
txt_c5e:
txt_c6: !scr "menus: fire, space or return"
txt_c6e:
+164
View File
@@ -0,0 +1,164 @@
; -----------------------------------------------------------
; credits.asm - the same crew as rabbitc64, but a new look:
; rainbow bars scrolling along the top and bottom edges in
; opposite directions, a rainbow sweep across the big
; CREDITS, each crew line in its own fixed color and the
; ai warning pulsing grey-white (no scroller, no parade)
; -----------------------------------------------------------
credits_init:
jsr clear_screen
; rainbow bars: solid blocks across rows 0 and 24
ldx #39
ci_bars:
lda #$a0
sta SCREEN,x
sta SCREEN+24*40,x
dex
bpl ci_bars
+prbig 2, 6, WHITE, txt_credits, 7
+prtext 9, 13, WHITE, txt_ttgname, 14
+prtext 12, 8, YELLOW, txt_crew0, txt_crew0e-txt_crew0
+prtext 13, 10, LGREEN, txt_crew1, txt_crew1e-txt_crew1
+prtext 14, 9, CYAN, txt_crew2, txt_crew2e-txt_crew2
+prtext 15, 10, LRED, txt_crew3, txt_crew3e-txt_crew3
+prtext 17, 9, LGREY, txt_ai1, txt_ai1e-txt_ai1
+prtext 18, 12, LGREY, txt_ai2, txt_ai2e-txt_ai2
lda #0
sta barphase
+setupd credits_update
rts
credits_update:
; the edge bars crawl in opposite directions
lda framectr
and #3
bne cu_barsdone
inc barphase
cu_barsdone:
ldx #39
cu_bars:
txa
clc
adc barphase
and #7
tay
lda raintbl,y
sta COLRAM,x ; top bar: colors drift left
lda barphase
stx rtmp
sec
sbc rtmp
and #7
tay
lda raintbl,y
sta COLRAM+24*40,x ; bottom bar: colors drift right
dex
bpl cu_bars
; rainbow sweep across the big CREDITS (rows 2-6)
lda #0
sta rtmp2
cu_sweep:
lda #2
clc
adc rtmp2
tax
jsr row_ptr
lda framectr
lsr
lsr
clc
adc rtmp2
and #7
tax
lda raintbl,x
sta rtmp
ldy #32 ; the letters span columns 6-32
cu_scol:
lda (scrlo),y
cmp #$a0 ; only touch the letter blocks
bne cu_sskip
lda rtmp
sta (collo),y
cu_sskip:
dey
cpy #5
bne cu_scol
inc rtmp2
lda rtmp2
cmp #5
bne cu_sweep
; the ai warning pulses grey-white
lda framectr
lsr
lsr
lsr
and #7
tax
lda pulsetbl,x
sta rtmp
ldx #17
jsr row_ptr
ldy #30
cu_p1:
lda rtmp
sta (collo),y
dey
cpy #8
bne cu_p1
ldx #18
jsr row_ptr
ldy #27
cu_p2:
lda rtmp
sta (collo),y
dey
cpy #11
bne cu_p2
; blink the return prompt
lda framectr
and #32
bne cu_hide
+prtext 21, 13, YELLOW, txt_return, 13
jmp cu_input
cu_hide:
+prtext 21, 13, YELLOW, txt_blank, 13
cu_input:
jsr start_edge
beq cu_stay
jmp menu_init ; back to the menu
cu_stay:
rts
; ---- tables ----
pulsetbl: ; grey-white pulse (8 entries, cyclic)
!byte $0b, $0c, $0f, $01, $01, $0f, $0c, $0b
raintbl: ; rainbow ramp
!byte $02, $08, $07, $0d, $03, $0e, $04, $0a
; ---- the crew (content ported from rabbitc64) ----
txt_crew0: !scr "mr.zero - tasnadi zsolt"
txt_crew0e:
txt_crew1: !scr "mr.one - tari balazs"
txt_crew1e:
txt_crew2: !scr "mr.two - timar zoltan"
txt_crew2e:
txt_crew3: !scr "mr.three - mezo bela"
txt_crew3e:
txt_ai1: !scr "warning: this game was"
txt_ai1e:
txt_ai2: !scr "written by an ai"
txt_ai2e:
; ---- credits variables ----
barphase: !byte 0 ; edge bar scroll offset
+137
View File
@@ -0,0 +1,137 @@
; -----------------------------------------------------------
; 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
SPR_EN = $d015 ; sprite enable bits (parked at 0)
CIA1_PA = $dc00 ; CIA1 port A: keyboard columns / joystick 2
CIA1_PB = $dc01 ; CIA1 port B: keyboard rows / joystick 1
CIA1_TA = $dc04 ; CIA1 timer A low byte: free-running entropy
; ---- SID registers (single-voice SFX driver) ----
SID_V1FL = $d400 ; voice 1 frequency low
SID_V1FH = $d401 ; voice 1 frequency high
SID_V1PL = $d402 ; voice 1 pulse width low
SID_V1PH = $d403 ; voice 1 pulse width high
SID_V1CR = $d404 ; voice 1 control (waveform + gate)
SID_V1AD = $d405 ; voice 1 attack/decay
SID_V1SR = $d406 ; voice 1 sustain/release
SID_VOL = $d418 ; filter mode + master volume
; ---- SFX IDs ----
SFX_CURSOR = 0 ; corner cursor step: click
SFX_SELECT = 1 ; menu fire: rising tone
SFX_ROTATE = 2 ; wall spin: noise whoosh
SFX_DENY = 3 ; blocked spin: low buzz
SFX_WIN = 4 ; escape: rising jingle
SFX_LOSE = 5 ; caught: descending sweep
NSFX = 6
; ---- colors ----
BLACK = 0
WHITE = 1
RED = 2
CYAN = 3
PURPLE = 4
GREEN = 5
BLUE = 6
YELLOW = 7
ORANGE = 8
BROWN = 9
LRED = 10
DGREY = 11
MGREY = 12
LGREEN = 13
LBLUE = 14
LGREY = 15
; ---- joystick bits (after read_joy / read_joy2, active high) ----
JOY_UP = 1
JOY_DOWN = 2
JOY_LEFT = 4
JOY_RIGHT = 8
JOY_FIRE = 16
; ---- the maze ----
; 19x11 cells on a 39x23 char lattice (screen rows 1-23):
; cell = odd column, even row (walkable)
; post = even column, odd row (pivot, always solid)
; slot = the rest: wall pieces that spin around the posts
; wall thickness therefore equals corridor width: one cell
MAZE_CW = 19 ; cells across
MAZE_CH = 11 ; cells down
NCELLS = MAZE_CW * MAZE_CH
NENEMIES = 4
CH_WALL = $a0 ; inverse space: solid wall block
CH_FLOOR = $20 ; space: corridor
CH_EXIT = $5a ; diamond: the goal
CH_PLAYER = $51 ; filled ball: the runner
CH_ENEMY = $57 ; open circle: an enemy
COL_WALL = MGREY ; spinnable wall pieces
COL_POST = DGREY ; fixed pivot posts
COL_EXIT = LGREEN ; the exit diamond
; ---- game timing ----
REP_HOLD = 10 ; runner: frames before a held dir repeats
REP_PERIOD = 5 ; runner: frames between repeat steps
CREP_HOLD = 12 ; cursor: same, a touch slower
CREP_PERIOD = 7
RAS_SYNC = 251 ; raster line polled for the frame sync
; ---- zero page pointers (same slots as rabbitc64) ----
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
}
+146
View File
@@ -0,0 +1,146 @@
; -----------------------------------------------------------
; intro.asm - boot screen ported from rabbitc64: giant TTG
; letters built from 8x8 bitmaps with a gold color wash
; running down them, TELETYPE GAMES / PRESENTS below;
; advances to the menu after a few seconds or on fire
; -----------------------------------------------------------
INTRO_LROW = 3 ; top cell row of the giant letters
INTRO_TIME = 250 ; frames before it advances on its own
intro_init:
jsr clear_screen
; T T G at columns 6 / 16 / 26
lda #<glyph_t
sta txtlo
lda #>glyph_t
sta txthi
lda #6
sta g8col
jsr draw_glyph8
lda #16
sta g8col
jsr draw_glyph8
lda #<glyph_g
sta txtlo
lda #>glyph_g
sta txthi
lda #26
sta g8col
jsr draw_glyph8
+prtext 13, 13, WHITE, txt_ttgname, 14
+prtext 15, 16, LGREY, txt_presents, 8
lda #INTRO_TIME
sta introt
lda #0
sta washphase
+setupd intro_update
rts
intro_update:
; gold wash: recolor the letter rows from the cyclic ramp
inc washphase
lda #0
sta g8row
iw_row:
lda #INTRO_LROW
clc
adc g8row
tax
jsr row_ptr
lda washphase
lsr ; half speed keeps the shimmer gentle
clc
adc g8row
and #15
tax
lda washtbl,x
sta rtmp
ldy #33 ; the letters span columns 6-33
iw_col:
lda (scrlo),y
cmp #$a0 ; only touch the letter blocks
bne iw_skip
lda rtmp
sta (collo),y
iw_skip:
dey
cpy #5
bne iw_col
inc g8row
lda g8row
cmp #8
bne iw_row
; fire skips, otherwise wait out the timer
jsr start_edge
beq iw_tick
jmp menu_init
iw_tick:
dec introt
bne iw_done
jmp menu_init
iw_done:
rts
; ---- draw an 8x8-cell giant letter: txtlo/hi = 8-byte
; bitmap, g8col = left cell column ----
draw_glyph8:
lda #0
sta g8row
g8_row:
lda #INTRO_LROW
clc
adc g8row
tax
jsr row_ptr
ldy g8row
lda (txtlo),y
sta curbits
ldy g8col
lda #8
sta bitcnt
g8_bit:
asl curbits ; leftmost pixel first
bcc g8_next
lda #$a0 ; inverse space = solid block
sta (scrlo),y
lda #YELLOW
sta (collo),y
g8_next:
iny
dec bitcnt
bne g8_bit
inc g8row
lda g8row
cmp #8
bne g8_row
rts
; ---- 8x8 letter bitmaps ----
glyph_t:
!byte %11111111
!byte %11111111
!byte %00111100
!byte %00111100
!byte %00111100
!byte %00111100
!byte %00111100
!byte %00111100
glyph_g:
!byte %00111100
!byte %01111110
!byte %11000011
!byte %11000000
!byte %11001111
!byte %11000011
!byte %01111110
!byte %00111100
; ---- intro variables ----
introt: !byte 0 ; frames left before auto-advancing
g8row: !byte 0 ; glyph renderer / wash internals
g8col: !byte 0
+116
View File
@@ -0,0 +1,116 @@
; -----------------------------------------------------------
; main.asm - LABYRINTH for the C64
; two-player maze duel: player 1 runs the corridors toward
; the green diamond exit while dodging the roaming colored
; enemies; player 2 rides the wall corners with a cursor
; and fire spins the four wall pieces around it by 90
; degrees, reshaping the maze under the runner's feet
;
; Files:
; defs.asm - constants, zero page, macros
; intro.asm - TTG boot logo (ported from rabbitc64)
; menu.asm - main menu: play / rules / credits
; rules.asm - how to play
; controls.asm- joystick + keyboard mappings
; credits.asm - the crew (content from rabbitc64, new look)
; play.asm - maze generation + the game itself
; win.asm - escape screen, returns to the menu
; over.asm - game over screen with retry
; common.asm - shared routines, input, RNG, texts
; sound.asm - single-voice SFX driver
;
; Build: acme -f cbm -o labyrinth.prg main.asm
; -----------------------------------------------------------
* = $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
lda #BLACK
sta BORDER
sta BG
lda #0
sta SPR_EN ; character-only game: sprites stay off
sta framectr
sta fheld
jsr sfx_init
!ifdef AUTOPLAY { ; test builds boot straight into one state
jsr play_init
} else {
!ifdef AUTORULES {
jsr rules_init
} else {
!ifdef AUTOCONTROLS {
jsr controls_init
} else {
!ifdef AUTOCREDITS {
jsr credits_init
} else {
!ifdef AUTOWIN {
jsr win_init
} else {
!ifdef AUTOLOSE {
jsr lose_init
} else {
jsr intro_init ; TTG logo, then the menu
}
}
}
}
}
}
; ============================================================
; 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 sfx_update ; advance the sound effect
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
; ============================================================
; the states and everything they share
; ============================================================
!src "intro.asm"
!src "menu.asm"
!src "rules.asm"
!src "controls.asm"
!src "credits.asm"
!src "play.asm"
!src "win.asm"
!src "over.asm"
!src "common.asm"
!src "sound.asm"
+194
View File
@@ -0,0 +1,194 @@
; -----------------------------------------------------------
; menu.asm - full-screen menu card: big LABYRINTH title with
; the gold wash, and a four-item menu: play / rules /
; controls / credits (joystick or w/s up/down + fire, space
; or return)
; -----------------------------------------------------------
MENU_ITEMS = 4
menu_init:
jsr clear_screen
+prbig 2, 2, YELLOW, txt_title, 9
+prtext 9, 13, LGREY, txt_ttgname, 14
lda #0
sta menusel
sta washphase
+setupd menu_update
rts
menu_update:
; gold wash rolling down the big title (rows 2-6)
inc washphase
lda #0
sta g8row
mw_row:
lda #2
clc
adc g8row
tax
jsr row_ptr
lda washphase
lsr
clc
adc g8row
and #15
tax
lda washtbl,x
sta rtmp
ldy #37 ; the title spans columns 2-36
mw_col:
lda (scrlo),y
cmp #$a0 ; only touch the letter blocks
bne mw_skip
lda rtmp
sta (collo),y
mw_skip:
dey
cpy #1
bne mw_col
inc g8row
lda g8row
cmp #5
bne mw_row
; joystick up/down steps the cursor (edge detected)
jsr read_joy
lda joyb
and #3
beq mu_udrel
lda udheld
bne mu_udend
lda #1
sta udheld
lda joyb
and #1 ; up
beq mu_down
lda menusel
beq mu_udend
dec menusel
lda #SFX_CURSOR
jsr sfx_play
jmp mu_udend
mu_down:
lda menusel
cmp #MENU_ITEMS-1
beq mu_udend
inc menusel
lda #SFX_CURSOR
jsr sfx_play
jmp mu_udend
mu_udrel:
lda #0
sta udheld
mu_udend:
; footer: both control lines always visible
+prtext 21, 8, LGREY, txt_info, txt_infoe-txt_info
+prtext 22, 5, LGREY, txt_info2, txt_info2e-txt_info2
; the menu items: the picked line is yellow
ldx #0
mm_items:
txa
pha
jsr draw_mitem
pla
tax
inx
cpx #MENU_ITEMS
bne mm_items
; blinking '>' cursor in front of the picked line
lda #$20
sta SCREEN+12*40+15
sta SCREEN+14*40+15
sta SCREEN+16*40+15
sta SCREEN+18*40+15
lda framectr
and #16
bne mu_start
ldx menusel
lda mcur_lo,x
sta scrlo
lda mcur_hi,x
sta scrhi
ldy #0
lda #62 ; '>'
sta (scrlo),y
lda scrhi
clc
adc #$d4 ; same cell in color RAM
sta scrhi
lda #YELLOW
sta (scrlo),y
mu_start:
; fire or space opens the picked line
jsr start_edge
beq mu_done
lda #SFX_SELECT
jsr sfx_play
lda menusel
beq mg_play
cmp #1
beq mg_rules
cmp #2
beq mg_controls
jmp credits_init ; each init's rts returns to the main loop
mg_play:
jmp play_init
mg_rules:
jmp rules_init
mg_controls:
jmp controls_init
mu_done:
rts
; ---- draw menu item X, yellow when it is the picked one ----
draw_mitem:
lda mitem_lo,x
sta txtlo
lda mitem_hi,x
sta txthi
lda mitem_row,x
sta prow
lda #17
sta pcol
lda mitem_len,x
sta plen
lda #WHITE
cpx menusel
bne dm_store
lda #YELLOW
dm_store:
sta pcolor
jmp print_text
; ---- menu tables ----
mitem_lo: !byte <txt_play, <txt_rules, <txt_controls, <txt_credits
mitem_hi: !byte >txt_play, >txt_rules, >txt_controls, >txt_credits
mitem_len: !byte 4, 5, 8, 7
mitem_row: !byte 12, 14, 16, 18
mcur_lo: ; cursor cells: column 15 of those rows
!byte <(SCREEN+12*40+15), <(SCREEN+14*40+15)
!byte <(SCREEN+16*40+15), <(SCREEN+18*40+15)
mcur_hi:
!byte >(SCREEN+12*40+15), >(SCREEN+14*40+15)
!byte >(SCREEN+16*40+15), >(SCREEN+18*40+15)
; ---- menu texts ----
txt_play: !scr "play"
txt_rules: !scr "rules"
txt_credits: !scr "credits"
txt_info: !scr "p1: joy2 / wasd - runner"
txt_infoe:
txt_info2: !scr "p2: joy1 / ijkl - wall spinner"
txt_info2e:
; ---- menu variables ----
menusel: !byte 0 ; picked line: 0-2
udheld: !byte 0 ; joystick up/down edge detection state
+9
View File
@@ -0,0 +1,9 @@
{
"name": "labyrinth",
"title": "Labyrinth",
"author": "Teletype Games",
"desc": "Two-player maze duel: one player runs for the exit while the other spins the walls of the labyrinth around their corners. Dodge the enemies, find the diamond.",
"site": "https://git.teletype.hu/games/labyrinth",
"license": "MIT",
"version": "1.0.0"
}
+56
View File
@@ -0,0 +1,56 @@
; -----------------------------------------------------------
; over.asm - game over screen: big red GAME OVER, fire
; restarts with a fresh maze, the m key backs out to the menu
; -----------------------------------------------------------
lose_init:
jsr clear_screen
+prbig 3, 2, RED, txt_gameover, 9
+prtext 11, 10, WHITE, txt_caught, txt_caughte-txt_caught
+prtext 21, 13, LGREY, txt_mmenu, txt_mmenue-txt_mmenu
+setupd lose_update
rts
lose_update:
; blink the retry prompt
lda framectr
and #32
bne lu_hide
+prtext 19, 12, YELLOW, txt_retry, txt_retrye-txt_retry
jmp lu_input
lu_hide:
+prtext 19, 12, YELLOW, txt_blank, txt_retrye-txt_retry
lu_input:
; fire or space: straight back in with a fresh maze
jsr start_edge
beq lu_nofire
jmp play_init
lu_nofire:
; the m key: keyboard column 4, row bit 4 (start_edge has
; already returned zero here, so joystick fire is not
; ghosting this row line)
lda #$ef
sta CIA1_PA
lda CIA1_PB
and #$10
bne lu_done
lda #$ff
sta CIA1_PA
jmp menu_init
lu_done:
lda #$ff
sta CIA1_PA
rts
; ---- game over texts ----
txt_gameover: !scr "game over"
txt_caught: !scr "the enemies got you"
txt_caughte:
txt_retry: !scr "fire = try again"
txt_retrye:
txt_mmenu: !scr "m = main menu"
txt_mmenue:
+783
View File
@@ -0,0 +1,783 @@
; -----------------------------------------------------------
; play.asm - the game: a fresh maze is carved with a
; recursive backtracker every round, player 1 (joystick 2)
; runs the corridors toward the exit diamond, player 2
; (joystick 1) rides the wall corners and fire spins the
; four wall pieces around the cursor by 90 degrees; the
; roaming colored enemies end the round on touch
;
; screen layout: status on row 0, the 39x23 maze lattice on
; rows 1-23 (cells = odd col / even row, posts = even col /
; odd row, wall slots = the rest), a hint line on row 24
; -----------------------------------------------------------
play_init:
jsr clear_screen
+prtext 0, 0, WHITE, txt_title, 9
+prtext 0, 28, LGREY, txt_goal, txt_goale-txt_goal
+prtext 24, 6, DGREY, txt_hint, txt_hinte-txt_hint
; fill the whole lattice with wall blocks
ldx #1
pi_fillrow:
stx ptmp
jsr row_ptr
ldy #38
pi_fillcol:
lda #CH_WALL
sta (scrlo),y
lda #COL_WALL
sta (collo),y
dey
bpl pi_fillcol
ldx ptmp
inx
cpx #24
bne pi_fillrow
; the pivot posts get their own darker shade
ldx #1 ; post rows 1, 3, ... 23
pi_postrow:
stx ptmp
jsr row_ptr
ldy #38 ; post columns 38, 36, ... 0
pi_postcol:
lda #COL_POST
sta (collo),y
dey
dey
bpl pi_postcol
ldx ptmp
inx
inx
cpx #25
bcc pi_postrow
; carve the maze
ldx #0
lda #0
pi_clrv:
sta visited,x
inx
cpx #NCELLS
bne pi_clrv
jsr gen_maze
; the exit: a green diamond in the right border wall
pi_exit:
jsr rand8
and #$0f
cmp #MAZE_CH
bcs pi_exit
asl ; cell row -> screen row
clc
adc #2
sta exrow
tax
jsr row_ptr
ldy #38
lda #CH_EXIT
sta (scrlo),y
lda #COL_EXIT
sta (collo),y
; the runner starts in the top-left cell
lda #1
sta px
lda #2
sta py
jsr draw_player
; the corner cursor starts on the center post
lda #18
sta kx
lda #11
sta ky
jsr spawn_enemies
; input state: a fire still held from the previous screen
; must be released before it can spin walls
lda #0
sta rheld
sta cheld
sta endflag
lda #1
sta cfheld
+setupd play_update
rts
; ============================================================
; maze generation - recursive backtracker over the 19x11
; cells; the screen itself is the wall data structure
; ============================================================
gen_maze:
lda #0
sta gcx
sta gcy
sta gsp
jsr gv_mark
jsr carve_cell
gm_loop:
; collect the unvisited neighbor directions
lda #0
sta gn
ldx #0
gm_scan:
stx gtd
lda gcx
clc
adc dxtab,x
sta gnx
cmp #MAZE_CW ; unsigned: -1 wraps to $ff and fails too
bcs gm_next
ldx gtd
lda gcy
clc
adc dytab,x
sta gny
cmp #MAZE_CH
bcs gm_next
ldx gny
lda mul19,x
clc
adc gnx
tax
lda visited,x
bne gm_next
ldx gn
lda gtd
sta gdlist,x
inc gn
gm_next:
ldx gtd
inx
cpx #4
bne gm_scan
lda gn
beq gm_pop
; pick a random one of them
gm_pick:
jsr rand8
and #3
cmp gn
bcs gm_pick
tax
lda gdlist,x
sta gtd
; push the current cell, open the wall, step through it
ldx gsp
lda gcx
sta stackx,x
lda gcy
sta stacky,x
inc gsp
jsr carve_wall
ldx gtd
lda gcx
clc
adc dxtab,x
sta gcx
ldx gtd
lda gcy
clc
adc dytab,x
sta gcy
jsr gv_mark
jsr carve_cell
jmp gm_loop
gm_pop:
; dead end: back up; empty stack = the maze is done
lda gsp
beq gm_done
dec gsp
ldx gsp
lda stackx,x
sta gcx
lda stacky,x
sta gcy
jmp gm_loop
gm_done:
rts
gv_mark: ; visited[gcy * 19 + gcx] = 1
ldx gcy
lda mul19,x
clc
adc gcx
tax
lda #1
sta visited,x
rts
carve_cell: ; open the cell (2*gcx+1, 2*gcy+2)
lda gcy
asl
clc
adc #2
tax
jsr row_ptr
lda gcx
asl
clc
adc #1
tay
lda #CH_FLOOR
sta (scrlo),y
lda #BLACK
sta (collo),y
rts
carve_wall: ; open the wall slot toward direction gtd
ldx gtd
lda gcy
asl
clc
adc #2
clc
adc dytab,x
tax
jsr row_ptr
ldx gtd
lda gcx
asl
clc
adc #1
clc
adc dxtab,x
tay
lda #CH_FLOOR
sta (scrlo),y
lda #BLACK
sta (collo),y
rts
dxtab: !byte 0, 0, $ff, 1 ; up, down, left, right
dytab: !byte $ff, 1, 0, 0
mul19: !byte 0, 19, 38, 57, 76, 95, 114, 133, 152, 171, 190
; ---- enemies: random colors, random right-half cells ----
spawn_enemies:
lda #0
sta ei
sn_loop:
sn_rx:
jsr rand8
and #$0f
cmp #MAZE_CH
bcs sn_rx
clc
adc #MAZE_CW-MAZE_CH ; cell column 8-18: away from the runner
asl
clc
adc #1
sta gnx ; candidate screen column
sn_ry:
jsr rand8
and #$0f
cmp #MAZE_CH
bcs sn_ry
asl
clc
adc #2
sta gny ; candidate screen row
ldx gny
jsr row_ptr
ldy gnx
lda (scrlo),y
cmp #CH_FLOOR ; only an empty cell will do
bne sn_rx
lda #CH_ENEMY
sta (scrlo),y
jsr rand8
and #7
tax
lda ecoltab,x
sta (collo),y
ldx ei
sta ecol,x
lda gnx
sta ex,x
lda gny
sta ey,x
jsr rand8
and #3
sta edir,x
inc ei
lda ei
cmp #NENEMIES
bne sn_loop
rts
ecoltab: ; anything but the white runner / grey walls
!byte RED, CYAN, PURPLE, YELLOW, ORANGE, LRED, LBLUE, BLUE
; ============================================================
; one frame of play
; ============================================================
play_update:
jsr upd_runner
lda endflag
bne pu_end
jsr upd_enemies
lda endflag
bne pu_end
jsr upd_cursor
jsr upd_flash
rts
pu_end:
cmp #1
beq pu_win
lda #SFX_LOSE
jsr sfx_play
jmp lose_init ; its rts returns to the main loop
pu_win:
lda #SFX_WIN
jsr sfx_play
jmp win_init
; ---- player 1: walk the corridors, one char per step ----
upd_runner:
jsr read_joy
and #$0f
sta rdir
bne ur_held
lda #0
sta rheld
rts
ur_held:
lda rheld
beq ur_first
dec rtimer
beq ur_rep
rts
ur_rep:
lda #REP_PERIOD
sta rtimer
jmp ur_move
ur_first:
lda #1
sta rheld
lda #REP_HOLD
sta rtimer
ur_move:
ldx px
ldy py
lda rdir
lsr
bcs ur_up
lsr
bcs ur_down
lsr
bcs ur_left
inx ; right
jmp ur_try
ur_up:
dey
jmp ur_try
ur_down:
iny
jmp ur_try
ur_left:
dex
ur_try:
stx ttx
sty tty
ldx tty
jsr row_ptr
ldy ttx
lda (scrlo),y
cmp #CH_FLOOR
beq ur_go
cmp #CH_EXIT
beq ur_win
cmp #CH_ENEMY
beq ur_dead
rts ; a wall: blocked
ur_go:
ldx py
jsr row_ptr
ldy px
lda #CH_FLOOR
sta (scrlo),y
lda #BLACK
sta (collo),y
lda ttx
sta px
lda tty
sta py
jmp draw_player
ur_win:
lda #1
sta endflag
rts
ur_dead:
lda #2
sta endflag
rts
draw_player:
ldx py
jsr row_ptr
ldy px
lda #CH_PLAYER
sta (scrlo),y
lda #WHITE
sta (collo),y
rts
; ---- player 2: the corner cursor + the wall spin ----
upd_cursor:
jsr read_joy2
sta j2bits
and #$0f
sta cdir
bne uc_held
lda #0
sta cheld
jmp uc_fire
uc_held:
lda cheld
beq uc_first
dec ctimer
beq uc_rep
jmp uc_fire
uc_rep:
lda #CREP_PERIOD
sta ctimer
jmp uc_move
uc_first:
lda #1
sta cheld
lda #CREP_HOLD
sta ctimer
uc_move:
ldx kx
ldy ky
lda cdir
lsr
bcs uc_up
lsr
bcs uc_down
lsr
bcs uc_left
cpx #36 ; right: interior posts end at column 36
bcs uc_fire
inx
inx
jmp uc_go
uc_up:
cpy #4 ; top interior post row is 3
bcc uc_fire
dey
dey
jmp uc_go
uc_down:
cpy #21 ; bottom interior post row is 21
bcs uc_fire
iny
iny
jmp uc_go
uc_left:
cpx #3 ; left interior post column is 2
bcc uc_fire
dex
dex
uc_go:
stx ttx
sty tty
ldx ky ; the old post gets its color back
jsr row_ptr
ldy kx
lda #COL_POST
sta (collo),y
lda ttx
sta kx
lda tty
sta ky
lda #SFX_CURSOR
jsr sfx_play
uc_fire:
lda j2bits
and #JOY_FIRE
beq uc_frel
lda cfheld
bne uc_done
lda #1
sta cfheld
jmp do_rotate ; its rts returns to play_update's caller
uc_frel:
lda #0
sta cfheld
uc_done:
rts
; ---- spin the four wall slots around post (kx, ky) by 90
; degrees clockwise; denied while anyone stands in them ----
do_rotate:
ldx ky ; gather the four slot chars
dex
jsr row_ptr
ldy kx
lda (scrlo),y
sta nch
ldx ky
inx
jsr row_ptr
ldy kx
lda (scrlo),y
sta sch
ldx ky
jsr row_ptr
ldy kx
dey
lda (scrlo),y
sta wch
iny
iny
lda (scrlo),y
sta ech
; every slot must be plain wall or floor
lda nch
jsr rot_ok
bcs rot_deny
lda sch
jsr rot_ok
bcs rot_deny
lda wch
jsr rot_ok
bcs rot_deny
lda ech
jsr rot_ok
bcs rot_deny
; clockwise: north<-west, east<-north, south<-east, west<-south
ldx ky
dex
jsr row_ptr
ldy kx
lda wch
jsr put_slot
ldx ky
inx
jsr row_ptr
ldy kx
lda ech
jsr put_slot
ldx ky
jsr row_ptr
ldy kx
dey
lda sch
jsr put_slot
iny
iny
lda nch
jsr put_slot
lda #SFX_ROTATE
jmp sfx_play
rot_deny:
lda #SFX_DENY
jmp sfx_play
rot_ok: ; C clear when A is spinnable (wall or floor)
cmp #CH_WALL
beq ro_yes
cmp #CH_FLOOR
beq ro_yes
sec
rts
ro_yes:
clc
rts
put_slot: ; write char A at (scrlo),y + matching color
sta (scrlo),y
cmp #CH_WALL
beq ps_wall
lda #BLACK
sta (collo),y
rts
ps_wall:
lda #COL_WALL
sta (collo),y
rts
; ---- the enemies: staggered random walkers ----
upd_enemies:
lda #0
sta ei
ue_loop:
ldx ei
lda framectr
clc
adc estag,x
and #7 ; each enemy steps every 8th frame
bne ue_next
jsr enemy_step
lda endflag
bne ue_out
ue_next:
inc ei
lda ei
cmp #NENEMIES
bne ue_loop
ue_out:
rts
estag: !byte 0, 2, 4, 6
enemy_step:
jsr rand8 ; 1 in 4: wander off in a new direction
and #3
beq es_newdir
ldx ei
lda edir,x
jsr etry_dir
bcc es_done
es_newdir:
lda #8
sta etries
es_pick:
jsr rand8
and #3
jsr etry_dir
bcc es_done
dec etries ; walled in on all sides: sit this one out
bne es_pick
es_done:
rts
etry_dir: ; A = direction; C clear when it moved (or won)
sta etd
tax
ldy ei
lda ex,y
clc
adc dxtab,x
sta ttx
lda ey,y
clc
adc dytab,x
sta tty
ldx tty
jsr row_ptr
ldy ttx
lda (scrlo),y
cmp #CH_FLOOR
beq ed_move
cmp #CH_PLAYER
beq ed_kill
sec ; wall, exit or another enemy: blocked
rts
ed_kill:
lda #2
sta endflag
clc
rts
ed_move:
ldx ei
lda ey,x
sta gny ; old position
lda ex,x
sta gnx
lda ttx
sta ex,x
lda tty
sta ey,x
lda etd
sta edir,x
ldx gny ; erase the old cell
jsr row_ptr
ldy gnx
lda #CH_FLOOR
sta (scrlo),y
lda #BLACK
sta (collo),y
ldx tty ; draw at the new one
jsr row_ptr
ldy ttx
lda #CH_ENEMY
sta (scrlo),y
ldx ei
lda ecol,x
sta (collo),y
clc
rts
; ---- per-frame flashes: the exit blinks, the cursor pulses ----
upd_flash:
ldx exrow
jsr row_ptr
ldy #38
lda framectr
and #8
bne uf_exw
lda #COL_EXIT
jmp uf_exset
uf_exw:
lda #WHITE
uf_exset:
sta (collo),y
ldx ky
jsr row_ptr
ldy kx
lda framectr
and #4
bne uf_cred
lda #YELLOW
jmp uf_cset
uf_cred:
lda #RED
uf_cset:
sta (collo),y
rts
; ---- play texts ----
txt_goal: !scr "reach the "
!byte CH_EXIT
txt_goale:
txt_hint: !scr "p1: joy2/wasd p2: joy1/ijkl"
txt_hinte:
; ---- play variables ----
px: !byte 0 ; runner position (screen col / row)
py: !byte 0
kx: !byte 0 ; corner cursor position (post col / row)
ky: !byte 0
exrow: !byte 0 ; screen row of the exit diamond
endflag: !byte 0 ; 0 = playing, 1 = escaped, 2 = caught
rheld: !byte 0 ; runner direction repeat state
rtimer: !byte 0
rdir: !byte 0
cheld: !byte 0 ; cursor direction repeat state
ctimer: !byte 0
cdir: !byte 0
cfheld: !byte 0 ; cursor fire edge detection state
j2bits: !byte 0
ptmp: !byte 0 ; scratch
ttx: !byte 0 ; move target (col / row)
tty: !byte 0
nch: !byte 0 ; the four slot chars around the cursor
ech: !byte 0
sch: !byte 0
wch: !byte 0
ei: !byte 0 ; enemy loop index
etries: !byte 0
etd: !byte 0
gcx: !byte 0 ; maze generator state
gcy: !byte 0
gsp: !byte 0
gn: !byte 0
gtd: !byte 0
gnx: !byte 0
gny: !byte 0
gdlist: !fill 4, 0
ex: !fill NENEMIES, 0 ; enemy positions, headings, colors
ey: !fill NENEMIES, 0
edir: !fill NENEMIES, 0
ecol: !fill NENEMIES, 0
; ---- maze generator buffers ----
visited: !fill NCELLS, 0
stackx: !fill NCELLS, 0
stacky: !fill NCELLS, 0
+56
View File
@@ -0,0 +1,56 @@
; -----------------------------------------------------------
; rules.asm - how to play, one static card; fire returns
; -----------------------------------------------------------
rules_init:
jsr clear_screen
+prbig 1, 10, WHITE, txt_rules, 5
+prtext 8, 9, LGREEN, txt_r1, txt_r1e-txt_r1
+prtext 9, 8, WHITE, txt_r2, txt_r2e-txt_r2
+prtext 10, 7, WHITE, txt_r3, txt_r3e-txt_r3
+prtext 12, 9, LBLUE, txt_r4, txt_r4e-txt_r4
+prtext 13, 6, WHITE, txt_r5, txt_r5e-txt_r5
+prtext 14, 6, WHITE, txt_r6, txt_r6e-txt_r6
+prtext 15, 7, WHITE, txt_r7, txt_r7e-txt_r7
+prtext 17, 7, LRED, txt_r8, txt_r8e-txt_r8
+setupd rules_update
rts
rules_update:
; blink the return prompt
lda framectr
and #32
bne ru_hide
+prtext 21, 13, YELLOW, txt_return, 13
jmp ru_input
ru_hide:
+prtext 21, 13, YELLOW, txt_blank, 13
ru_input:
jsr start_edge
beq ru_stay
jmp menu_init
ru_stay:
rts
; ---- rules texts ----
txt_r1: !scr "player 1 - joystick 2"
txt_r1e:
txt_r2: !scr "run the maze and escape"
txt_r2e:
txt_r3: !scr "through the green diamond."
txt_r3e:
txt_r4: !scr "player 2 - joystick 1"
txt_r4e:
txt_r5: !scr "ride the corners of the walls;"
txt_r5e:
txt_r6: !scr "fire spins the four wall pieces"
txt_r6e:
txt_r7: !scr "around the cursor by 90 degrees."
txt_r7e:
txt_r8: !scr "avoid the colored enemies!"
txt_r8e:
+71
View File
@@ -0,0 +1,71 @@
; -----------------------------------------------------------
; sound.asm - single-voice SFX driver: each effect is a
; waveform + start pitch + per-frame pitch slide + duration,
; played on SID voice 1 (sustain 0, the decay shapes it)
; -----------------------------------------------------------
sfx_init:
lda #$0f ; full volume, no filter
sta SID_VOL
lda #0
sta SID_V1CR
sta sfx_timer
rts
; ---- start effect A ----
sfx_play:
tax
lda #0
sta SID_V1CR ; retrigger the envelope cleanly
lda sfxad,x
sta SID_V1AD
lda #0
sta SID_V1SR
sta SID_V1FL
lda sfxfreq,x
sta SID_V1FH
sta sfx_fh
lda #$08 ; 50% pulse width, if a pulse wave is used
sta SID_V1PH
lda #0
sta SID_V1PL
lda sfxctrl,x
sta SID_V1CR
sta sfx_ctrl
lda sfxslide,x
sta sfx_slide
lda sfxdur,x
sta sfx_timer
rts
; ---- called once per frame by the main loop ----
sfx_update:
lda sfx_timer
beq su_idle
dec sfx_timer
bne su_slide
lda sfx_ctrl ; time is up: gate off
and #$fe
sta SID_V1CR
rts
su_slide:
lda sfx_fh ; bend the pitch by the slide value
clc
adc sfx_slide
sta sfx_fh
sta SID_V1FH
su_idle:
rts
; ---- effect tables: cursor, select, rotate, deny, win, lose ----
sfxfreq: !byte $50, $20, $12, $06, $20, $38
sfxslide: !byte $00, $04, $fe, $00, $02, $ff
sfxctrl: !byte $11, $21, $81, $21, $11, $21
sfxad: !byte $06, $08, $08, $08, $0a, $0a
sfxdur: !byte $04, $0a, $08, $0c, $28, $30
; ---- driver variables ----
sfx_timer: !byte 0 ; frames left of the current effect
sfx_fh: !byte 0 ; frequency high byte shadow
sfx_slide: !byte 0 ; signed per-frame pitch step
sfx_ctrl: !byte 0 ; control register shadow
+76
View File
@@ -0,0 +1,76 @@
; -----------------------------------------------------------
; win.asm - the escape screen: big ESCAPED with the gold
; wash; fire returns to the main menu
; -----------------------------------------------------------
win_init:
jsr clear_screen
+prbig 3, 6, YELLOW, txt_escaped, 7
+prtext 11, 15, WHITE, txt_welldone, txt_welldonee-txt_welldone
+prtext 13, 9, LGREY, txt_beaten, txt_beatene-txt_beaten
lda #0
sta washphase
+setupd win_update
rts
win_update:
; the gold wash rolls down the big letters (rows 3-7)
inc washphase
lda #0
sta rtmp2
wu_row:
lda #3
clc
adc rtmp2
tax
jsr row_ptr
lda washphase
lsr
clc
adc rtmp2
and #15
tax
lda washtbl,x
sta rtmp
ldy #32 ; the letters span columns 6-32
wu_col:
lda (scrlo),y
cmp #$a0
bne wu_skip
lda rtmp
sta (collo),y
wu_skip:
dey
cpy #5
bne wu_col
inc rtmp2
lda rtmp2
cmp #5
bne wu_row
; blink the prompt
lda framectr
and #32
bne wu_hide
+prtext 20, 14, YELLOW, txt_firemenu, txt_firemenue-txt_firemenu
jmp wu_input
wu_hide:
+prtext 20, 14, YELLOW, txt_blank, txt_firemenue-txt_firemenu
wu_input:
jsr start_edge
beq wu_stay
jmp menu_init ; a win leads back to the main screen
wu_stay:
rts
; ---- win texts ----
txt_escaped: !scr "escaped"
txt_welldone: !scr "well done!"
txt_welldonee:
txt_beaten: !scr "you beat the labyrinth"
txt_beatene:
txt_firemenu: !scr "fire = menu"
txt_firemenue: