Browser CLI
Since my last devlog, I implemented a CLI for Hadronize that works across Node, Deno, Bun, and browsers. You can try it out by visiting https://ethmarks.github.io/hadronize/cli or running the following commands (you only have to run one of the Node/Deno/Bun commands):
# Clone the repo
git clone https://github.com/ethmarks/hadronize.git
cd hadronize
# Run with Node (via npm or pnpm)
npm install
npm run cli
# Run with Deno
deno task cli
# Run with Bun
bun install
bun run cli
Link to the diff between last devlog and this one
Abstraction
In case you’re not familiar with the JS ecosystem, a cross-runtime CLI like this is not normal. My CLI uses styled text, but the different runtimes have different ways of logging styled text. Node, Deno, and Bun all use ANSI escape characters, but browsers use %c placeholders (Deno also supports %c because Deno is awesome, but none of the others do). Node uses the readline API to get user input, but Deno and Bun both use prompt(), and browsers don’t even have a way to get user input without clever tricks (as you’ll see later).
My solution to the styled text problem was to create a custom micro-library which I call styledLog.ts (or sl for short) which invents an easy-to-use syntax for rich-text logging called slChunk, then automatically detects if it’s running in a browser and renders slChunks with either ANSI escapes or %c placeholders accordingly.
Cross-runtime user input
My solution to the user input problem was a little less elegant, but in my opinion it’s much cleverer. Node, Deno, and Bun were the low-hanging fruit. All I had to do was create an abstracted NbrInputFunc() function that automatically detects which runtime is running the code, then switches out the method it internally uses to fetch input accordingly. Easy.
But implementing a CLI in the browser was a lot more tricky. I could have just used the window.prompt() method, but that opens a disruptive pop-up window that looks terrible. Instead, my code dynamically adds custom properties to the window object, then halts the script execution via a Promise<void>. Each custom property is named after a player name and has an anonymous getter function. When you type a player’s name (e.g. alice) in the console, it automatically evaluates window.alice, which triggers the getter. The getter sets the userInput variable then resolves the frozen Promise to resume execution.
I hope that made sense. It’s a really hacky solution, but it does work, and it provides an elegant input syntax that resembles that of terminal CLIs. My first idea (which is still used as the fallback) was to just define a window.turn() function, so you would enter your input with turn("alice"). The current solution is much more elegant, in my opinion, because the user doesn’t need to type parenthesis or quotes.
Here’s a link to the code if you want to check it out for yourself.
Beginnings of the UI
I also started working on the UI the tiniest bit. Not anything that’ll make it into the final version, mind you, but I added a page that demos the Hadronize CLI. It seemed wrong to make the demo page blank and not include instructions, so I wrote some. It seemed wrong to use unstyled bare HTML, so I added holidaycss.
Next Steps
I think that there’s still some work I need to do in the game logic and CLI before I start working on the UI. The CLI demo currently doesn’t have any way to customize the game settings (e.g. how many players there are and what deterministic seed to use), so I’ll probably work on that next.
Thanks for reading!
Comments 0
No comments yet. Be the first!
Sign in to join the conversation.