(local util (require :lib.util)) (local lume (require :lib.lume)) (local {: parse} (util.require :presentation.engine)) (local core (require :core)) (local style (require :core.style)) (local TileEditView (require :editor.tileedit)) (local MapEditView (require :editor.mapedit)) (local PortraitEditView (require :editor.portraitedit)) (local FontEditView (require :editor.fontedit)) (local ScreenEditView (require :editor.screenedit)) (local files (require :game.files)) (local h {:style true :font (renderer.font.load "presentation/font/PrintChar21.ttf" 64) :color style.caret :justify :center :topPadding 14 :lowerPadding 64}) (local ** {:style true :font (renderer.font.load "presentation/font/PRNumber3.ttf" 32) :color style.text :justify :left :lowerPadding 7 :pause-after true}) (fn p [style] (lume.merge style {:pause-after true})) (fn np [style] (lume.merge style {:pause-after false})) (fn bgimg [filename] {:image filename :justify :center :overlay true :alpha 0.3 :topPadding 0}) (fn view-cleanup [view] (let [root core.root_view.root_node node (root:get_node_for_view view)] (when node (node:close_active_view root)))) (fn split-and-open [self f] (let [focused-view core.active_view focused-node (core.root_view:get_active_node) _ (when self.split (focused-node:split self.split)) view (f self) node (core.root_view:get_active_node)] (when (= (core.root_view.root_node:get_node_for_view view) nil) (node:add_view view)) (when self.split (core.set_active_view focused-view)) ; don't switch focus #(view-cleanup view))) (fn openview [f ?extra] (lume.merge {:action #(split-and-open $1 f)} (or ?extra {}))) (fn openfile [filename ?extra] (openview #(let [ldoc (core.open_doc filename) view (core.root_view:open_doc ldoc)] (when $1.line (view:scroll_to_line $1.line)) view) ?extra)) (parse [ [h "" "" "" "Honeylisp" "" "" "" (np **) "Jeremy Penner" "https://spindleyq.itch.io/" "https://blog.information-superhighway.net/" "https://bitbucket.org/SpindleyQ/honeylisp" "https://gamemaking.social/@SpindleyQ" "https://twitter.com/SpindleyQ" {:pause-after true}] [(bgimg "presentation/pics/pete286.jpeg") h "Some Background" ** "In 2019 I built a 16-bit MS-DOS game engine." "* Built on hardware" "* Using only period-appropriate software (Turbo C, NeoPaint)" "* Powered by Forth" "* Integrated custom tools" "* Interactive development via serial terminal"] [(bgimg "presentation/pics/ggj2020.jpeg") h "Neut Tower" ** "In 2020, I did the Global Game Jam on my 286." "Finished 'Shareware Episode 1' a couple of months later."] [h "The Idea" ** "What if I took a similar DIY approach with modern tooling?" "* I'd done Forth; what about Lisp?" " https://fennel-lang.org/" "* How far can I push fast iterative development?" "* Could I integrate an editor?" "* How can I leverage emulation?"] [h "Step 1: Assembler" ** "Represent instructions using Fennel data literals" (openfile :neuttower/defs.fnl {:split :right :line 57}) " [:lda 0xff]" "Represent labels with Fennel strings" " :loop [:bne :loop]" "Lexical scope with nested blocks" " [:block :loop (generate-loop-code) [:bne :loop]]"] ; ;; DEMO before tech dive?? [h "Wait WTF Is An Assembler" ** "It's just converting mnemonics to bytes, right?" {:image "presentation/pics/assembly-markup.png" :justify :center :pause-after true} "Whoooops, actually the hard part is converting labels to addresses" "Zero-page instructions are a different size, which messes up data layout!" "Initial pass is needed to gather all symbols to determine sizes" "What about data?" " [:db 123] [:dw 12345] [:bytes \"HELLO WORLD\"] [:ref :hello]" "Must be able to line up bytes on page boundaries" " [:align 0x100]"] [h "Step 2: Virtual Machine" {:image "presentation/pics/thinkhard.png" :justify :center} ** "Not super keen on writing a complicated compiler" "I'm already very comfortable with Forth" "Let's build a stack machine!" "\"Direct threaded\" inner interpreter" "\"Immediate words\" can be Fennel functions that generate code!"] [h "Extensible Assembler??" ** "How do you turn code into bytes?" " [:vm 1 2 :+ :.]" "Branching?" " (vm:if [:do-true-thing] [:do-false-thing])" "I can even do short-circuiting OR!" " (vm:if-or [[:dup 1 :=] [:dup 3 :=]] [:do-true-thing] [:do-false-thing])"] [h "Step 3: Tooling And Workflow" ** "I want an environment that makes it easy to make graphical tools" "I'm SO tired of web tech" "Could LÖVE2D with an imgui work?"] [h "lite" ** "A small, highly-extensible text editor written in Lua" "Backend is SDL" "Could I rewrite it to run under LÖVE2D?" " Yes! In a weekend!"] [h "Custom Editors" ** "Retro game programming is just the process of writing a series of barely-usable custom paint programs." {:action #(files.reload :neuttower/game.json)} "14x16 tile editor" (openview #(TileEditView)) "Font editor" (openview #(FontEditView)) "Portrait editor" (openview #(PortraitEditView)) "Map editor" (openview #(MapEditView)) "Full-screen bitmap editor" (openview #(ScreenEditView :neuttower/title.screen) {:pause-after true}) (openfile :presentation/slides.fnl {:split :right :line 133}) "Presentation viewer!?"] [h "Editing Editors With My Editor" ** "Lua provides a very dynamic environment that allows me tremendous flexibility" (openview #(MapEditView)) (openfile :editor/mapedit.fnl {:split :right :line 235}) "Downside:" {:image "presentation/pics/bsod.png" :justify :center :pause-after true}] [h "Step 4: Emulator Integration" ** "Directly inspired by Dagen Brock's 2016 KFest talk on GSPlus" "Ended up using MAME - has a full Lua plugin system" "What if I could poke my program directly into an emulator's memory?" "What if I could preserve the current runtime state but rewrite the code?" "... even if the data has moved?" "What if I could interactively try out new code while my game was running?"] [h "Step 5: Running on Hardware" ** "I have a IIgs with a serial cable - I can poke bytes in directly from the monitor" "]IN#2\n]PR#2\n]CALL-151" "Easy to send bytes faster than the monitor can process them"] [h "Audio" ** "I have a II+ with a cassette port" "LÖVE2D is a game engine - my editor can generate audio and play it back immediately" "Need to generate a BASIC program to bootstrap my machine code" (openfile :asm/tape.fnl {:split :right}) " [:basic [10 :call :2061]]" "Future work: Apple Game Server fastloader"] [(bgimg "presentation/pics/beneath-apple-prodos.png") h "ProDOS" ** "Disk image is a must-have for distribution" (openfile :asm/prodos.fnl {:split :right :line 132}) "Of course I wrote my own disk image generation code!" "Start with a blank ProDOS disk and add to it" "Fun bugs!" "* Accidentally implemented undelete instead of inserting new files at first" "* Read the free space bitmap backwards and overwrote the OS" "* Tried to name a volume starting with a number"] [(bgimg "presentation/pics/boot-tower.jpeg") {:action #(files.reload :neuttower/game.json)} h "Neu] [ower" ** "A small puzzle adventure game!" "--== D E M O ==--"] [(bgimg "presentation/pics/bitsy.png") {:action #(files.reload :bitsy/game.json)} h "8-Bitsy" ** "Bitsy is a popular free, accessible, web-based game-making tool" "Spring Lisp Game Jam - 10 days to hack" "Could I make my tools a little less... programmer-y?" (openview #(MapEditView) {:pause-after true})] [h "Thanks!" (openfile :neuttower/level6.fnl {:split :right :line 153}) ** "Questions?"] ])