(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 link (require :link)) (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 ?text] (lume.merge style {:pause-after true} (if ?text {:text ?text :style false}))) (fn np [style ?text] (lume.merge style {:pause-after false} (if ?text {:text ?text :style 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)) (fn boot-game [] (let [p (util.reload :game)] (util.in-coro (fn [] (link:switch :mame) (link.machine:run) (util.waitfor #(link.machine:connected?)) (p:upload link.machine) (link.machine:launch p))) nil)) (fn vm-eval [...] (let [prg (require :game) overlay (prg.vm:gen-eval-prg [:vm ...])] (link.machine:overlay overlay) nil)) (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}] [h "Honeylisp is hard to explain" ** "It is an experimental programming environment designed to enable a productive Apple // game development workflow" "https://fennel-lang.org/"] [(bgimg "presentation/pics/pete286.jpeg") h "Some Background" ** "2019: Built a 16-bit MS-DOS game engine, using only retro hardware and software."] [(bgimg "presentation/pics/ggj2020.jpeg") h "Neut Tower" ** "2020: Created Neut Tower as part of two game jams. * Global Game Jam - One weekend - Feb 2020 - First two rooms * MS-DOS Game Jam - 1.5 months - April 2020 - 'Shareware Episode 1'"] [(bgimg "presentation/pics/boot-tower.jpeg") {:action #(files.reload :neuttower/game.json)} h "Neu] [ower" ** "A small puzzle adventure game!" "Magic Trick #1" {:action boot-game} "--== D E M O ==--"] [h "How It's Made" ** "Is the Apple ][ running Lisp?" " * Not really?" "Is the code written in Lisp?" " * Sort of!" "Show me some Lisp already! >:/" (openfile :neuttower/level1.fnl {:split :right :line 42}) " * OK!"] [h "What is this unholy abomination?" ** "Lisp and Forth?!" {:image "presentation/pics/thinkhard.png" :justify :center} "Not super keen on writing a complicated compiler" " * \"Direct threaded\" inner interpreter" "Forth allows efficient, composable, interactive code"] [h "Why use Lisp to compile Forth?" ** "\"Immediate words\" can be Fennel functions that generate code!" "Program can be compiled into a rich data structure" (openfile :neuttower/level1.fnl {:split :right :line 59}) "Magic Trick #2" (np **) "Magic Trick #3" {:button #(vm-eval :mixed) :text ":mixed"} {:button #(vm-eval :hires) :text ":hires"} {:button #(vm-eval 1 2 :+ :.) :text "1 2 :+ :."} {:button #(vm-eval :earthquake) :text ":earthquake"} {:pause-after true}] [h "Explain this voodoo!" ** "Directly inspired by Dagen Brock's 2016 KFest talk on GSPlus" "Ended up using MAME - Lua plugin system exposes EVERYTHING" "Use Jeejah nREPL server library with custom nREPL client" "1. What if I could poke my program directly into an emulator's memory?" "2. What if I could preserve the current runtime state but rewrite the code?" " ... even if the data has moved?" "3. What if I could interactively try out new code while my game was running?"] [h "Digging Deeper: 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]]"] [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 "The Tools" ** {:image "presentation/pics/retro-game-dev-quote.png" :justify :center :pause-after true} {: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" ** "lite is a small, highly-extensible text editor written in Lua" "Lua provides a very dynamic environment" (openview #(MapEditView)) (openfile :editor/mapedit.fnl {:split :right :line 235}) "Downside:" {:image "presentation/pics/bsod.png" :justify :center :pause-after true}] [(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" {:action boot-game :pause-after true} (openview #(MapEditView) {:pause-after true})] [(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?" (np **) {:topPadding 128} "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}] ]) ; [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"]