WEBVTT captioned by sachac 00:00:00.000 --> 00:00:03.800 Hi, I'm Zach and today I'll be giving 00:00:03.800 --> 00:00:05.320 a presentation on asm-blox, 00:00:05.320 --> 00:00:08.960 a programming game inspired by WebAssembly. 00:00:08.960 --> 00:00:10.840 So programming games came into prominence 00:00:10.840 --> 00:00:13.160 about a decade ago and are loved for providing 00:00:13.160 --> 00:00:14.760 interesting programming challenges 00:00:14.760 --> 00:00:17.160 without all the messiness of real world programming. 00:00:17.160 --> 00:00:19.960 I wanted to make a programming game 00:00:19.960 --> 00:00:24.880 and I decided to base it off of TIS-100, 00:00:24.880 --> 00:00:28.240 having a pretty basic UI. 00:00:28.240 --> 00:00:30.680 It seemed pretty doable in Emacs. 00:00:30.680 --> 00:00:33.160 TIS 100 is a programming game 00:00:33.160 --> 00:00:35.760 where you write a fictional assembly language 00:00:35.760 --> 00:00:37.280 into a grid of cells which can each 00:00:37.280 --> 00:00:39.480 communicate with one another, 00:00:39.480 --> 00:00:41.200 you're tasked with solving 00:00:41.200 --> 00:00:44.960 fairly simple CS 101 like problems. 00:00:44.960 --> 00:00:48.440 To mix things up a bit I decided to base 00:00:48.440 --> 00:00:49.800 the language of asm-blox off of 00:00:49.800 --> 00:00:52.520 WebAssembly, which is stack based, 00:00:52.520 --> 00:00:55.360 as opposed to TIS-100 which is registered based. 00:00:55.360 --> 00:00:59.200 Here you can see the same program 00:00:59.200 --> 00:01:01.680 written in the game TIS-100, 00:01:01.680 --> 00:01:03.960 what it looks like in asm-blox, 00:01:03.960 --> 00:01:08.040 and the original WebAssembly that it's based off of. 00:01:08.040 --> 00:01:10.640 With that said, let's get into a demo. 00:01:10.640 --> 00:01:12.240 This is the game board. 00:01:12.240 --> 00:01:14.120 It's a 4 by 3 grid. 00:01:14.120 --> 00:01:16.840 Each cell has a stack of size 4. 00:01:16.840 --> 00:01:20.280 First off, I'll show some of the stack editing commands. 00:01:20.280 --> 00:01:23.760 We can add a value with the const function. 00:01:23.760 --> 00:01:27.480 Here we're adding two values to this stack 00:01:27.480 --> 00:01:33.400 to get added, and eventually the stack gets overflowed. 00:01:33.400 --> 00:01:37.360 We can fix that as follows with the clear command, 00:01:37.360 --> 00:01:40.720 so that clears the stack. 00:01:40.720 --> 00:01:43.200 We can duplicate values on the stack. 00:01:43.200 --> 00:01:45.600 This duplicates the item at the bottom of the stack. 00:01:45.600 --> 00:01:48.880 10 gets put on, 20 gets put on, 00:01:48.880 --> 00:01:50.200 then 10 will get duplicated 00:01:50.200 --> 00:01:52.680 and put on the top of the stack. 00:01:52.680 --> 00:01:55.920 We can increment. For example, this increments 00:01:55.920 --> 00:01:58.760 the second to bottom, the second to bottom 00:01:58.760 --> 00:01:59.920 from the stack. 00:01:59.920 --> 00:02:04.400 So 10, 20, increment that, clear. 00:02:04.400 --> 00:02:07.640 That's basic stack operations. 00:02:07.640 --> 00:02:11.000 Next up, we have numeric commands. 00:02:11.000 --> 00:02:12.560 For example, here, if we add "add", 00:02:12.560 --> 00:02:14.680 it pops two values off the stack, 00:02:14.680 --> 00:02:17.080 adds them, and pushes the result on. 00:02:17.080 --> 00:02:20.680 Another way we can write this is as follows. 00:02:20.680 --> 00:02:22.480 We can have the add here 00:02:22.480 --> 00:02:26.400 and then nest the two constants, 00:02:26.400 --> 00:02:28.520 and then this does the same thing. 00:02:28.520 --> 00:02:31.720 First, the inner constant operations run, 00:02:31.720 --> 00:02:35.520 and then the outer add operation runs. 00:02:35.520 --> 00:02:40.280 We can nest as deeply as we want. 00:02:40.280 --> 00:02:44.680 There's also subtraction, multiplication, and whatnot. 00:02:44.680 --> 00:02:46.480 Next up are Boolean operations. 00:02:46.480 --> 00:02:49.080 Zero counts as true. 00:02:49.080 --> 00:02:51.720 Anything else--sorry, zero counts as false. 00:02:51.720 --> 00:02:52.760 Anything else is true. 00:02:52.760 --> 00:03:01.840 For example, this would give us false and true, 00:03:01.840 --> 00:03:04.040 so that result should be false. 00:03:04.040 --> 00:03:06.120 Zero gets put on the stack, 00:03:06.120 --> 00:03:08.160 one gets put on, and then the "and" operation. 00:03:08.160 --> 00:03:12.840 So there's also or, not, 00:03:12.840 --> 00:03:17.760 and various numerical comparison operations 00:03:17.760 --> 00:03:21.400 like greater than and less than. 00:03:21.400 --> 00:03:22.880 Next up are the port operations. 00:03:22.880 --> 00:03:27.320 We can send values to other cells as follows. 00:03:27.320 --> 00:03:29.600 Here we create a value 00:03:29.600 --> 00:03:33.640 and then send it right. 00:03:33.640 --> 00:03:35.040 Let's run this. 00:03:35.040 --> 00:03:37.480 The 10 goes on the stack, 00:03:37.480 --> 00:03:38.480 and then it gets sent to the right. 00:03:38.480 --> 00:03:41.360 Here it's waiting for this cell to pick it up. 00:03:41.360 --> 00:03:44.360 It can pick it up just as follows. 00:03:44.360 --> 00:03:47.480 So left... and then why don't we have it 00:03:47.480 --> 00:03:49.520 drop that value after it gets it. 00:03:49.520 --> 00:03:53.920 So the 10 gets sent to the right. 00:03:53.920 --> 00:04:00.240 This one picks it up and drops it. 00:04:00.240 --> 00:04:03.200 Lastly, we have control flow, 00:04:03.200 --> 00:04:04.280 which is a bit tricky, 00:04:04.280 --> 00:04:06.880 but with this visual, 00:04:06.880 --> 00:04:08.440 it helps explain it. 00:04:08.440 --> 00:04:12.280 There are two block constructs, "block" and "loop", 00:04:12.280 --> 00:04:16.880 and there's two jumping constructs, "br" and "brif". 00:04:16.880 --> 00:04:23.120 So if "loop" is jumped to, 00:04:23.120 --> 00:04:25.360 the control flow goes to the beginning, 00:04:25.360 --> 00:04:26.520 the top of the loop. 00:04:26.520 --> 00:04:28.640 If a block is jumped to, 00:04:28.640 --> 00:04:31.520 it goes to the end of the block, 00:04:31.520 --> 00:04:33.640 and these various blocks 00:04:33.640 --> 00:04:36.520 are identified by their level of nestedness. 00:04:36.520 --> 00:04:40.640 From the point of view of this jump statement, 00:04:40.640 --> 00:04:45.160 this "br" statement, this is block level 0, 00:04:45.160 --> 00:04:46.440 this is 1, this is 2. 00:04:46.440 --> 00:04:49.560 So here, "br 1" would be referring to this loop. 00:04:49.560 --> 00:04:51.080 What this [br 1] would do is, 00:04:51.080 --> 00:04:54.000 it would jump to this loop right here. 00:04:54.000 --> 00:04:57.360 If we were to do this [br 2], what this would do is, 00:04:57.360 --> 00:05:02.680 this would jump past this block right here. 00:05:02.680 --> 00:05:09.880 So as another example, this right here, 00:05:09.880 --> 00:05:15.720 this is a loop that generates increasing numbers. 00:05:15.720 --> 00:05:22.640 Let's see. Next up, we have modules. 00:05:22.640 --> 00:05:26.280 This is an example of a stack module. 00:05:26.280 --> 00:05:28.760 In addition to stack, there's also heaps. 00:05:28.760 --> 00:05:34.560 What this does is it allows us to create 00:05:34.560 --> 00:05:38.080 an extra stack that we can push and pop items onto. 00:05:38.080 --> 00:05:41.240 This one can have as large size as we need. 00:05:41.240 --> 00:05:43.800 Here it has a size of 20. 00:05:43.800 --> 00:05:46.400 It's taking values from up 00:05:46.400 --> 00:05:51.080 and exposing those values on the left. 00:05:51.080 --> 00:05:57.080 This loop right here, it generates numbers, 00:05:57.080 --> 00:05:59.160 and it's putting them onto the stack. 00:05:59.160 --> 00:06:00.920 We can see here that those numbers 00:06:00.920 --> 00:06:03.200 are being exposed to this cell right here. 00:06:03.200 --> 00:06:07.040 It's just taking values, and eventually, 00:06:07.040 --> 00:06:11.200 it's going to overflow and cause an error. 00:06:11.200 --> 00:06:14.480 That finishes the basic commands. 00:06:14.480 --> 00:06:16.480 Why don't we try solving this puzzle. 00:06:16.480 --> 00:06:21.320 The puzzle description is right here. 00:06:21.320 --> 00:06:23.280 We want to read a value from I. 00:06:23.280 --> 00:06:28.480 Send 1 to G if I is greater than 0. 00:06:28.480 --> 00:06:30.800 Send 1 to E if it's equal to 0. 00:06:30.800 --> 00:06:32.440 Send 1 to L if it's less than 0. 00:06:32.440 --> 00:06:35.360 And then all the other ones, we send 0 to. 00:06:35.360 --> 00:06:40.920 First things first, let's send the value we get 00:06:40.920 --> 00:06:44.400 from the input down as follows. 00:06:44.400 --> 00:06:49.680 Let's send that value right. 00:06:49.680 --> 00:06:51.240 You get from up. 00:06:51.240 --> 00:06:54.320 Okay. So next, we're getting a value on the left. 00:06:54.320 --> 00:06:58.040 Now we want to compare if this number is greater than 0. 00:06:58.040 --> 00:06:59.800 If it's greater than 0, we send 1 to G. 00:06:59.800 --> 00:07:03.280 Let's perform the greater than operation 00:07:03.280 --> 00:07:08.080 on that item we just got, and we're comparing it to 0. 00:07:08.080 --> 00:07:11.680 Now that result, we're going to send down, 00:07:11.680 --> 00:07:13.880 and we're going to send this original value 00:07:13.880 --> 00:07:16.880 we got from here to the right. 00:07:16.880 --> 00:07:19.000 Here, we do a similar step. 00:07:19.000 --> 00:07:20.240 We get the value from the left, 00:07:20.240 --> 00:07:22.920 but this time, we have to do an equal operation. 00:07:22.920 --> 00:07:25.760 Is that number we got equal to 0? 00:07:25.760 --> 00:07:28.960 We send that result down, 00:07:28.960 --> 00:07:32.880 and then send this number to the right. 00:07:32.880 --> 00:07:38.040 Lastly, we get this number from the left. 00:07:38.040 --> 00:07:42.400 Here, we need to compare if it's less than 0. 00:07:42.400 --> 00:07:45.640 We send that result down, 00:07:45.640 --> 00:07:50.280 and now lastly, we drop that remaining value. 00:07:50.280 --> 00:07:53.080 Okay, let's--oh, and then lastly, 00:07:53.080 --> 00:07:56.040 we need to send down the value we get up. 00:07:56.040 --> 00:08:02.560 Send down, up, send down, up. 00:08:02.560 --> 00:08:04.760 Okay, so let's try running this. 00:08:04.760 --> 00:08:08.920 Let's see. We notice that 00:08:08.920 --> 00:08:10.360 the numbers are coming in from I. 00:08:10.360 --> 00:08:14.200 They're going through our various conditions 00:08:14.200 --> 00:08:18.160 and should be sending all the correct values. 00:08:18.160 --> 00:08:23.560 It looks like we're not getting any errors so far. 00:08:23.560 --> 00:08:26.680 Let's speed this up. 00:08:26.680 --> 00:08:33.040 That completes the puzzle. 00:08:33.040 --> 00:08:42.000 Now let's get into some of the implementation details. 00:08:42.000 --> 00:08:46.320 The first thing is the game loop. 00:08:46.320 --> 00:08:50.560 The game loop is... So this is actually extremely simple. 00:08:50.560 --> 00:08:52.320 All the state for the entire game 00:08:52.320 --> 00:08:54.400 is stored in just a few variables. 00:08:54.400 --> 00:08:56.480 There's one variable storing 00:08:56.480 --> 00:09:01.400 the text of each cell as a vector of strings. 00:09:01.400 --> 00:09:06.280 There's a single function 00:09:06.280 --> 00:09:09.080 that renders the entire game, the entire board. 00:09:09.080 --> 00:09:11.120 There's a single function that would render 00:09:11.120 --> 00:09:13.920 this entire screen based off of the state, 00:09:13.920 --> 00:09:19.240 and then the game waits for you to press a key. 00:09:19.240 --> 00:09:24.120 The key usually, depending on what action you perform, 00:09:24.120 --> 00:09:27.040 updates the state and causes a re-render. 00:09:27.040 --> 00:09:29.360 It's an extremely simple game loop, 00:09:29.360 --> 00:09:32.800 but it makes implementing it pretty easy. 00:09:32.800 --> 00:09:35.200 To demonstrate how this game loop works, 00:09:35.200 --> 00:09:38.400 I have a simple demo prepared. 00:09:38.400 --> 00:09:41.880 This is a game of tic-tac-toe. 00:09:41.880 --> 00:09:44.800 Let me show this real fast. 00:09:44.800 --> 00:09:49.200 It's an extremely simple implementation, 00:09:49.200 --> 00:09:51.465 but it follows the same principles 00:09:51.466 --> 00:09:53.600 that I used in asm-blox. 00:09:53.600 --> 00:09:57.680 First, we have the state defined in variables. 00:09:57.680 --> 00:09:59.560 Here we have two pieces of state. 00:09:59.560 --> 00:10:01.600 We have which player's turn it is 00:10:01.600 --> 00:10:03.120 and the state of the game board. 00:10:03.120 --> 00:10:06.640 The player turn can be nil if it's empty, 00:10:06.640 --> 00:10:08.760 the string "x" or the string "o". 00:10:08.760 --> 00:10:14.240 Then the game board is a list of nine board elements. 00:10:14.240 --> 00:10:16.960 So that's the state. 00:10:16.960 --> 00:10:18.120 Then we have a helper function. 00:10:18.120 --> 00:10:19.440 You can go into the details, 00:10:19.440 --> 00:10:21.000 but it just returns true 00:10:21.000 --> 00:10:25.600 if the board has a winning player. 00:10:25.600 --> 00:10:30.040 Part two is the rendering function. 00:10:30.040 --> 00:10:32.800 Only based off of the game state, 00:10:32.800 --> 00:10:36.720 we have a function that erases the buffer 00:10:36.720 --> 00:10:40.280 and draws this from scratch. 00:10:40.280 --> 00:10:45.320 That's this part right here. 00:10:45.320 --> 00:10:46.720 Lastly, we have the action. 00:10:46.720 --> 00:10:51.920 We have one action which is bound to RET, 00:10:51.920 --> 00:10:55.840 and it places a player token. 00:10:55.840 --> 00:10:59.920 Once it places a player token, 00:10:59.920 --> 00:11:03.120 it rerenders the board, 00:11:03.120 --> 00:11:06.880 and all the rerendering is handled by this function. 00:11:06.880 --> 00:11:12.480 Then we have just creating of the mode 00:11:12.480 --> 00:11:14.680 and initialization function. 00:11:14.680 --> 00:11:16.680 With these three steps 00:11:16.680 --> 00:11:20.640 it clearly separates out all of the state, 00:11:20.640 --> 00:11:22.960 the rendering, and the actions, 00:11:22.960 --> 00:11:25.880 and it makes implementing it very simple. 00:11:25.880 --> 00:11:29.640 One trick that's used here and that I use 00:11:29.640 --> 00:11:32.382 in my asm-blox game is that 00:11:32.383 --> 00:11:33.316 when I render the board, 00:11:33.317 --> 00:11:40.800 I propertize the text to contain extra information. 00:11:40.800 --> 00:11:45.080 For example, here, each cell has 00:11:45.080 --> 00:11:49.400 a tic-tac-toe index to indicate which number cell it is. 00:11:49.400 --> 00:11:53.640 This has index 0, 1, 2, all the way up to 8. 00:11:53.640 --> 00:11:58.640 That way, for placing, the only thing it has to do 00:11:58.640 --> 00:12:01.200 is just look at its position 00:12:01.200 --> 00:12:04.960 based off of the text property. 00:12:04.960 --> 00:12:07.800 It makes implementation extremely simple. 00:12:07.800 --> 00:12:14.360 Next up, we have the implementation of the code cells. 00:12:14.360 --> 00:12:16.960 If you notice, here it's kind of weird 00:12:16.960 --> 00:12:21.000 how it's like a buffer, but each cell kind of acts 00:12:21.000 --> 00:12:25.760 like its own buffer, and it has its own limits. 00:12:25.760 --> 00:12:27.600 All of the Emacs editing-- 00:12:27.600 --> 00:12:30.760 well, some of the Emacs editing commands kind of work, 00:12:30.760 --> 00:12:35.360 like beginning-of-line, end-of-line, end-of-buffer. 00:12:35.360 --> 00:12:38.240 How is that done? 00:12:38.240 --> 00:12:41.760 Well, it's all just a trick, actually. 00:12:41.760 --> 00:12:47.280 Each cell has text properties of which line it's at 00:12:47.280 --> 00:12:48.800 and its cell coordinates. 00:12:48.800 --> 00:12:54.360 Whenever a key is pressed for editing, moving lines-- 00:12:54.360 --> 00:12:58.360 there's even kind of more complicated things 00:12:58.360 --> 00:13:00.600 like switching cells around-- 00:13:00.600 --> 00:13:03.360 so all of that, 00:13:03.360 --> 00:13:05.200 it knows which position it's in, 00:13:05.200 --> 00:13:08.080 it knows what cell it's in, 00:13:08.080 --> 00:13:12.880 and then it copies the text of the cell, 00:13:12.880 --> 00:13:16.320 because remember, the contents of the cell 00:13:16.320 --> 00:13:18.360 are stored in internal state. 00:13:18.360 --> 00:13:23.000 It copies that cell contents into a temporary buffer. 00:13:23.000 --> 00:13:27.960 It then moves the point to whichever line it was 00:13:27.960 --> 00:13:31.160 in the game board. 00:13:31.160 --> 00:13:33.000 It performs the action. 00:13:33.000 --> 00:13:36.200 It makes sure that the resulting text isn't 00:13:36.200 --> 00:13:40.160 longer than the cell width or the cell height. 00:13:40.160 --> 00:13:42.040 If everything checks out, 00:13:42.040 --> 00:13:45.120 it updates the state and calls a re-render. 00:13:45.120 --> 00:13:48.440 So there's nothing going on in here 00:13:48.440 --> 00:13:51.080 that's, like, actually inserting a letter A. 00:13:51.080 --> 00:14:00.920 It's all updating the state and causing a re-render. 00:14:00.920 --> 00:14:03.640 So this makes things like certain 00:14:03.640 --> 00:14:06.480 internal Emacs editing constructs 00:14:06.480 --> 00:14:09.120 pretty hard to use, like undoing. 00:14:09.120 --> 00:14:12.200 Normally the undoing construct 00:14:12.200 --> 00:14:15.120 works off the contents of the buffer. 00:14:15.120 --> 00:14:17.840 But if your buffer is actually just 00:14:17.840 --> 00:14:20.080 a reflection of the internal state, 00:14:20.080 --> 00:14:21.440 then how does undoing work? 00:14:21.440 --> 00:14:24.880 Well, it pretty much is kind of a hack. 00:14:24.880 --> 00:14:27.040 I mean, undoing is here, 00:14:27.040 --> 00:14:32.680 but it's pretty much redone 00:14:32.680 --> 00:14:37.560 in a not so configurable, not so modifiable way. 00:14:37.560 --> 00:14:40.080 Pretty much everything is like that, 00:14:40.080 --> 00:14:42.440 from these parentheses highlighting... 00:14:42.440 --> 00:14:46.320 Normally, parentheses highlighting 00:14:46.320 --> 00:14:47.243 would be kind of weird, 00:14:47.244 --> 00:14:49.840 with cross-line parentheses and everything. 00:14:49.840 --> 00:14:52.360 All of that had to be redone. 00:14:52.360 --> 00:14:58.160 Another point about how this is implemented 00:14:58.160 --> 00:15:02.360 is the assembly text to executable code. 00:15:02.360 --> 00:15:05.800 If you're familiar with WebAssembly 00:15:05.800 --> 00:15:10.720 you might have encountered a tool wat-wasm. 00:15:10.720 --> 00:15:16.440 It basically converts the WebAssembly text format 00:15:16.440 --> 00:15:18.280 to byte code. 00:15:18.280 --> 00:15:22.440 And what I do here... It goes through a similar process. 00:15:22.440 --> 00:15:28.000 Normally, when you're writing this text format, 00:15:28.000 --> 00:15:30.360 you can nest things as deeply as you want. 00:15:30.360 --> 00:15:33.800 Basically, what happens is it flattens out everything. 00:15:33.800 --> 00:15:35.920 It kind of knows the order 00:15:35.920 --> 00:15:38.160 that all these things are going to get executed, 00:15:38.160 --> 00:15:40.680 and then it puts it into one single line 00:15:40.680 --> 00:15:44.120 that it can just run through and execute. 00:15:44.120 --> 00:15:48.360 The same thing for the loops and blocks. 00:15:48.360 --> 00:15:52.240 It internally generates labels and jump statements. 00:15:52.240 --> 00:15:58.640 So that concludes this presentation. 00:15:58.640 --> 00:15:59.666 Thank you for listening, 00:15:59.667 --> 00:16:07.440 and I hope you enjoy the rest of the conference.