What do you want to learn? Leverged jhuang@tampa.cgsinc.com Skip to main content Pluralsight uses cookies.Learn more about your privacy Play by Play: HTML, CSS, and JavaScript with Lea Verou by Lea Verou and Geoffrey Grosenbach In this live coding session, W3C member and front end development expert Lea Verou implements the classic Conway's Game of Life in the browser using HTML, CSS, and JavaScript. Start CourseBookmarkAdd to Channel Table of contents Description Transcript Exercise files Discussion Recommended Introduction Introduction I'm Geoffrey Grosenbach, and this is Play-by-Play at Pluralsight. At JSConf in Florida I say down with Lea Verou, internationally-respected CSS and web standards expert. I did something that I've never done in a Play-by-Play; I let her choose the project. She chose Conway's Game of Life, a classic computer science brain twister where the player sets up a pattern of black and white squares on a board, then the game engine runs continually obeying four rules: under populated cells with fewer than two live neighbors die; cells with two or three live neighbors stay alive; overpopulated cells with more than three neighbors die; dead cells with exactly three neighbors come to life. The application runs indefinitely applying these rules to the game board on every turn. Lea is has been on the payroll of the W3C and is an expert at all things frontend. So this simple game may seem too easy to implement, but in a brief two hours she showed how to cleanly structure JavaScript code and gradually add features. Along the way she uses several of her own home-grown tools, which you can find at lea.verou.me/projects. Play-by-Play is a series where we give you a rare view into the workflows and thought processes of top admins, developers, and designers. Have you ever wished you could watch one of your tech heroes work on a project on their own time, using their own tools, at their natural speed? Not a rehearsed presentation or a prepared course, but planning and problem solving as it happens in the real world every day. That's what we do in this series. We give talented admins, developers, and designers a task to work on, live and unrehearsed for about two hours. We talk through architecture decisions, and fix unexpected problems. This isn't a tutorial; we won't pause to explain how to set up server infrastructure, or a development environment, but you will see how professionals use tools in their everyday environments. Data Model and Game Mechanics Lea's Travels So I'm here at Amelia Island, Florida with Lea Verou. Very excited to be at CSSConf and JSConf. And you've been on a massive traveling tour of the world, I mean, you speak all over the place, but you drove down here from-- Washington From Washington D.C, along the coast. I bet that was pretty beautiful? Egh, it was okay. It was all on the I95 so it was kind of boring. And it was exciting because your boyfriend is British so he was tempted to drive on the wrong wide of the road. He tried to exit on the left on the highways. That was awkward. He had to pull over and like -- half a mile before the exit in front of every other car. So then -- but then you have a few little break in traveling before you go on to MIT, one of your dreams, now you're going to be getting PhD. A PhD, yeah. Fantastic. And this is my-- I only have one conference left before MIT. This was my almost last one. Almost last one, well, and it's a nice place to be. It's a lovely place. Because it's been beautiful, and it's the beach, and yeah. We actually-- we need to find out how to do this session on the beach; it'd probably just be a little windy, and hot. That would be great: coding on the beach. (Laughing) There's actually a thing called Hack your Beach where people gather on a tropical beach every January and code on the beach for, like, a month. I need to find out about this, wow. Yeah, we went to Hack your Beach in January. It was Kenya. It was lovely. Wow. Description of Conway's Game of Life Well the coding problem that we'll do, even though we're not exactly on the beach, is Conway's Game of Life. And I'm surprised I've never done some of these classic problems in one of these Play-by-Play sessions. But this is one of the classics; I think it's been around since, like the seventies, or something. The seventies. Okay. So basically its four rules, you set up a grid with black and white squares, and then the four rules are: any live cell with fewer than two live neighbors dies because it's under populated; any live cell with two or three live neighbors lives on; any live cell with more than three live neighbors dies because it's overpopulated; and finally, a dead cell with exactly three live neighbors, has to be exact, becomes live, itself, by reproduction. So then this game plays itself out. It's a zero player game. Zero player, it's all automated. So you just set an initial statement; it just keeps going. Forever or until--? Sometimes it reaches a state where the next stage is the same as the previous one so it can't change, or there are these things called, I forget, Oscillators, which just go through the same states over, and over, and over again. And there are lots of different formations. So we'll see if we run into some of those but many people have done this just with Canvas, but you want to do this with actual HTML, use the DOM, drive it that way? Well, it's one of the classic things that every programmer has coded at some point, so I always wanted to do it. And it seems that everybody who has done it in JavaScript does it in Canvas so I thought it would be an interesting experiment to do it with the DOM, and using checkboxes for the squares. Because that also makes it accessible, out of the box, because when you're using Canvas it makes it really difficult to make the game keyboard accessible. I like that. So in with being accessible it means that when you setup the game, which is actually the only way that you actually play it as a player who's interacting, is you get to choose which boxes are on or off, and then that could be accessible as well. Well, and I guess it could tell someone the state of the game accessibly, as well, if we use DOM. If you implement something for that, yeah. It might be able to read out, you know-- it has 10 live players and 50 dead cells or something. I think the code ends up being more understandable when you use the DOM, like in Canvas you have to implement everything from scratch, so like, even when the player clicks a grid square, and that makes it dead or alive, you need to implement that from scratch. Whereas when you're using, for example, a styled checkbox, you get that behavior out of the box. Exactly, yeah, that state, which is exactly the state we need for this game, just on and off. Initial Strategy and Starting Point So if you were hacking away on your own time, on the beach, with this project, how would you start? I'd probably-- well, I would probably start by loading the Wikipedia page and remembering the rules, then I would go to Espresso, create a new project. Yes, I know that most people don't use Espresso anymore, but I quite like it. It's a good app. It's well done and it supports, I mean especially for something like this, great support for HTML, CSS, JavaScript. MmmHmm. There are still things that it needs, but I find it quite easy to use, and convenient. So I would create a new project, new HTML file, I usually use a snippet for the main. Ah, for the HTML boilerplate, nice. Yeah, then I guess, how would we name the JS file, life.js, maybe? That sounds good, life, or game, or yeah, life works. I kind of like life.js. And create life.js, the CSS file in case we need any styling. And your template already looks for the style.css? Yeah, it already has this here. And let's add a title. So I would first start by implementing the main logic in JavaScript, like the model, so that it's playable through the console so that we make sure that the actual logic works first. And then I would implement the interface through an HTML table, probably. Because it's a grid, so a table seems appropriate. Right. I would probably use a table with check boxes, but I would first try to implement the logic like a, sort of, class that takes a two-dimensional array as an input, as the seed, and then has a method for next, that creates the next generation. So let's try to do that first. And are there any standard rules for how big the board is? Or we will just decide that at some point? So, in theory, because Conway's Game of Life was invented by a British mathematician, Conway, so in theory the grid is infinite, but every implementation usually uses a fixed grid of like, a certain amount of squares that's variable. Like in my model I plan to get the size of the grid from the size of the seed, of the seed array it accepts. So, this is just my own convention, the underscore, so that I can refer to the name of the prototype just by the underscore so I can change it easily. Because I'm still not sure that life would be a good name for it. And it would accept one parameter for the seed, and then the width would-- no let's set the height first because that's the length of the seed, and the width in columns would be the length of any row so let's get the first row. I'm not implementing any error checking right now because we first want to get this working and then we can implement error checking afterwards. So the seed will be an array of multi-dimensional ray through the initial state of the game. Yeah. We can start-- We can create an instance here so we can easily check if this is working. And let's get one of the examples from the Wikipedia page. For example, let's get the Blinker first. And we'll print this out in the console to see if it's working. So we'll use an array of zeros and ones for whether a cell is dead or alive. Okay. So a blinker would have-- this blinker would have these places in the array as ones. And that's smart to start with one of the known states like that because it's just going to go back and forth-- So you can easily test if it's working. Yup. See I would've started with just random on and off, but then that would be really hard to debug. Then you need to calculate the next state yourself. Yup. So we'll add some prototype methods that we'll implement later like next generation. And then our code that tests if this is working-- we have already created an instance of this, then we'll print out the initial state. So we'll need to implement a toString method that prints out a string with the current state of the board so we can easily debug this. And since it's a toString method it automatically converts it to a string if we need a string. With console.log, okay. Or, I think, since console.log can print objects as well it might print the objects, so let's convert it to a string by concatenating an empty string. So here we're printing out the original state, then we're calling next to create the next generation, then we're printing out the next generation to see if this worked. So we need-- Right, because that's part of the mechanics is calling next frame when then it applies all the rules. Yeah. And gets to the next state. Yeah. And after we make sure it works we can now implement methods for autoplaying which calls next every certain number of milliseconds, and then we can add something in the GUI that sets the number of milliseconds. But the main thing to get working first is the algorithm. So we need two separate arrays, one for the current board and one for the previous state of the board. Because if we start at changing the states of the cells in place then-- It's going to confuse the output because it needs to know the surrounding states. Yeah, exactly. Because whether its cell is dead or alive depends on-- you can't-- if you start setting every cell you'll change the number of neighbors in the cells you're going to check next. Yup. So let's set, let's call it previous board, prevBoard. And at first we would clone the seed array we have, so we could clone an array by using slice with no parameters, but now this is a shallow clone because we also need to clone the row-- each row of the table. Right. So we can clone the main array and then go over it with map. And then return a clone of each row. Oops there's no clone method, but there's slice. And if we need to clone arrays multiple times we can turn this into a method, like a helper function here. (Typing) Convenient. And why would-- you know if this was Ruby, people would want to just slam that into the array class itself. Why would you do it as a helper method instead of on the prototype--? I love adding things to the prototype but many people would say it's a bad practice because if you're using this script with other scripts on some pages, I guess here it's just by itself, but it's generally considered a bad practice to add things to the prototype. And especially since this method is actually not perfect because it only clones two-dimensional arrays because that's what we need here. So it's not a perfect deep clone for arrays. Yup. If we had an array within array within an array it wouldn't deep clone that. Right. So because it's not perfect we kind of want to hide it because this clone array helper will not be visible outside this closure. Makes sense. So we use it here. And now we get a little bit more readable initialization method there, clone the array. Actually I think it might be better to set this as the main board and have the previous board be null, or probably an empty array. And then the next function could take care of cloning the current board. CloneArray, right, and transferring it to the previous board. And then we can start processing the current board. Correct, because the initialization function doesn't need to work with two states of the board; it just needs the initial one. Initial Console Debugging Yeah, so let's implement the toString method first so we can start, because we will need it a lot for debugging. So we want to print out the current board status so let's map every line to a string. So we would return row joined by white space, and then we get an array of all the rows converted to strings, so we want to join this with a line break. With that new line, okay. So this should at least print the current state, even if it doesn't do anything. So let's start testing what we're doing here. Undefined is not a function. 42. 42. 42. 42, game.next. So we've defined game here. Could I have done something wrong here? Nope. Undefined is not a function. And it's printing an object. What have I done here? New life, okay. Does next need to return-- no, I guess it need to return. No, no, it doesn't need to return anything. Anyway we haven't implemented next at all yet so we just want to print out game. So it's not printing out the correct thing. So let's remove this string conversion and see what it's printing. So it seems that toString is not working. Why is it not working? This seems to be the object we have. So it's got the instance variables of, like, the seed width, and height. Let's see if I return a random string, like yolo, will it work? No. Hmm. Have I made a typo in toString? No. So it's the method that's not working correctly. So is it not calling toStr-- oh, it's not calling toString at all on our life object. Yeah, it's like this doesn't exist, I guess. Yeah. Oh, because I've misspelled prototype. Agh. Of course. And it didn't pull that out as an error? So let's go back to the original thing we had. Yes, this is printing the array. That's better. Yeah. So this is printing the array. So that's my job as the pair programmer is to catch all your typos, but I didn't catch it there. (Laughing) So right, we set the current state to the previous state; we send it to the previous board variable. So now let's go over every row, and cell, and see what's happening. (Typing) So the reason we're doing rows first instead of columns first is that actually the first number here is the row. (Typing) The X and Y, see, usually I don't like single letter variable names, but in this case it really is just kind of a mathematical X, Y grid. Yeah, exactly. So that makes sense. I guess we could still do row and column, but it's-- but that's not exactly what we're talking about either. Neighbor Logic Let's have a method, let's call it aliveNeighbors, that gets an array, and then X and Y, and returns the number of alive neighbors. So this is our first rule that we're implementing? Because we can print out the state of the array, but now we need to actually apply the logic. Yeah. So the array we're doing this checking on is previous board because we've already set it with the current state and we don't want to modify it with what we're doing right now. And now we only have an array, and an X and Y. We need to count the number of neighbors. So we could do a loop, but we only need to check eight neighbors, and we would need to do a two-dimensional loop anyway so it's kind of pointless, I think, to loop. Instead, I think, I'd rather do an array of neighbors, like-- and actually let's separate it by rows so that it's even more clear what we're doing. Because when this function is called it gets the entire board. It doesn't just get a row-- and it needs the entire board because it has to look in all different directions. Okay. Yeah, yeah, exactly. So the previous row would be array Y-1, and X-1, then X, then X+1. We just got the three squares above the current square. Okay. Then we need the two squares left and right of the current square, so that would be this, and this. And we also need the row underneath the element so that would look like this. See that was so fast. I would have to spend 20 minutes sketching out a tic tac toe board to figure out all those, but that gives us what we need. So we need to go; now we have the neighbors but we haven't added them yet. We don't know what state they have. So if we use, now if we use-- we need to go over the neighbors and return zero when-- And, oh, we have a sum here. We can have a sum here with zero, then go over the neighbors. That could be a forEach actually. And it's just iteration so I'll just use a single letter variable. And so we'll use plus equals to add things, and if A, we want-- if A is undefined or zero we don't add anything so we add zero to the same thing. If A is true we add one. Yeah, so we want to convert both undefined and zero into zero, so if we use plus, and this is undefined, it will return not a number, none. So let's convert it to a Boolean value first and then use plus. Okay. So we convert the Boolean value to a number. Because if we use the console here, if we have undefined, and we use plus on it, it returns none. If we use undefined and we convert it to a Boolean with the two exclamation marks, it returns false. And if you convert a Boolean to a number with a plus sign it returns zero. So, together that will give us a number; then we can add up to find out how many neighbors are still alive. Yup. Concise. And then we can return the sum. However here, we have a problem here because as you will see-- actually let's try to run it and you'll see what the problem is. Oh, we're not calling next yet. So this will give an error. Yup, because we will end up running into values of the array that don't exist as rows, and then we're trying to call a certain cell in the row, in a row that doesn't exist. So we're trying to get a property from an array that doesn't exist, so we're trying to get a property of undefined, which gives us this error. And is that middle, the current row, where we're looking at the left and right? So assuming, for example, assuming the current cell is this, the top row, and it's trying to check the neighbors above it. Yes, so there are multiple cases where we wouldn't have a neighbor-- Exactly, so we wouldn't mind if that returned undefined, but we don't want to try to get properties from undefined arrays. So instead of doing this, we'll define a previous row, which is-- and also this might make the code a bit more understandable as well, so two birds, one stone. And next row, which is this. So what this does is if this doesn't exist then it gets this value which is an empty array so we can call properties on it. These properties will still return undefined because it's an empty array, but we're fine with undefined, in that case. So let's replace these rows with this. And the next row. And we don't need the current row because we know that the current row exists, so we don't need to do this. We just need to do this for the previous row and the next row. So this does exist, but we're not doing anything with the neighbors yet so we get the same state back. Let's print the numbers of neighbors so we know what we're doing and if they make any sense. Because in theory, right now, we should be able to see how many neighbors are alive. Yeah, if this works we should be able to see how many neighbors are alive. So we need to also print the coordinates we have. And let's print the coordinates first; that probably makes more sense. And then a colon. So let's get some-- a sample, for example, this one. This would be one, zero, the second row, because they're in zero indexed, and the column one, so it's the-- we need to find two in one, and it says three neighbors so it seems to be working. So let's improve the code here a little bit. We can probably do this with only one function, so we have the neighbors array and instead of using forEach I'll user reduce. So reduce takes a function with a previous and current values, and you also specify an initial value, so you can just return previous value plus the current value converted the way we said earlier. To a Boolean and then a number, okay. If this works, I should just be able to use return here. I don't even need this sum anymore. So let's pray this works. Yeah, it seems to work. We could check our Y of two, and X of one, and does it still say three? Yup. Straight on, nice, little refactor. Yeah, I love reduce; it's not getting as much love as it deserves. Okay, so we have the number of neighbors. That was, like, an ES5 new thing? Yeah. Or was it their previous--? Yeah, that was ES5. But even though people have been using, like, forEach, and filter, and mop quite a lot, reduce doesn't get much love. Okay, so we have the number of neighbors. We know they're correct. Let's implement the logic. So, this board, what is this board currently? Oh. Okay. We cloned the seed. So let's set a variable; is it alive? this.board Y X, and let's convert it to a Boolean. Okay. Death Logic So if it's alive, and what are the rules again? Any live cell with fewer than two neighbors dies. Or more than three neighbors dies. So if neighbors are more than two-- no, more than three, or if neighbors are fewer than two, this should probably be first, (Typing) then this-- gets-- this dies so it gets a value of zero. And I was a little bit confused. I was thinking we were going to have to change other cells but we're just changing the one-- we information about the surrounding neighbors, but then those only affect the one that we're currently dealing with, so that's why we set it to zero. Yeah, which is actually kind of inefficient because we're checking the same cells multiple times. Right. But the main-- I think it's important to first get it working and then do any optimizations if there's time. So if it's not alive, any dead cell with exactly three live neighbors becomes a live cell as if by reproduction. So if neighbors equals three, then this board becomes alive. So that's the easiest. It's just straight forward, exactly three. So let's test if this is working. And there are more rules but we want to just see those two-- or are those the only two that affect that oscillation? Right, these are three rules so I'm missing one rule. Oh, any live cell with two or three live neighbors, lives on to the next generation. So if it's alive, it should still be alive with this, so let's sort of test what we've done so far. Uh ha! Okay. Let's see what's happening. Huh? It seems to be working. We got the blink from vertical to horizontal. Yup. And we can even do it again and see if what the next state will be. Yeah, it seems to be correct. Kind of goes back, impressive. So we've already done this. Let's try another one just in case, just in case we were lucky. Let's try the Toad. So the toad is six by six. And let's convert these to zeros again. And it's three here. Offset. Okay, it's on the third row. (Typing) So let's see. Fingers crossed. So the initial state is correct. This seems correct, yup. Yup, that's how it should be. Wow. So I feel like-- I still feel like we're missing a rule but it's in there somewhere. So if it's alive and it has two or three neighbors it keeps being alive, but because we're setting things on the previous state. So we cloned the board to the previous board, but the current board remained the way it was. So if it's alive and it keeps being alive we don't need to make any changes. Oh okay, so we have three conditionals, and then there's a fourth conditional somewhere that we didn't really specify and it just falls through to staying as is. Yeah, exactly. Wow, so that's the basic game logic-- wow that only took 15 minutes, but now we've printed out the console. Now we need to make a UI for it because obviously we can't play that way, by setting arrays in JavaScript. (Laughing) HTML Game View Implement the LifeView Object So we have our basic game logic in model, but it only works in the console to print out that logic. So now we want to visualize this in HTML, CSS, and the DOM. Exactly. So let's make another class. Class, that will be the view. So let's name it-- what should we name it? LifeView? Okay, so the first one was just the logic and this is the-- this will render it to HTML. Yeah. So the prompters this will accept is a table, a size, it will be responsible for other adding the columns, but it will accept a table element. A size, and-- well, these seem good for now so let's set a grid-- let's store the table in the grid property. Let's add a table here. And the table is basically blank to start with? Yeah, and let's add, let's make sure I type this correctly this time, prototype. Now I'm liking your little underscore technique more because it means if you rename the Life.View class you only have to do it one place, is that the--? Exactly, exactly, other programming languages have a variable for this that you can use, like self, or something, but JavaScript doesn't' really have anything. There's this, but this refers to the current instance. There's nothing that refers to the actual prototype. So let's a method that creates the grid. Let's store this size, and this should be pretty simple. (Typing) Pretty much the same kind of loop. (Typing) And I'm not using any frameworks for this so I'll just-- (Typing) use the native DOM methods. (Typing) And let's create a document fragment to add these on. (Typing) Oh, I know, what's a document frame-- is that like the shadow DOM? Or you're just creating a memory? NO, that's like a part of the DOM that's not visible in the DOM so that you don't interact with the DOM too much, because every DOM interaction is a performance hit. So if there are many DOM interactions you try to minimize them and just do it in one big lump. Oh, okay, so we'll manipulate the fragment, and then at the end we'll load that in. And then at the end after we add everything we want we'll do this.grid.appendChild(fragment). And if the table-- we want to empty the table first, in case it already has something in that might be useful in, like, future iterations. So, let's add innerHTML to an empty string, whereas, that should work. That's nice, I learned that general technique from, like, early 3D graphics programming where you do all you're drawing offscreen, and then once you're done you swap it in. Yeah, if you're going to lots of interactions with the DOM, it's always useful to make the model on an element that's not in the DOM tree yet, and then add it all together. So we append the row, and then here we add stuff to the row. So let's add the cell. (Typing) And let's create a checkbox. (Typing) And we have an extra closing paren on the checkbox, two lines up. Oh yeah, we do. We would've found it eventually. Yeah. Makes it easier. So we've appended the checkbox to the cell; now let's append the cell to the row. And here is where it's nice to have the actual terms row and cell that we know what we're talking about plus we already have X and Y externally so that we need another name for the checkbox. And also since it will be useful to have an array of all the checkboxes, so let's-- let's set it here, checkboxes. Or should we name it controls? Let's name it checkboxes. (Typing) And let's add this checkbox here. (Typing) So let's test this and create a grid with a fixed size, and eventually we'll add a control for the size, But for now this size will be defined in the code. So let's call it lifeView. Admittedly, the names are not perfect right now. Or we could just use query selector. And let's add a grid of 12. So let's remove this. Ah, that was just for debugging so we don't need that anymore. Yup. So this doesn't have any connection to the life model, right now, so it will just create a grid, but it won't do anything. Okay. But hopefully-- oh, we haven't called the createGrid method, actually. So this should just create a grid. Okay, unexpected token, line 94. Where is that line 94? Oh right, of course. So now we have a grid of checkboxes. Wow. They're not styled in any way. And it's 12 by 12. Yup. Grid Styling Let's center this first, because it's kind of awkward on the side. Make it sans-serif, just in case. And we don't want any border spacing so let's cancel this. (Typing) Yup, so we don't want these to look like checkboxes. Okay, they'll just be a black or white square? Yup. But we want them to behave like a checkbox that we can touch it. Yeah, exactly. So let's add a border to the cells and the checkboxes shouldn't be visible at all. Let's cancel paddings, and stuff, because there are some default paddings here. Okay, I know this looks awful right now, but that's how it's supposed to look. (Typing) So let's style the checkboxes a bit. And this is the style they'll have when checked. Oh, a little browser prefix. Oh, and you're using-- well, we were in chrome so that is webkit. Yeah. And if I remember correctly, appearance is actually nonstandard with the nonvalue, but it should get the value normal eventually. But I'm not sure if that's actually in the spec yet. So let's check what the spec says. See, you're probably one of the few developers who actually reads the spec and can understand it. I think the-- so the appearance property was removed from the spec at some point, and then we resolved to re-add it with just the normal value, but no browser actually accepts the normal value. But I think this spec hasn't been updated in awhile--yes, since 2012. So let's see the in development version of this spec. Ah, that's somewhat more recent. It's not even there. Does it have the appearance property? (Scrolling) Doesn't seem to. So only vendor prefix, vendor prefix is the only way to get it. Yeah. It will be there eventually because I remember the discussions in the CSS working group, but apparently the editor of this spec hasn't added it yet. So in the meantime, and we can't just use display none because then that would remove any kind of interaction at all. Exactly, you wouldn't be able to interact with it at all. And even if you specify visibility hidden, it still hides it from screen readers and other assistive software. And that's one of the goals here is to also make this accessible, so we want to make sure we do CSS in the right way. So let's give it a background of yellow so we see what we're doing and then we'll remove it. So that now, they don't have any dimensions, the checkboxes, so let's give them-- Okay, first we need to set a display of block, and then let's give them, I don't know, 20 pixels, maybe, and a height of 20 pixels. Let's see. Yellow. Yeah. But the borders of the cells are-- if we have two neighboring borders they seem to create the appearance of a really thick border. Right. So let's specify border.collapse. Right. And we need to specify that on the table. Yup. And we could do this. So right now, this kind of works. However, from what I remember in Firefox, this doesn't appear at all. Actually, let's check what we're done in Firefox so far. You like Alfred for launching apps? Yeah. So Firefox has a problem that should be fixed pretty soon with the borders of checkboxes. There's absolutely no way to set that you don't want borders on the checkboxes. So we could try. We should specify a border of zero, none, or actually none's the default value so just a border of zero. And it should work on Firefox, but there's an open bug that because this doesn't work yet, so you get this awful border. And there are ways to hide it visually, by clipping the checkbox off, but let's not spend too much time on that. So for now, this will be webkit? Well, it will work on Firefox. Oh, it'll work, it just won't look-- So the problem is with Firefox, we don't get the border. Oh, okay. We get these ugly checkboxes, which is not what we wanted. So let's try a small hack and instead of setting a background, or in addition of setting a background, let's set an inset box shadow with no offsets and no blur, and let's say 10 pixel spread, black, and inset. So as you can see this works. Wow. And let's set it to 99 pixels just in case we want to change the size of the cells. We don't want to have to change that as well. So as you can see now, these-- Now, we at least get a solid color instead of a check mark. Yeah, and eventually Firefox will soon solve, fix this bug, because many people are complaining about it. So let's go back to Chrome. We have this yellow grid at this point. let's change the yellow because-- oh, white. Okay. So we have the interface, and we have the model, but they're not connected yet. Connecting the Interface to the Model And this is how the game will start. We'll get a blank board and we'll decide which boxes need to be on. Exactly. And then we'll have some kind of play button that runs the logic. Yeah. Well in the beginning, we'll have to call play in the console and then we'll add a button. Okay. So, let's add a play method here, and a next method. So the play method will create an instance of the life class. Let's call it game. And what prompters does the life class need? A seed. So we need to create a seed from the checkboxes we have, so we need to convert these checkboxes into an array with zeros when the checkboxes aren't checked, and ones when the checkboxes are checked. So, let's say-- So that why you set up this.checkboxes earlier? Yes, exactly. So let's have a boardArray getter that will return this. And since this is basically a function with no parameters, I specified it as a getter so we can use it as a property. That's also ES5. And I've never seen that syntax, so get creates a getter for any-- any properties. And you can also use set to create a setter, so that when you set a property something happens in a similar way as the native getters and setters work. Okay, and that works right within the object syntax that you're using right now? Mmmhmm, yup. So let's see, we have an array of checkboxes, and we want to create an array of zeros and ones. So let's map the array of checkboxes to what we want. So now we have a row with checkboxes, so we also want to map this array as well. Actually this is a checkbox. At this point we've reached the checkbox. So we return, if the checkbox is checked-- so this is a Boolean so I use a plus in front of it to convert it to a number. So now this should return zero if it's checked, and one if-- one if it's checked and zero if it's not checked. So this-- at this point this boardArray should work. So actually, before I test this, let's test the boardArray getter first. Unexpected token, 101. So what did I do here? Did I miss parenthesis? There is something. Needs another parenthesis in 100? Oh yes, yes, I missed the parenthesis. Okay, so, let's check a few checkboxes, and see what boardArray returns. So we assigned it in a global variable for now just so that we're able to test things easily. Oh, right. So let's check. This would be-- now let's check this checkbox. That's easier. So yeah, this is at one. It's on. Seems to be working. Implementing the 'next' Function So we have the array with the seed now, so let's create a game model. So now-- okay, so we've connected the number of cells on the visible board with our checkboxes array but we still haven't hooked it into the logic. Exactly. So now, basically what we need the checkboxes to do is A, to act as an input in the beginning to get the initial seed, and then we need them to function as outputs to show the state of the game. So that will happen on next. So on next, we'll call next on the game, and then we need to represent the array on the checkboxes. So the array of the game-- the board of the game is on the board property, and now we need to reflect this on the grid of checkboxes. Okay, we already had code to set up the board, so now we're not creating new check boxes, we'll just modify their state. Yes, exactly. So the board property of this class returns a two dimensional array of the current board status, which is why I've used forEach to loop over it. Or actually, let's just use a traditional for loop. (Typing) (Typing) So, let's cache the board in variable. (Typing) So we will reference the checkbox like this, and we want to set the check property, and this would probably work. We probably don't need to convert it to a Boolean that way, but let's convert it anyway and let's see. So, maybe, this should work. Okay, because board just has zeros and ones, but we maybe want to make it true or false when we set the checkbox status. Yeah, but I think that's done automatically anyway, but we've converted it just in case. I think it's not needed. So let's try this pattern that we already know what it should create. And let's call play, and then next. Oops, cannot read property zero of undefined. Okay. So maybe it doesn't like the board variable, or what? Oh, we don't get a line number. Yeah, exactly. So let's-- let's log a few things. Let's make sure that everything here-- Cannot read property ‘0' of undefined. What could it be? Sometimes these error messages are really cryptic. Right. (Typing) Still. And it doesn't print anything Oh, so it's not getting to that point? Yeah, it's not even printing anything. So, let me check something. So, the game property, let's see, is this one correct? So this one is correct; it has the correct status, so the problem is-- Yes it moved. Okay, this is the Toad. Yeah, I think it's the the Toad. Okay. So the problem is with getting the checkboxes to show the next state. So this worked. Oh, of course, this prototype does not have a board property. It's the game that has a board property. Ahh, that error message gave us no help in finding that. Yeah. So let's see. (Typing) Huh. Blam. So it works, and then we can keep calling next. Back and forth. (Laughing) Adding Buttons to Run the Simulation Amazing. So that's-- we connected the visible board to the logic. MmmHmm. And we can play and we can advance each stage of the game, but it's not doing it automatically right now. No, right now we have to call next and play from the console. So let's add two buttons for those, and we'll style them later. So right now it won't look very pretty. But at least we'll be able to do it without needing to run commands. And then we can do autoplay. So-- (Typing) I'm also, jumping ahead, I'm also curious about speed. Will this play so fast that we can't even see each stage, or are we going to have to do something about that? I guess we'll find out. Yeah. (Typing) So I like to add a convenience dollar method, sometimes, which gets a selector and a container, and returns-- (Typing) So then I can do things like this. (Typing) And I could-- (Typing) I like that. A little tiny jQuery. Really, really tiny. (Laughing) Yeah, because some people use jQueries just to be able to get elements by selector, and by using the dollar notation. It's kind of-- If that's all you need then why get the rest? Yeah. (Typing) Or actually, the only thing we need is a next. Because it's going to call play when it sets up the board initially? Yeah. Oh, but don't we-- So we set up the board, and then we call next-- So that might be able to handle it itself? We did talk about not wanting to manipulate the state of the game in process, while it was running. Yeah. Does that play into whether or not we need a play state? Yeah. Yeah, because this is like an implementation detail that shouldn't be exposed to the user. As far as the user is concerned, if they make a change, and they press next, it should just work. They shouldn't have to think, oh, should I press play or should I press next? Right. Well, good. So we can abstract that away a little bit, or at least hide it from the UI. MmmHmm. Then I forget what play even did? That just sets up the initial--? Play just creates a new game. Okay. So maybe we can have a flag that when we check a checkbox, it kind of, it sets the flag to false so it knows that it has to call play first. So let's try to do that. (Typing) So if the game has started, like if you've clicked next, because right now we're not autoplaying, so if you click next, and it started then it should-- then this should be already true and it should set it to false. So if you call next-- if it's already started, it should call next. So, actually, let's do it the other way around. Right, if it's not started then we play. If it's not started OR there's nothing in the game property, just in case we screwed up. Okay. So then we call next. Because every time we'll have to call next is just _____ -- Yeah, yeah, yeah. So-- (Typing) And after we've done this, we set started to true. And do you want-- if it's not started, and there's not a game, or if it's, like right now it says if it's not started or there is a game? So, yeah, so if we check a checkbox, any checkbox, started should become false. Okay. And we should start a new game. Oh, we could try it out. Let's try it out, yeah. (Typing) So let's add an event on the table, because I guess the change event on the checkboxes should bubble. So that we can just use it on the table. And let's add this to a variable. So--(Typing) Let's assume a checkbox then, because we don't have any other inputs in there. (Typing) So anytime someone interacts with the checkboxes that just stops the whole game so that it then would have to, someone would have to hit next to start it up again. Is that what we just did? Well, that should be transparent to the user. Okay. At least that the thinking behind it. So right now we haven't really done anything with these buttons yet. Actually let's remove these buttons for now. Let's make sure it does what we want to do first. So the only thing we want to call out from the outside is next. Okay. So let's see if this works. Yes. It does. Okay good. So now if we modify it, to something else— let's do something completely different. Maybe just our vertical line? Yeah, that was my plan. Yes. It does it. It does work! So it reset the state of the logical board based on the current state of the visual. Yeah, it's not very performant, but I presume users won't interact with it too much once it's started. And it's not done very frequently, so as long as the performance is not-- as long as there's no noticeable delay, I am not sure if it's worth to optimize this. For now. And you said, maybe, we could disable all the checkboxes, that's another benefit of the fact that we used checkboxes. Yeah, but then we lose this. Okay. So I think, why shouldn't the user interact with it once it's started? What if they want to change it? Yeah. Refining the 'next' and 'autoplay' Buttons So we just need a next button. Good, got rid of an entire button. (Typing) Yeah, ¬¬¬¬Espresso's not great in uncommenting and commenting things sometimes. So we just need to call next. Let's check that this worked, and yes, I know, it looks really bad right now. Wow, there it is. It works. Or we can change it. Great. So, let's do the autoplay now. Okay. So the autoplay would be a checkbox, I guess. (Typing) And I'm going to use a separate label because I want to style the label eventually. So let's give it an ID then, because we need the ID to connect the label to it. (Typing) Right, we've styled all the checkboxes. Oh, because we've styled all the checkboxes. And actually, we only wanted to style this grid, not all the tables. A little bit more specific. So, yeah, let's be a bit more specific. And let's indent everything inside of grid, so that we can find rules more easily. Oh okay, so you'll do that for yourself visually almost as if you were using SAS, or some-- Yeah, because now it's just a small CSS file, and you can find everything, but once it gets, like, thousands of lines this becomes really useful to find rules. Yup. And to understand what's going on. What's inside what. Okay, so now we have this and it works when you click the label, but it doesn't autoplay. So, we'll style it afterwards. Let's first make it work. So if you click-- if autoplay is clicked by default, then it starts autoplaying before you even do anything to the board. So, I guess, it should be unchecked by default. I have to rant, slightly. It annoys me so much when people don't connect the label to the checkbox. Me too. I feel like the browser should just crash immediately if the developer fails to do that, or something dire should happen. Yeah. And it's not that hard. We thought give it an ID, label for that ID, done. Yeah, exactly. Or, well, if you don't want to, because here I've given it a separate label because I want to style the label eventually, and hide the checkbox completely, and, like, style the label as a button. But usually you just need to nest the checkbox in the label. You don't even need to give them an ID and connect them. So I hate it when people don't do that. So, how would you picture the interaction working? You would click autoplay after you set up the board, and it would start playing? Good questions, well, I guess there are two options, one is-- oh, because now the next button is kind of nebulous because if it's autoplaying, then next really just means start playing. Exactly, nothing, yeah. Maybe you could click Autoplay, and then this becomes start? Oh, that works. So it's still just one button, but it's either-- it says next if Autoplay is off, or it says start if Autoplay is checked? MmmHmm. But that still doesn't seem great. Or what's the other option? We just have two buttons: one says Autoplay indefinitely, and the other one's just next? And do we have a stop button, or they would-- one of those would change to stop it? Now, we're getting into game design. Yeah. And should it have a mascot? No, it's basically UX. And I haven't played Conway's Game of Life enough to know how other people would set it up. Yeah, me neither. But would people expect. And usually, I guess-- Well, this is almost like a movie player, can we take inspiration from that? I mean, because you have, a lot of movie players have next frame, or just play indefinitely. Almost, so do we almost just like have a-- it's just a play button just means autoplay? Or maybe, yeah maybe you could click Next, and if Autoplay is checked it becomes disabled so you can't stop. But no, no, no. I'm feeling like autoplay is going to be the default so that should be, that should be the easiest thing to do. Because most of the people probably just set up the board, and then just hit play, and they would just watch it run forever. You think so? Unless they're studying each-- I mean, at this point, we want to study each transition to make sure it's happening correctly. Right. But you were skeptical. You think people would prefer to just see the frame by frame? No, I'm not sure. I prefer to see the frame by frame, but I don't think I represent most people in this. Yeah, let's just do this. Let's make it become start when autoplay is checked. Okay, and if it's not checked, it's next. Yeah. I like that. So, autoplay already has an ID anyway. This should probably be in the view class instead of outside. Okay. I guess, so let's-- Although then we would need to pass buttons into it, and maybe it's better contained this way, like just the table. Yeah, yeah, let's do it that way. So does it go in the view or the logic, that's what we're trying to decide? No, no, no, it's definitely in the view if it goes in here, but I was thinking, should it even be in this class at all, or should it be just some code outside of that class? Okay. Simple Controller Functionality Because this is-- I kind of like thinking about it as like, this is the model. It's just—it's the game, it's the logic. Then this is the table that reflects the status of the game, and then there are the buttons and all other controls. Well, that's a controller then. Yeah, I guess. Is it a controller? Hooks the buttons up to the view? Yeah, that's a good-- yeah. Someone's though this through before. (Laughing) But this is a really tiny app so I don't want to use like an MVC-- I don't want to make it exactly MVC-like, but it's good to have that separation. But I don't it would be good to use like, rigid design pattern. Sure. So, right. (Typing) So on change, we change the status, the label of this. So let's make a closure cause we'll need to store these in variables, and it's not very good to store them as global variables. (Typing) (Typing) Just in case. Ahh, so we want the select-- oh, we want the reference to the button itself in an object, that makes a little more sense. Oh, yes. Then we can refer to it elsewhere without reusing the ID. And now we will end up having content in our JavaScript, but, which will make it slightly more difficult to localize this, if we ever wanted to localize this. But let's just say, because it's a smaller example, let's just go with it for now. So if this is checked, so if we're autoplaying this, is start, otherwise it's next. (Typing) Actually let's first test this. No. Is this the right ID? Should be. (Typing) Oh, you know what, this should be textContent probably. Ah ha. Yup. So value only works for text inputs, or--? I guess value would work with a button created through an input type equals button, or type equals submit, but probably not with a button created with the button tag. Okay. So-- Yeah, I've always wondered why there were two different kinds of ways to get a button like that. There's probably some historical reason. I think the button tag came afterwards. Okay. So now we have the label changing. But now it needs the functionality. Yup. So maybe next could check. So let's have a property in this. (Typing) Which would be false by default. Or we could do it from the outside. So, maybe if-- (Typing) We could either use set timeout or request animation frame, if we wanted to do it with the refresh rate of the browser, but if we want to delay it even more so we can see what's going on, maybe we could use setTimeout. Do one round per second, or something like that. Yeah. Let's use setTimeout first. Because request animation frame, that could be up to 60 frames per second? That'd be quite a lot of action. Yeah. So, we can't just use a reference to this.next here, because then the context would be the global object. Okay. So let's store this to variable and call this every, let's say, one second. So now every time next runs, if it's autoplaying, it schedules the next time it should run. Shall we see if this works? Yeah. Oh, wait, I haven't done anything. (Laughing) Yes! And it works. High five. (Laughing) Now I guess we should check the other state as well. Then if we uncheck it, does it stop? Yeah. It did. It took a second for that. It took a second so we need to, if it's unchecked-- if it's unchecked, clear the timeout. Stop everything. Okay. So we need a reference to the-- ah, you know what? What if we try to do, I'm not sure if this will work, but I'm not sure if this can work on real properties, instead of, like, fake properties that have getters and setters only. But what if, when you set autoplay, it also clears the timeout? So we have this timer equals this. And then clearTimeout here. Would that work? So we're overriding the autoplay setter? Yeah, but because autoplay's a real property and not just a getter and setter, I'm not sure if this will work at all, but let's try it. We'll find out. Yeah. Nothing. (Typing) Undefined. Yeah, because I destroyed autoplay, basically. Ah, so, let's do it in a different way. It seemed like a good idea. It seemed like a good idea, but in practice not so much. So let's do it in here for now and let's decide afterwards if it should be here in the other, or somewhere else in the code. So, at least we have a reference to the time. It should probably be private. But let's make it work first. (Clicking) It plays. It plays, and then if I cancel autoplay-- It seems to stop immediately, so that works. Yeah, and what happens if I click it again? (typing) Seems to do the job. Yeah. Accessibility and Styling Using git We've got the majority of this game working, at least in the most basic element. We can interact with it; it follows all the rules; and we can even autoplay so that we can just keep watching it endlessly and this could be entertainment at parties, or something like that. Several things that we could keep doing though. First, is something completely unrelated, which is source code control. Do you use source code control? What do you like to use? How would you go about storing a code project like this? I use Git. I usually write few lines of code before I start using Git in a project, so I think this is a good point to start using Git here. So let's Open Parent in Terminal. Okay. So just standard command line? Yup. Old school. So we've added everything And you like a message just right on the command line. Yup. I guess I should use longer commit messages. I usually use commit messages of, like, one line long so can just write a commit message from here without switching to an editor, or to VI, or anything. But the best practice is usually to write, like, a longer commit message. Well, it's always nice to just have the summary be brief, and then if you want to write paragraphs, you can do that. Exactly, but that's something I don't do yet. It's in my resolutions for the future. Accessibility So we thought one of the reasons of doing this in the DOM is so that we could have it be accessible, and one way to test that is just to navigate this with the keyboard, which might require some-- Exactly. a little bit of tweaking. Right now, right now, this should be accessible with the keyboard, but we have to press Tab, and go through every cell in sequence, and press Spacebar to check them or uncheck them, so it is-- and Shift and Tab if we want to go back, but it's possible to use it right now, but it's not great. It would be much easier if we could use, like, the arrow keys to move up and down, and left and right, instead of having to use Tab to-- because imagine if we wanted to check something -- it's a cell on, like, the last line. We would have to press Tab almost 100-- yeah, over 100 times to get there. So it's better than nothing, but it's still not great for keyboard users. So we could implement something like that. So we want to be able to use the arrow keys to just move up and down a lot more easily. Yeah. But we'll still use the spacebar to check or uncheck. Oh, that Spacebar, we can use the Spacebar by default, to check and uncheck a focused checkbox. We just need to implement the arrow keys. I like thinking about this because for some people-- well, for some people they wouldn't think about accessibility at all, but once you start you might just think, okay, it's accessible, job over. But if we spend time thinking about the user experience of the rest of our app, we should think of how the user experience is when it's being used from the keyboard, or other accessible methods. Exactly. And it's not just users that need to use a keyboard because of some disability. It's also users without any disability that just prefer to use the keyboard for one reason or another. Exactly. So-- And that's something right now that doesn't-- so we can't navigate with the arrow keys, but we're going to do something to make that possible. Exactly. So I guess, we could add an event listener to the grid again, because key up, and key down, or key press bubble, so we can just catch them on the grid. (Typing) It is nice that JavaScript is definitely part of accessibility now. Ten years ago, or more, it was just about if it's going to be accessible, don't use JavaScript for anything at all. But now we have, we have these capabilities so we can do this JavaScript. Yeah, actually if we catch-- the problem is if we use event delegation here to catch the key up on the main table, yes, it's more performant because we only use one event listener, but on the other hand we, so we get the-- we use event.target to get the checkbox that was clicked, but then how do we know what checkbox that was in the array? So we can either use a property on every checkbox to store its position in the array, or we could use a separate listener in every checkbox. So which way would you rather go? I can see pros and cons in each. Ok, so there's no way from the event to know where that is happening in relation to the table; we need to track that ourselves. We could use active element, which stores the currently-focused element and go through the entire array until we find the checkbox, but that's also not great for performance. Yup. So instead, so we may give every-- what is it the checkbox that's going to get some kind of ID? Or is it the cell? We could use a property on the checkbox. Okay. So then it gets maybe the row, dash, the column number, or something like that? Yeah. So now we have a reference from the checkboxes array to the checkboxes, but it would be useful if we also had a reference from the checkbox to the position in the array. Yup. So let's do that. We used to-- it used to be considered a bad practice to add properties to a lot of DOM elements in the early IE days, back then, because I had some memory leaks, but that was when you were using properties to store elements. Okay. Not when you were using properties to store, like, an array, or a string, or anything. So, how else should we call this property? And do we want, like, a data attribute? Or this could just be anything that we want to name it? Oh, it doesn't have to be a date attribute; it could just be a property. Okay. It doesn't have to be reflected in the-- Coordinate. Yeah. It could be as simple as this. Oh, it could be in data structure, so then that's going to be easy to reference, once the event happens? Yeah. So now we can get the-- (Typing) So we can't use this here because this will refer to the table, because that's where we set the listener, so we need to get the target of this event and make sure it was a checkbox. So we'll use this again. If you're using a framework, it already has abstractions for these. But here we're not using any framework so we have to call them from scratch, but it's not that much extra code. So now, in here, we know we have a checkbox, and-- (Typing) Now it's up to the keyCode. So if-- actually let's, because I remember that the arrow keys are somewhere like, 37, 38, 39, 40, but I don't remember which one's which. Right. And there are various plug-ins that reference these with meaningful names, but here we probably just want to find out what those key codes are. Yeah, exactly, here we're just using plain, old JavaScript. So let's log this, and that will also, that way we can make sure this works. And that we haven't made a typo or anything so far. Yup. UseCapture. Yeah, that's something Espresso adds every time you add a listener and you have to delete it manually. So if we're here, and we press the up arrow key, it's 38. So 38. The bottom one is 40. Left is 37. (typing) I'm guessing 39. No, no, no, I pressed-- It's 39. It's 39. So-- So now we have our directions and oh, so now we actually have to move the selection to a different checkbox. Exactly. So since we're inside an event listener, this will be pointing to something else so we already have a variable, me. So me.checkboxes, and let's store the coordinates into variables. So Y would be coords zero, and X would be coords one. So, I would call focus. Oh, okay. So now the left-- Although we didn't change that at all, we're just focusing on the current one? MmmHmm. Which is already focused. Oh, right. Yes. So if we're going left we want to subtract one. If we're going left we want, yeah, we subtract one from X. Bingo. Yes, and now we can press the Spacebar, and let's remove this logging. It's annoying. The problem here is, if we're here, let's refresh, if we're here and we try to press left, then obviously there's no checkbox to the left so it returns an error message. Literally, an edge case. Literally, yes. (Laughing) That was a good one. Wish Jen Schiffer was here. Yes. So we need to check that X is bigger than zero. And now we don't get an error message. Nothing happens. Is that what we'd expect? Yup. And we can move all the way there. Let's implement right, now. So, similarly, here would be plus one, and we would still have the same problem if we were here. Let's first make sure it works. Yup, it works, and here, we get an error. So if X is smaller than width, if not, we shouldn't get an error. Now we don't get anything. Do we need me.width instead of this width? Oh, it's size, its size here, width was in the-- Oh, in the logical portion, ok. And speaking of which, why do we have width and height there? Can't we just assume that it's-- no, I guess, in the general case, I guess you could have a different width than height so let's leave it as it is. So we have size here. Nothing. Did that refresh? Oh this.size, of course, and this points to the table, here. So it should be me.size. Works, works, works. Ooh, another error. It's the same error. Is that off by one? Yeah, it's probably, yeah-- It's probably an off by one, but let's-- let's log to see what's happening. 12? Uh huh. With 10 and 12? 10 and 12? So here, it says is one. Oh, yeah, because it's the previous one. Oh. But now it's moving, oh yeah, I forgot to, of course. Always happens. Elusive break. I wish we had the switch statement that didn't have this weirdness. This comes from C. Right. So, when we're here, oh, the console.log is only here. When we're moving towards the right, so yeah, so now it's zero. So yeah, it reflects the previous one. Yeah, because we set the focus to the-- Right. So it's still just a minus one? Do we want if X is less than me.size minus one? Yeah, I think that should work, because then we increment it. Let's see. Yes. Ah, no error. Or instead of making it stop, maybe we should make it wrap around? I was wondering about that -- old school asteroid style, just keeps going. But then we should make, maybe then we should make the logic wrap around, and-- What do you think? Oh, so if you have a live cell on the edge of the table you want it to seem like a neighbor of the other one? Yeah, but is you're pattern is-- So this is a spherical world presented on a 2D plane, or something? Yeah, but I think we're drifting too strange paths. Okay, so we'll just stick with two dimensions? We've reached the end of the world. No errors thrown, but you can't go any further. That works for me. Although I guess the interaction doesn't need to mirror the way the game works, because maybe it's easier to go this way and then have the focus wrap around. What do you think? So that same row? MmmHmm. Well, if we're thinking of it-- I mean part of this is from an accessibility perspective, so let's say if a blind person was using this, would there would be any feedback to tell them they're at the end of the table, and that that had stopped? But I guess they would still need to know that they'd wrapped around as well. And mainly the keyboard navigation helps people who have a disability, and can't use a mouse. There are people who are only able to use the keyboard. Okay, so it's more of a physical, motor skill situation. Yes. Okay. I'm happy with just having it stop at the edge. What do you think? Okay, yup. Makes sense. So similarly, up, would subtract one from X. (typing) (typing) And then bottom would be plus one, then we will have the same problem with the up and down. Right. So we would need to make sure that Y is bigger than zero. That's the easy one. Yup. And then that Y is smaller than size minus 1, for the same reason. And we are similar—- similar number cell, we're square, so we don't worry about width or height. Yeah. It's just the same distance in each direction. So now we can move to either direction with the arrow keys. Much easier. Yeah. I mean, even for me, I don't have a disability and I would much rather do this than have to click through each cell. I agree. I find this much easier to use. I'm a heavy keyboard user so I always try to make things that are keyboard accessible because I know many people are heavy keyboard users even if they don't have a disability. Styling So, lots and lots of things could still be done to make this game fascinating, addicting, social, or otherwise, but we want to do a little bit of styling to make this-- because you haven't been really happy with that autoplay checkmark. Yeah. So you want it to be a button? Yeah, I want it to look like a button so let's style-- But then that kind of changes what the Next button is going to do because right now, that's been our button. Yes. So you're going to style the Autoplay checkmark, checkbox, as a button. Yes, and I'll need to style this button as well because it looks awful, so I'll do that at the same time. Okay. So, I'll add the class of button to the label, and then, so the CSS is here; let's make it second. Let's add some padding. The top padding should be always a bit smaller than the left and right padding. And why is that? It's just visually we need to see more space? Because the top and bottom padding always looks more because of all the gaps that the font has. Oh, okay. So let's-- Yeah, and you said you started in graphic design, or you're still interested in graphic design, so that informs your work? I always-- so I started computer science but I was always interested in graphic design. I read a lot of books on graphic design. It's, I guess, like a hobby of mine. I'll never be a good designer. I mean, I'm kind of decent, but I'll never be like as much of a developer as-- I'll never be as much of a designer as I am a developer. I'm much more of a developer than I am a designer, but I love design, but I don't have much talent in it. Well, that's enough to start to have an interest in it, and it helps when you're adding padding to a button. And let's cancel the border that browsers add by default. Oh, a nice, flat design. No, we'll make them a bit-- Okay, we'll make it better than that. So let's add some margins to the table. (Typing) So now we're just styling buttons so let's add anything that has a class of button as well. And right now buttons have their own styles so let's inherit the font. So now they're the same, so because the button has had its browser styles applied, it had a separate font size and now it inherits the font size, and font family, and everything from the parent. And let's add some border radius. That's always confused me because I thoughts a whole point of cascading style sheets is that everything inherits from the parent, but buttons are special. Yeah, and font is a property that should inherit, not every property inherits, but font is one of the properties that are inherited by children. Border, for example, isn't inherited, but buttons, and inputs, and text areas, and some other elements, and actually every element has a user agent style applied. So if the user agent sets a style on the element obviously it doesn't inherit and that property. So for buttons the browser sets the form property so it's not inherited unless you explicitly say it should be inherited. So let's add some border radius, make it a bit bigger. No, it was better before. I always like to add border radius, and everything, in ems so then you can scale the button. Right, then if you make the font size bigger it will still look right with the border radius. So basically you have a button that's, basically, vector. You can make it smaller or bigger. But you have to style almost, to specify almost everything in ems for that to work, but for example, I want the border to be one pixel so I would specify it in pixels. And then let's say it will be slightly darker than the element. Something like that. RGBA, I thought you were a fan of HSL, or some of these other ones. Yeah, but RGBA is more convenient when it's semi-transparent black. Black is one of the colors that it works well for, yes. Because it's all zeros. I use HSLA if I want to specify, like, semi-transparent white, or really any other color. Okay. And let's give it a box-shadow of, like, one pixel, photo clean, or blur. So here I want to use semi-transparent white. So now we get HSL, okay. So, yeah. And inset. Alright, a little a— a little gloss, a little highlight. A little bit. And let's make it a bit more subtle, maybe. And let's give it a bottom shadow as well. That could be an ems. (Typing) Like semi-transparent black, and it should be a little smaller than the element because right now it won't look very nice, so let's make it a bit smaller. And actually, the blurring was too much, so, no. That's kind of better. And let's make a pressed style as well. Actually let's add a gradient since we went that route. Were you the one who was complaining at CSSConf that now that browsers can do gradients, and fancy borders, that graphic design has moved on from all those, and actually things that people look at today with flat design could've been done in 1998? Yeah, that's a good point, but it wasn't me that made it. Okay. But I think buttons are some of the things that do benefit from not having a flat design because otherwise the user is like, what's clickable? What's decorative here and what can I actually click? So I do like flat design, but for buttons I think it's a good idea to make it obvious that they're clickable. Exactly. Or link underlines, that's one of my pet peeves. I received a letter from a mailing list the other day and there were links in the body of text, but there was no way that you could tell that any of those were links. Yeah, and that's why we've been adding things like text underlined color to CSS, or is it text decoration color, I forget, which allows you to specify the color of the underline. Because many people were complaining that underlines are too conspicuous so they specify text decoration, none, but with these properties they can actually set the color of the underline. So let's add a little gradient here from semi-transparent white to completely transparent white. Yeah, it's not too bad. Subtle. Yeah. Works. And so that the way we've defined the styling of this button, we can change the color of the button whenever we want and it just adjusts. Wow! Okay, with a named color. With any color. I'm just using named colors because it's easier, but we can use any color. And let's fix the text a bit. That's nice. So if we make the text white, we could make the font weight bold, but I suspect that white isn't-- You have a tool to show where which color works best on which background. So I've built a tool to calculate color contrast, so if the background is yellowgreen and the text color is white, that's bad. So I could make, I could use an HSL color, and let's say something like this, that's even worse, wow. I didn't mean to have a saturation of that much. So it should it be more-- no. And I know we're looking for that button to be green, or at least not red, that tells us that it would be good, but are we looking for a number of one? We're looking for a number of, I forget, three. Oh, okay. We want something higher than three. That means there's a lot of contrast between the two different colors. Yeah, and it's only three because we have bold text of a certain size, otherwise it would need to be even higher. But the rules for contrast ratio require a bit of a lower number when you have bold text. So, I don't like this color very much. And this is one of those addicting color matching games. One could play this forever. Yeah. So let's settle on something. Settle on this, I think it should be okay. Let's see how it actually looks. Doesn't look too bad, what do you think? I like it. There is definitely a lot more contrast, and roughly a green. And let's add a text shadow to increase the contrast a bit more. (Typing) No. Wrong side. Not just, no, no, it's not just that. It's just way too obvious. (Typing) And I think that padding could be a little higher. No. Yeah, maybe, I'm going to get the other side. And I don't like the shadow very much to be honest. Oh, the overall button shadow. Yeah, let's make it a bit-- Okay, let's say, I don't like this very much, but let's say this works. I remember spending half a day doing that in Photoshop, and now we can just do it in CSS -- so much better. So now that if we try to press these buttons, they don't change state so that's quite confusing. So let's add an active style. So let's remove the background image from that, and the shadow needs to be different. So the box-shadow could be something like, maybe this, to make it look a bit pressed. So we want to make it a little darker? Or lighter? And actually let's cancel the outline because we're going to add our own focus styles anyway. So we want to make it look like it's actually pressed. Yep. but this is a bit too subtle maybe. (Typing) Something like that. Styling Labels and Rethinking Interactions But this doesn't quite-- All right, because we haven't added anything for the label. Right. So we also-- we want the label to have the active style every time the checkbox is checked, so-- (Typing) When the checkbox is checked, the label that's next to it, if it has a class of button, it should get this style, even when the mouse pointer is not over it. Speaking of the mouse pointer, we should add cursor pointer to those buttons, so that they get this. So now we should hide the checkbox over there, but we don't want to hide all the checkboxes, so let's hide this one. And would we also use--? We need to use-- we can't hide it with display none, or with visibility hidden because then we won't be able to interact with it. Yup. So we need to hide it by using position absolute and then clip. Oh, okay, I thought we would have to use those vendor-prefixed appearance-- can we use appearance none, or that's in a different issue? We could use appearance none, but it would still be there. Okay. So it's better to hide it in this way, because what this does is basically clipping clips the element, based on this rectangle you specify, so if you specify rectangle of all zeros it ends up hiding the element, but you can still interact with it. So as you can see here, I've hidden the element, but this still works as a, sort of, toggle button. Right, because there's no toggle input element. But now if we've styled it as a button, it needs to act as a button. So an act to start the animation, start the game. Yes, because we've designed our entire interaction based on the fact that it's a checkbox. So we need to change that so that, because right now it doesn't work in the way people expect. You would expect that if you press this it would start, right? Right. So maybe Next should be disabled once you click this, and clicking this would start the game? Do you think that makes sense? That makes sense, and also the Next should not display that Start label anymore because it doesn't do anything. Of course. That makes sense. Let's go to the button code again. So then if you wanted to just go frame by frame you would have to turn off autoplay, and then just use Next. That works for me. MmmHmm. So let's set this to disabled. So if this is checked we want this to be disabled, so that works. Let's style the disabled state first. Let's make it slightly-- And this could be only on the actual button object because we're not doing this to the button class? Yeah, exactly. It's the only one that needs that. So let's set the background color only so it still has the same style, but it's more grayish. Yeah. Ahh, that works. Now we can make it a bit lighter. You know? I mean we could test this again, but I think the contrast is sufficient. Yeah, more than enough. And oh, then there's a disabled cursor we can use for disabled buttons. I forget what it is though. So let's go up to the spec, CSS3-UI. Because we got some new cursor values that are pretty useful. Not allowed. I think that's it, the most appropriate one. So you can see how-- Ooh, you get the-- that's nice. And that's a standard CSS3 feature, pretty widely supported. And it's supported pretty much by every browser in use today. See, I should put myself to sleep by reading the spec. I might learn some of the things like that. Well, the problem with reading specs is that sometimes you read about features that aren't supported yet, at all, but if you know that something is supported, like the spec would be the first place I'd look if I know that something is supported. And then I would go to some resource like Can I use, or web platform, or MDN, to see if it has any support. And sometimes it's a good idea if it doesn't have any support, to open a bug report with the browser vendor, and ask for support, because that's how browsers prioritize what they're going to implement. Oh, user bug reports. For Open Source browsers like Blink, Webkit, and Gecko, basically. You can't really do that with IE, but they have their own channels. But they don't use bug reports in that way. But for Gecko, and Webkit, and Blink, I've done it and it worked. Wow. So, right, we wanted to make this act like a button. So if this is checked now, actually this is like a double negative so let's do this instead since we need both states. If this is checked, then autoplay and click next. Otherwise clear timeout. That should work. Let's set something. Using our convenient keyboard feature. So if I try to click next, ahh, that shouldn't work. Oh, so it shows it visually but it doesn't do anything. So I believe there's a pseudo-class for enabled. Okay, stop. So I believe there's a pseudo-class for enabled. I forget in which spec it is. So this lifter specification defines several user interface selectors, so this should be in the selector's level three spec. Enabled, yes, so there is a pseudo-class for enabled. So we only want to do this when the button is enabled otherwise we don't want it to respond to active at all. Oh, okay, so enabled and active. Yeah. So now it's not clickable at all. And we've tested that autoplay works, but do you think the interaction is good? Is it better than before? I think it's definitely better than before. The, I guess, some thought could be put into whether the Next and Autoplay buttons should each have equal weight like they do now, or do we need to somehow emphasize the one that people are more likely to use. But the interaction I really like. I think it's covered a lot of the edge cases and seems to work well. Animation Okay, how about we add some animation? Okay, so we want to wrap up with some animation. So the simplest way to add animation would be to do something like this. So if, so let's say, one second? That's a bit long though. Let's say half a second. So that, basically, tells the browser anything you can animate, animate it and make it last half a second, or 500 milliseconds. That's might be easier to read. So that will animate both the background and the box shadow. So now if, even when we're clicking it will have the animation, or-- it's even animating the outline so we might want to restrict the properties it animates. So let's see, so now it animates these as well. That's good. But what if we want to make it go through red when it dies and green when it gets born? Right, as someone who likes data visualization, I just felt like could we reflect that somehow if it's in the dying state, or the birthing state. So let's try-- I don't even know if it's going to look good, but we could try it. Yeah, we can't do that with transitions, but we can do it with animations because transitions just go from the start value to the end value. You can't tell them, so as you're going from white to black maybe do a detour and go through red as well. You can't tell transitions that, but you can do that with animations. Okay. So let's add a keyframes animation. Birth, we'll name it birth, and we have the-- so let's add a state at 50 percent. We'll have a background; it would be green; and we also want the box-shadow. Or, actually, since we have the box-shadow, why are we even using a background of black? We can just have the box-shadow. So, unfortunately, we can't just set the color. We have to copy the entire thing because box-shadow is not a short hand. Oh, there's no box-shadow color. Exactly, sadly. It's something people have been asking for for a while. I don't think-- it's going to happen eventually, I guess, but for now there aren't any discussions in the CSS working group about that. Sadly. So now we haven't applied this anywhere yet, so let's use an animation property, but now it will work in-- so let's apply this in the checked state, otherwise it would show even when the board is created. So let's say, birth, 500 milliseconds. Oh, so you can use-- okay, you can use the keyframes there in the animation directive. Yeah, I'm not sure how well this would work though. So now it just snaps to life when you click it. Yeah, apparently. Which I think is good. I think that if I'm interacting, I feel like I want instant action. Actually, it doesn't seem to work. Not much animation. Nope. Oh, maybe—does Chrome have unprefixed animations in this version? I use prefix every so often; I've forgotten which browsers need prefixes for what. Right, and that's your plug-in to--? Yeah, it's a library I've written that allows you to write CSS without any prefixes. But I didn't want to use a library here so we'll have to do everything by hand. Style, animation, yeah, it's not there. Webkit Animation is there. So we have to do everything twice. So let's see, and this would be webkit keyframes, and then we should copy it and add the prefixes as well. But let's see if it works first. Oh, we got a green pulse. Yeah. And I think, okay, we don't have an animation when it disappears. We could probably-- Can we see that just with the birth animation? No. I feel like-- no, okay, well those were the ones that were added. Might get a little crazy if we have red on death, and green on birth. This might be enough. You think so? Or we could leave it as an exercise for the reader? Yeah. Yeah, I guess. Is that as good or as bad as you thought it was going to be? (Laughing) I can't decide if I like it or not, but I think it's an interesting effect. Let's not forget, though, that if we're using prefixes manually we should also do this. Do the right thing, yup. Yeah. Because every other browser supports animations unprefixed: Firefox, IE, so, even Safari, I think, I'm not 100 percent sure about that. Okay. So we should never forget the unprefixed version. Wow, we've written a lot of code and a bit of CSS in that time, had a few other tasks for the reader, of course. They could add the red dying animation or have an adjustable board size. Because now the board size is hard-coded here in this line, so this number could be set through some form control. Or you could also have a slider that sets the time, when it's autoplaying you could have a slider that sets how frequently this happens. Because one second might be good for some things, but might be too slow for other things if you just want to go to the final state. That's a good point. So, now that would be tough though, because then we'd have to make sure that all of our animations fit within the speed that was being specified. Yeah, or you could have a reasonable lower limit, I guess. Like 100 milliseconds as a lower limit would probably be more than-- That makes sense. It could still be visible. Yeah. And then we could have a button to clear the board if you just wanted to start over. Yeah, that's a good idea too. That's probably not too difficult but it would be interesting. Or another idea would be to use local storage to save the last state of the board and then when you refresh, it would show you the last state, so it would continue where it left. But you definitely need to add a clear button then because there's no way to clear. Right now, now I'm refreshing to clear, but if you did that then I wouldn't be able-- if we implemented that, I wouldn't be able to clear the board by refreshing so there would need to be a clear button. Right. And then for the more zany features, we could have it tweet every time it went through a lifecycle. Another good idea would be to make it detect when it's reached a point where it will just repeat indefinitely. Yes. And show that to the user. Some kind of stale mate. Or if you've reached a point where it's a still, so Wikipedia has some information about still lives and oscillators so it would detect those somehow and show it to the user, or it could count the number of generations and display that somewhere. There's so many things you could do. So many-- Or my favorite, making it kind of a Rubik's cube thing to where you have six of these, and they could may be even influence. Wrap around. Wrap around, yes. That would be a cool use of 3D transforms. Yes. Plus a little extra matrix math in there somewhere. Yeah. Well, thanks. That was fun. It was. Course authors Lea Verou Geoffrey Grosenbach Course info LevelAdvanced Rating (480) My rating Duration2h 15m Released19 Jul 2014 Share course