diff options
author | Sacha Chua <sacha@sachachua.com> | 2022-12-03 18:48:37 -0500 |
---|---|---|
committer | Sacha Chua <sacha@sachachua.com> | 2022-12-03 18:48:37 -0500 |
commit | 84db94cd5ca298b5a423749debe39926369e5765 (patch) | |
tree | 9f9cbdc484f3ec0aab9a196669f1f276a5d1dfa1 | |
parent | d1b0b1a5a31a1addbec80f0b395799f6ce85901f (diff) | |
download | emacsconf-wiki-84db94cd5ca298b5a423749debe39926369e5765.tar.xz emacsconf-wiki-84db94cd5ca298b5a423749debe39926369e5765.zip |
Test
-rw-r--r-- | 2022/captions/emacsconf-2022-asmblox--asmblox-a-game-based-on-webassembly-that-no-one-asked-for--zachary-romero--main.vtt | 874 |
1 files changed, 0 insertions, 874 deletions
diff --git a/2022/captions/emacsconf-2022-asmblox--asmblox-a-game-based-on-webassembly-that-no-one-asked-for--zachary-romero--main.vtt b/2022/captions/emacsconf-2022-asmblox--asmblox-a-game-based-on-webassembly-that-no-one-asked-for--zachary-romero--main.vtt deleted file mode 100644 index ef640bf3..00000000 --- a/2022/captions/emacsconf-2022-asmblox--asmblox-a-game-based-on-webassembly-that-no-one-asked-for--zachary-romero--main.vtt +++ /dev/null @@ -1,874 +0,0 @@ -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. |