183 lines
5 KiB
Fennel
183 lines
5 KiB
Fennel
(local util (require :lib.util))
|
|
(local {: lo : hi : readjson} util)
|
|
(local lume (require :lib.lume))
|
|
(local asm (require :asm.asm))
|
|
(local VM (require :asm.vm))
|
|
(local tiles (require :game.tiles))
|
|
(local files (require :game.files))
|
|
(local Prodos (require :asm.prodos))
|
|
(local actions (require :editor.actions))
|
|
|
|
(local prg (asm.new))
|
|
(local vm (VM.new prg {:org 0xc00}))
|
|
(Prodos.install-words vm)
|
|
|
|
(local org {
|
|
:boot vm.code
|
|
:code (prg:org 0x4000)
|
|
})
|
|
|
|
(local mapw 20)
|
|
(local maph 12)
|
|
|
|
(local mon {
|
|
:hexout :0xfdda
|
|
:putchar :0xfded
|
|
:bell :0xff3a
|
|
})
|
|
|
|
(local style {
|
|
:normal 0x80
|
|
:inverse 0x00
|
|
:flashing 0x40
|
|
})
|
|
(fn str-with-style [s stylebits]
|
|
(-> [(string.byte s 1 -1)]
|
|
(lume.map #(bit.bor (bit.band $1 0x3f) stylebits))
|
|
(-> (table.unpack) (string.char))))
|
|
(fn achar [c] (bit.bor (string.byte c) style.normal))
|
|
(fn astr [s ?style] (str-with-style s (or ?style style.normal)))
|
|
|
|
(fn rot8l [n] ; clears carry
|
|
(local block [:block [:clc]])
|
|
(for [_ 1 n] (table.insert block [:block [:asl :a] [:adc 0]]))
|
|
block)
|
|
|
|
; core graphics words needed for booting
|
|
(vm:def :hires
|
|
[:sta :0xc050]
|
|
[:sta :0xc057]
|
|
[:sta :0xc052]
|
|
[:sta :0xc054])
|
|
|
|
(vm:def :cleargfx
|
|
(vm:push 0x4000)
|
|
[:block :page
|
|
[:dec vm.TOPH :x]
|
|
[:lda 0]
|
|
[:block :start
|
|
[:sta [vm.TOP :x]]
|
|
[:inc vm.TOP :x]
|
|
[:bne :start]]
|
|
[:lda vm.TOPH :x]
|
|
[:cmp 0x20]
|
|
[:bne :page]]
|
|
(vm:drop))
|
|
|
|
; a handful of debugging words
|
|
(vm:def :.
|
|
[:lda vm.TOPH :x]
|
|
[:jsr mon.hexout]
|
|
[:lda vm.TOP :x]
|
|
[:jsr mon.hexout]
|
|
[:lda (achar " ")]
|
|
[:jsr mon.putchar]
|
|
(vm:drop))
|
|
|
|
(vm:def :stacklen
|
|
(vm:reserve)
|
|
[:txa] [:lsr :a] [:sta vm.TOP :x]
|
|
[:lda 0] [:sta vm.TOPH :x])
|
|
|
|
(vm:word :.s
|
|
:stacklen (prg:parse-addr vm.TOP) :swap
|
|
(vm:for :dup :get :. :inc :inc) :drop)
|
|
|
|
; input words
|
|
(vm:def :last-key ; -- key
|
|
(vm:reserve)
|
|
[:lda :0xc000]
|
|
[:and 0x7f]
|
|
[:sta vm.TOP :x]
|
|
[:lda 0]
|
|
[:sta vm.TOPH :x])
|
|
|
|
(vm:def :read-key ; -- key|0
|
|
[:block
|
|
(vm:reserve)
|
|
[:lda :0xc000]
|
|
[:bmi :key-pressed]
|
|
[:lda 0]
|
|
[:sta vm.TOP :x]
|
|
[:sta vm.TOPH :x]
|
|
(vm:ret)
|
|
:key-pressed
|
|
[:and 0x7f]
|
|
[:sta vm.TOP :x]
|
|
[:lda 0]
|
|
[:sta vm.TOPH :x]
|
|
[:sta :0xc010]])
|
|
|
|
; "random" numbers
|
|
; this is used only for cosmetic purposes and short noise generation, so we can get away
|
|
; with just including a short table of random digits rather than implementing our own
|
|
; pseudorandom number generator
|
|
(var randombytes "")
|
|
(for [i 0 0x40] (set randombytes (.. randombytes (string.char (math.random 0 255)))))
|
|
(vm.code:append :randombytes [:bytes randombytes])
|
|
(vm:var :irandom [:db 0])
|
|
(vm:word :rnd
|
|
:irandom :bget
|
|
:dup 1 :+ 0x3f :& :irandom :bset
|
|
:lit :randombytes :+ :bget)
|
|
|
|
; 20x12 means full map is 240 bytes - we have an extra 16 bytes at the end for metadata
|
|
(fn append-map [map org label]
|
|
(org:append
|
|
[:align 0x100] label
|
|
[:bytes map.map]
|
|
[:db (length map.objects)]
|
|
[:dw (tiles.encode-yx map.player)]
|
|
[:jmp (if (= (or map.tickword "") "") :next map.tickword)]
|
|
[:jmp (if (= (or map.moveword "") "") :move-noop map.moveword)]
|
|
[:jmp (if (= (or map.loadword "") "") :next map.loadword)]))
|
|
|
|
(vm.code:append :map-ptr [:db 0] [:hot-preserve :map-page [:db 0]])
|
|
(vm:word :map :lit :map-ptr :get)
|
|
(vm:word :entity-count :map 240 :+ :bget)
|
|
(vm:word :map-player-yx-ptr 241 :+)
|
|
(vm:word :map-player-yx :map :map-player-yx-ptr :get)
|
|
(vm:word :map-specific-tick :map 243 :+ :execute)
|
|
(vm:word :map-specific-move :map 246 :+ :execute)
|
|
(vm:word :map-specific-load :map 249 :+ :execute)
|
|
|
|
(fn generate-entity-code [level vm prefix]
|
|
(each [ientity entity (ipairs level.objects)]
|
|
(when (not entity.advanced)
|
|
(let [code []]
|
|
(each [iaction action (ipairs (or entity.steps []))]
|
|
(if action.condition (lume.push code (.. :cond- action.condition) (vm:when (actions.generate action vm iaction)))
|
|
(lume.push code (actions.generate action vm iaction))))
|
|
(vm:word (.. prefix ientity) :drop (table.unpack code))))))
|
|
|
|
(fn deflevel [ilevel label]
|
|
(local level prg) ; todo: (asm.new prg) - if we want to load levels as an overlay
|
|
(local org level.vm.code) ; (level:org org.level.org) - if we want to give level data a stable loxation
|
|
(local map (. files.game.levels ilevel))
|
|
(local entity (require :game.entity))
|
|
(append-map map org label)
|
|
(entity.append-from-map map org label)
|
|
(set level.vm.code org)
|
|
(generate-entity-code map level.vm (.. label "-entity-word-"))
|
|
level)
|
|
|
|
(fn say-runon [portrait ...]
|
|
(local result [:vm (.. :draw-portrait- portrait)])
|
|
(local lines [...])
|
|
(local ilineOffset (if (< (length lines) 4) 1 0))
|
|
(each [iline line (ipairs lines)]
|
|
(table.insert result [:vm (vm:str line) (.. :draw-text (+ iline ilineOffset))]))
|
|
result)
|
|
|
|
(fn say [portrait ...]
|
|
(local result (say-runon portrait ...))
|
|
(table.insert result :dismiss-dialog)
|
|
result)
|
|
|
|
(fn itile [label] (tiles.find-itile files.game.tiles label))
|
|
|
|
(set vm.code org.code)
|
|
|
|
{: vm : prg : mapw : maph : mon : org : achar : astr : style : rot8l : deflevel : say : say-runon : itile}
|
|
|