Hi, I'm Zach and today I'll be giving
a presentation on asm-blox,
a programming game inspired by WebAssembly.
So programming games came into prominence
about a decade ago and are loved for providing
interesting programming challenges
without all the messiness of real world programming.
I wanted to make a programming game
and I decided to base it off of TIS-100,
having a pretty basic UI.
It seemed pretty doable in Emacs.
TIS 100 is a programming game
where you write a fictional assembly language
into a grid of cells which can each
communicate with one another,
you're tasked with solving
fairly simple CS 101 like problems.
To mix things up a bit I decided to base
the language of asm-blox off of
WebAssembly, which is stack based,
as opposed to TIS-100 which is registered based.
Here you can see the same program
written in the game TIS-100,
what it looks like in asm-blox,
and the original WebAssembly that it's based off of.
With that said, let's get into a demo.
This is the game board.
It's a 4 by 3 grid.
Each cell has a stack of size 4.
First off, I'll show some of the stack editing commands.
We can add a value with the const function.
Here we're adding two values to this stack
to get added, and eventually the stack gets overflowed.
We can fix that as follows with the clear command,
so that clears the stack.
We can duplicate values on the stack.
This duplicates the item at the bottom of the stack.
10 gets put on, 20 gets put on,
then 10 will get duplicated
and put on the top of the stack.
We can increment. For example, this increments
the second to bottom, the second to bottom
from the stack.
So 10, 20, increment that, clear.
That's basic stack operations.
Next up, we have numeric commands.
For example, here, if we add "add",
it pops two values off the stack,
adds them, and pushes the result on.
Another way we can write this is as follows.
We can have the add here
and then nest the two constants,
and then this does the same thing.
First, the inner constant operations run,
and then the outer add operation runs.
We can nest as deeply as we want.
There's also subtraction, multiplication, and whatnot.
Next up are Boolean operations.
Zero counts as true.
Anything else--sorry, zero counts as false.
Anything else is true.
For example, this would give us false and true,
so that result should be false.
Zero gets put on the stack,
one gets put on, and then the "and" operation.
So there's also or, not,
and various numerical comparison operations
like greater than and less than.
Next up are the port operations.
We can send values to other cells as follows.
Here we create a value
and then send it right.
Let's run this.
The 10 goes on the stack,
and then it gets sent to the right.
Here it's waiting for this cell to pick it up.
It can pick it up just as follows.
So left... and then why don't we have it
drop that value after it gets it.
So the 10 gets sent to the right.
This one picks it up and drops it.
Lastly, we have control flow,
which is a bit tricky,
but with this visual,
it helps explain it.
There are two block constructs, "block" and "loop",
and there's two jumping constructs, "br" and "brif".
So if "loop" is jumped to,
the control flow goes to the beginning,
the top of the loop.
If a block is jumped to,
it goes to the end of the block,
and these various blocks
are identified by their level of nestedness.
From the point of view of this jump statement,
this "br" statement, this is block level 0,
this is 1, this is 2.
So here, "br 1" would be referring to this loop.
What this [br 1] would do is,
it would jump to this loop right here.
If we were to do this [br 2], what this would do is,
this would jump past this block right here.
So as another example, this right here,
this is a loop that generates increasing numbers.
Let's see. Next up, we have modules.
This is an example of a stack module.
In addition to stack, there's also heaps.
What this does is it allows us to create
an extra stack that we can push and pop items onto.
This one can have as large size as we need.
Here it has a size of 20.
It's taking values from up
and exposing those values on the left.
This loop right here, it generates numbers,
and it's putting them onto the stack.
We can see here that those numbers
are being exposed to this cell right here.
It's just taking values, and eventually,
it's going to overflow and cause an error.
That finishes the basic commands.
Why don't we try solving this puzzle.
The puzzle description is right here.
We want to read a value from I.
Send 1 to G if I is greater than 0.
Send 1 to E if it's equal to 0.
Send 1 to L if it's less than 0.
And then all the other ones, we send 0 to.
First things first, let's send the value we get
from the input down as follows.
Let's send that value right.
You get from up.
Okay. So next, we're getting a value on the left.
Now we want to compare if this number is greater than 0.
If it's greater than 0, we send 1 to G.
Let's perform the greater than operation
on that item we just got, and we're comparing it to 0.
Now that result, we're going to send down,
and we're going to send this original value
we got from here to the right.
Here, we do a similar step.
We get the value from the left,
but this time, we have to do an equal operation.
Is that number we got equal to 0?
We send that result down,
and then send this number to the right.
Lastly, we get this number from the left.
Here, we need to compare if it's less than 0.
We send that result down,
and now lastly, we drop that remaining value.
Okay, let's--oh, and then lastly,
we need to send down the value we get up.
Send down, up, send down, up.
Okay, so let's try running this.
Let's see. We notice that
the numbers are coming in from I.
They're going through our various conditions
and should be sending all the correct values.
It looks like we're not getting any errors so far.
Let's speed this up.
That completes the puzzle.
Now let's get into some of the implementation details.
The first thing is the game loop.
The game loop is... So this is actually extremely simple.
All the state for the entire game
is stored in just a few variables.
There's one variable storing
the text of each cell as a vector of strings.
There's a single function
that renders the entire game, the entire board.
There's a single function that would render
this entire screen based off of the state,
and then the game waits for you to press a key.
The key usually, depending on what action you perform,
updates the state and causes a re-render.
It's an extremely simple game loop,
but it makes implementing it pretty easy.
To demonstrate how this game loop works,
I have a simple demo prepared.
This is a game of tic-tac-toe.
Let me show this real fast.
It's an extremely simple implementation,
but it follows the same principles
that I used in asm-blox.
First, we have the state defined in variables.
Here we have two pieces of state.
We have which player's turn it is
and the state of the game board.
The player turn can be nil if it's empty,
the string "x" or the string "o".
Then the game board is a list of nine board elements.
So that's the state.
Then we have a helper function.
You can go into the details,
but it just returns true
if the board has a winning player.
Part two is the rendering function.
Only based off of the game state,
we have a function that erases the buffer
and draws this from scratch.
That's this part right here.
Lastly, we have the action.
We have one action which is bound to RET,
and it places a player token.
Once it places a player token,
it rerenders the board,
and all the rerendering is handled by this function.
Then we have just creating of the mode
and initialization function.
With these three steps
it clearly separates out all of the state,
the rendering, and the actions,
and it makes implementing it very simple.
One trick that's used here and that I use
in my asm-blox game is that
when I render the board,
I propertize the text to contain extra information.
For example, here, each cell has
a tic-tac-toe index to indicate which number cell it is.
This has index 0, 1, 2, all the way up to 8.
That way, for placing, the only thing it has to do
is just look at its position
based off of the text property.
It makes implementation extremely simple.
Next up, we have the implementation of the code cells.
If you notice, here it's kind of weird
how it's like a buffer, but each cell kind of acts
like its own buffer, and it has its own limits.
All of the Emacs editing--
well, some of the Emacs editing commands kind of work,
like beginning-of-line, end-of-line, end-of-buffer.
How is that done?
Well, it's all just a trick, actually.
Each cell has text properties of which line it's at
and its cell coordinates.
Whenever a key is pressed for editing, moving lines--
there's even kind of more complicated things
like switching cells around--
so all of that,
it knows which position it's in,
it knows what cell it's in,
and then it copies the text of the cell,
because remember, the contents of the cell
are stored in internal state.
It copies that cell contents into a temporary buffer.
It then moves the point to whichever line it was
in the game board.
It performs the action.
It makes sure that the resulting text isn't
longer than the cell width or the cell height.
If everything checks out,
it updates the state and calls a re-render.
So there's nothing going on in here
that's, like, actually inserting a letter A.
It's all updating the state and causing a re-render.
So this makes things like certain
internal Emacs editing constructs
pretty hard to use, like undoing.
Normally the undoing construct
works off the contents of the buffer.
But if your buffer is actually just
a reflection of the internal state,
then how does undoing work?
Well, it pretty much is kind of a hack.
I mean, undoing is here,
but it's pretty much redone
in a not so configurable, not so modifiable way.
Pretty much everything is like that,
from these parentheses highlighting...
Normally, parentheses highlighting
would be kind of weird,
with cross-line parentheses and everything.
All of that had to be redone.
Another point about how this is implemented
is the assembly text to executable code.
If you're familiar with WebAssembly
you might have encountered a tool wat-wasm.
It basically converts the WebAssembly text format
to byte code.
And what I do here... It goes through a similar process.
Normally, when you're writing this text format,
you can nest things as deeply as you want.
Basically, what happens is it flattens out everything.
It kind of knows the order
that all these things are going to get executed,
and then it puts it into one single line
that it can just run through and execute.
The same thing for the loops and blocks.
It internally generates labels and jump statements.
So that concludes this presentation.
Thank you for listening,
and I hope you enjoy the rest of the conference.