85
edits
No edit summary |
No edit summary |
||
Line 3: | Line 3: | ||
<blockquote> | <blockquote> | ||
Helpful? Olaf is clearly a master of understatement. Try beautiful. [...] It contains syntactic sweeteners for React classes, methods, properties, and most important, DOM tree generation. Olaf had thereby done away with React's grossest feature, the JSX minilanguage, in about 10 lines of code. [...] It will not only educate but inspire you. | Helpful? Olaf is clearly a master of understatement. Try beautiful. [...] It contains syntactic sweeteners for React classes, methods, properties, and most important, DOM tree generation. Olaf had thereby done away with React's grossest feature, the JSX minilanguage, in about 10 lines of code. [...] It will not only educate but inspire you. | ||
</blockquote> | </blockquote> | ||
Line 17: | Line 13: | ||
= The Stack = | = The Stack = | ||
I decided from the start that I wanted Lisp-style interactive development. Parenscript itself is (basically) just a function that takes code and spits out a JS string, so there's not really a development environment component to it. I found [https://github.com/johnmastro/trident-mode.el trident-mode] which is a pretty | I decided from the start that I wanted Lisp-style interactive development. Parenscript itself is (basically) just a function that takes code and spits out a JS string, so there's not really a development environment component to it. I found [https://github.com/johnmastro/trident-mode.el trident-mode] which is a pretty barebones tool that makes the HTML page long-poll Emacs for JS strings and then evals them. I wanted to do something similar to React's "fast refresh" which swaps out components while preserving state. Basically the frontend equivalent of [https://nickfa.ro/w/images/8qgw3f.jpg "C-M-x'ing that sexp" ] and having your running program automatically update. Every time I'm in a hot-reloading environment like this I like to pretend I'm Andy Gavin [http://www.codersnotes.com/notes/disassembling-jak/ with my own PS2 toolchain]. | ||
I found | Since I want to side-step the modern frontend stack a bit, I found Preact as an alternative implementation of React in 3kb, and it [https://dev.to/jovidecroock/prefresh-fast-refresh-for-preact-26kg has a blog post] describing how to hook into its hot updating system. I quickly whipped up a macro which implements a JSX-like syntax, and a <code>defcomponent</code> macro for creating React components, so I can write something like this: | ||
<syntaxhighlight lang="lisp">(defcomponent -ui-button () (props) | <syntaxhighlight lang="lisp">(defcomponent -ui-button () (props) | ||
Line 70: | Line 66: | ||
I ended up relying on packing information into the HTML <code>id</code> property, since it provides a convenient way to point to a specific card ("1_heart" is the ace of hearts) and perform some side-effectful action on it without getting into ref hell. I'm not sure if this is the best practice but it seems to work ok. | I ended up relying on packing information into the HTML <code>id</code> property, since it provides a convenient way to point to a specific card ("1_heart" is the ace of hearts) and perform some side-effectful action on it without getting into ref hell. I'm not sure if this is the best practice but it seems to work ok. | ||
As far as implementing the game itself, the logic is all pretty straightforward to write, although I had to crack open the [https://copy.sh/v86/?profile=windows95 Windows 95 VM] a couple times to compare the behavior to the classic FreeCell. My thing even uses the same random number generator as the Windows one ([https://rosettacode.org/wiki/Deal_cards_for_FreeCell#Common_Lisp courtsey of Rosetta Code]), so the game numbers are the same between the two. | |||
My thing even uses the same random number generator as the Windows one ([https://rosettacode.org/wiki/Deal_cards_for_FreeCell#Common_Lisp courtsey of Rosetta Code] ), so the game numbers are the same between the two. | |||
Once the game was working properly, I wanted to include a win screen kind of like the original Solitaire which had the bouncing trailing cards. Since I had GSAP at my disposal to do all sorts of transforms, I made it so the cards get into big sine waves on the top and bottom of the page, and a "You win!" pops up in the middle. I think it looks pretty cool, and only took a few lines of code. | Once the game was working properly, I wanted to include a win screen kind of like the original Solitaire which had the bouncing trailing cards. Since I had GSAP at my disposal to do all sorts of transforms, I made it so the cards get into big sine waves on the top and bottom of the page, and a "You win!" pops up in the middle. I think it looks pretty cool, and only took a few lines of code. | ||
Line 81: | Line 73: | ||
I will say this about React, the restrictions it puts on you are annoying, but once you've wired up the animations and stuff properly, everything just kind of magically works. I added an undo/redo feature which keeps track of the previous state whenever you perform a <code>set-board-state</code> and the cards perfectly float into place and the columns perfectly expand/contract according to my rules... very nice. You can even undo out of the win screen and it all reverts back properly, and back in again if you redo. | I will say this about React, the restrictions it puts on you are annoying, but once you've wired up the animations and stuff properly, everything just kind of magically works. I added an undo/redo feature which keeps track of the previous state whenever you perform a <code>set-board-state</code> and the cards perfectly float into place and the columns perfectly expand/contract according to my rules... very nice. You can even undo out of the win screen and it all reverts back properly, and back in again if you redo. | ||
I grabbed [https://www.me.uk/cards/ Adrian Kennard's card SVGs] which are public domain, which is very generous of him. I want to maybe touch up the number and letter fonts on the cards a bit myself but I'll do it later. | |||
The last thing I had to do was plug my Parenscript stuff into the build system so it could "link" it all together in a minified package. It was pretty trivial to write a [https://github.com/SuperDisk/cardgames/blob/master/frontend/vite-parenscript.js Vite plugin to process importing `.paren` files], and then at that point I could simply <code>netlify build; netlify deploy --prod</code> to push the whole thing up to Netlify. | |||
It worked, but there was occasionally a small lag of a second or two before the code started running when nothing appeared; from what I understand, the usual way to solve that these days is by doing server-side React rendering and then re-hydrating it on the frontend once your code starts executing. | |||
I found a link to [https://github.com/vitejs/vite-plugin-vue/blob/main/playground/ssr-vue/prerender.js this] in the Vite docs, which is a pretty rudimentary way to do pre-rendering using Vue-- it basically renders the Vue stuff to a string, and then does a find-and-replace on a special comment in HTML sources after the build finished. Seems pretty hacky to me, but it seems to be the way things are done so I whipped up [https://github.com/SuperDisk/cardgames/blob/master/frontend/prerender-hack.js my own version] which does something similar, albeit in a more grotesquely ugly way. | |||
And that's pretty much it! Reactive layout, smooth animation, incremental Lisp-style development, and pre-rendering using just Parenscript, GSAP and Preact, all about 50kb gzipped. | |||
= The Good = | = The Good = |