arch z80
include "msx.oc"
include "msx/romram.oc"
msx:link-as-rom main _
module device {
data screen = [32 * 20]byte : bss
data screen-sync = byte @ <reserved>
macro screen-sync/enable() {
@0xfe -> [device:screen-sync] // CP <byte>
}
macro screen-sync/disable() {
@0x18 -> [device:screen-sync] // JR <byte> (default)
}
data key-changes = byte : bss
data key-states = byte : bss
proc init() {
msx:set-vdp-mode 0b0000_0000 0b1110_0000
clear-namtab()
clear-spratr()
msx:update-hook msx:H_TIMI timi
return
}
proc timi() {
push/pop AF {
*patch* screen-sync; once {
JR _END
msx:set-vdp-write-addr! msx:t32nam(0 2)
msx:write-vdp-data/wide screen sizeof(screen)
}
A - A -> [msx:SCNCNT] -> [msx:INTCNT] // skip default key scan
device:read-keys(=> B ! C)
}
return
}
proc read-keys(=> B ! C) {
@5 . msx:snsmat(A => A) // Z Y X W - - - -
A >* 4 & 0x0F -> B
@8 . msx:snsmat(A => A) // RIGHT DOWN UP LEFT - - - -
A & 0xF0 | B -not -> B // RIGHT DOWN UP LEFT Z Y X W
@[key-states] ^ B -> [key-changes]
@B -> [key-states]
return
}
proc clear-namtab() {
msx:set-vdp-write-addr msx:T32NAM/INI
msx:fill-vdp-data/wide ' ' (256 * 3)
return
}
proc clear-spratr() {
msx:set-vdp-write-addr msx:T32ATR/INI
msx:fill-vdp-data 209 128
return
}
}
module main {
data player = struct {
x byte
y byte
dx byte
dy byte
} : bss
data score = word @ <reserved>
data count = byte : bss
macro scene=>(addr) {
SP <- msx:STACK_ADDR
goto %=addr
}
proc main() {
romram:boot
device:init()
fallthrough
}
proc game-main() {
data screen/default = byte [
"#[SCORE:000]####################"
"# #"
"# #"
"# #"
"# #"
"# #"
"# #"
"# #"
"# #"
"# #"
"# #"
"# #"
"# #"
"# #"
"# #"
"# #"
"# #"
"# #"
"# #"
"################################"
] : rodata
memcpy device:screen screen/default sizeof(device:screen)
HL@0 -> [score]
A - A -> [player.dx] -- -> [player.dy]
@16 -> [count] -> [player.x] -> [player.y]
device:screen-sync/enable
fallthrough
}
proc game-main/loop() {
msx:wait-vsync
once {
A <- [device:key-states]
A <* 1; if carry? { // RIGHT
A - A -> [player.dy]
A ++ -> [player.dx]
break
}
A <* 1; if carry? { // DOWN
A - A -> [player.dx]
A ++ -> [player.dy]
break
}
A <* 1; if carry? { // UP
A - A -> [player.dx]
A -- -> [player.dy]
break
}
A <* 1; if carry? { // LEFT
A - A -> [player.dy]
A -- -> [player.dx]
// break
}
}
[HL@count] --; if zero? {
C@A@[player.x] . A@[player.y] . player/xy->addr(A C => HL ! BC)
@'*' -> [HL]
@[player.x] + [HL@player.dx] -> [player.x] -> C
@[player.y] + [HL@player.dy] -> [player.y]
player/xy->addr(A C => HL ! BC)
@[HL] -? ' '; if not-zero? {
scene=> game-over
}
@'@' -> [HL]
HL@[score] ++ -> [score] + HL + HL // HL << 2
@10 - H -> [count]
HL <- 0; *patch* score word
DE@(device:screen + 8) . itoa(HL DE => DE !)
}
recur
}
proc player/xy->addr(A C => HL ! BC) { // A: 000y_yyyy C: 000x_xxxx
A >* 3 -> L & 0b0001_1111 -> H // L: {yyy}{0_00yy} ==> H: {000}{0_00yy}
A ^ L + C -> L // L: {yyy}{0_0000} ==> L: {yyy}{x_xxxx}
HL + BC@device:screen // HL: {000}{0_00yy}_{yyy}{x_xxxx}
return
}
proc game-over() {
data message = byte [
" "
" *--------------* "
" | | "
" | GAME OVER | "
" | [Z] RETRY | "
" | | "
" *--------------* "
" "
] : rodata
device:screen-sync/disable
msx:write-vdp-data/rect message msx:t32nam(7 8) 18 8 32
never-return loop {
msx:wait-vsync
@[device:key-states] & BIT-3; if not-zero? {
scene=> game-main
}
}
}
proc itoa(HL DE => DE !) {
BC@-100 . count(BC => HL DE)
BC@-10 . count(BC => HL DE)
@L + '0' -> [DE]
return
proc count(BC => HL DE) {
A <- ('0' - 1)
loop {
A ++
HL + BC
} while carry?
HL -$ BC
A -> [DE]; DE ++
return
}
}
}