JavaScript Snake Game Tutorial Using Functional Programming

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in this video I want to take you through this snake-like game that I've built using javascript and more precisely JavaScript in functional style or in functional programming style so with a focus on purity small functions and composability so as you can see here now I'm running this snake game in the browser and ah you can see also there that I actually crashed sorry my camera is in the way so I can't really see what I'm doing in the snake game so butBut this is the browser implementation and because I tried to put the focus on modularity it was kind of trivial to make one sort of GUI for the browser and then another GUI for the terminal so if we jump over here to this side you can see if I just look at where I'm at now I'm in a folder called snake where I have a few files and what we're looking at here in the browser is this web dot HTML file but what I can also do is I can run this CLI dot J's file through node and then we can play snake in the terminal so if we if we run a CLI through node you can see that we now get snake but in in sort of terminal mode let me make this a bit bigger so the XS represent the snakes the dots are the grid that's traversable and the o's are the apples that you need to eat in order to become longer and the interesting portion is of course or the interesting part is of course that this runs on the same quotation marks' engine like we've separated the game engine from from the visuals of the game which is generally what we should do right we should separate presentation from the business logic so my interpretation of most of these snake tutorials or actually game tutorials that you find on the interwebs is that they are focused on just getting a quick version of up and running quickly essentially but what I want to do as I tend to do with all of these videos is instead talk about design so I'm by no means saying that this is a perfectly designed snake game at some point that just said okay never mind let me just drop this make this video and I'll leave it to you guys to hopefully improve upon this game and make it even better or rather I should say improve upon this code and make this code based even more module and elegant that would be fantastic so if you do have comments please do shoot those in the comments or if you do fork off from this codebase and make changes please do post your changes in the comments that would be really interesting to read but anyways when we focus on modularity we can again trivially achieve this kind of thing where we have one implementation for the terminal and one implementation for the web which of course if we generalize this notion what we're talking about here is that you might have a single core and then multiple different implementers of that core or users of that core so that might mean that you could have let's say a multiplayer portion of your game or you might expose your game through an API or you might make an iOS version and an Android version and a web version of your game and so forth and now I'm saying game but actually we're talking about applications right so generally what I usually want to talk about is its design of programs is how we keep programs alive over longer periods of time and how to increase modularity and so in some sense then this game is just an example of that but but anyways you could also just see this as how to build snake using functional programming in JavaScript let's get going so let me cancel out the game here and let me make this fullscreen okay so again let me just show you the files that I have in this folder here so the main snake implementation is in this file called snake j/s that's sort of the core library or the game engine in some sense and then these three files web dot CSS web dot HTML and web KS that's extent essentially that's essentially the web interface for the snake game so that's what we were looking at when we were playing in the browser those three files and then this CLI dot JS file that's the node implementation that's that's what we're looking at up here I thought that's the terminal GUI for for through which you can play the snake game and then finally this base j/s is some base functions so essentially when doing functional programming as I've talked about in other videos we tend to pass data last and we tend to have functions that are curried so we tend to have functions that are partially appliable but of course javascript doesn't work this way so that's why I implemented a few functions myself such as map and cetera and of course like if you would build an extensive game you would probably use a third-party library such as for example Ram de Jas or another library implementing the Fantasyland specification but for the sake of this video I really wanted to keep everything self-contained I don't want to use any third-party libraries and this is why we have some code in this base in this base file then and of course we have this readme file just because I've put this up on github and by the way you can find the link to all of the code in the description I might have moved it elsewhere but the link in description should work let's actually start by looking at the web and so let's start with web dot HTML so let me open vein and then let's open web dot HTML and as you can see I mean this is clearly just any old normal HTML file where we we are loading in the the style sheet here I'm doing some funky stuff I'll explain that in a moment but then in the body we have a canvas setup that's of a size 700 and of a height or over with 700 and of a height 500 and then I'm loading these script tags and maybe it's maybe people nowadays always load their scripts here and then make sure that the DOM is load that in the scripts files I was just lazy and put them here to ensure that when I load this web Dutch is the canvas has definitely loaded but yeah that's a completely different discussion but anyways you can see here that I'm loading in the base library then I'm loading in snake and then I'm loading in web dodge is so so clearly I mean this is because I don't have a build system setup or I don't have a build process setup so I mean clearly if you were building this and actually shipping this on the web you would you would probably somehow like minify all of your or all of your files into a single main min-jae's file right and you'd only load that and I would do everything so you would come construct a single JavaScript file from all of your other JavaScript files but now this is just a simple example so let's not bother with that right so then we're loading in the base library and the base library will expose a bunch of Constable's or it will essentially define a bunch of functions that are available on the global scope and same thing for snake and then web is the actual machinery of this web of limitation right shouldn't say the machinery is the actual main entry point of this web application and then let me just quickly explain this I mean this I do let me actually let me open up a split here and open up snake Jas in order to show you that so snake Taj is because I started with the the consume with a command-line implementation actually from the beginning I was thinking that I only should do to the command-line implementation but then I figured actually it's so easy to make another web implementation so let me just what make a web implementation as well I'm a big fan of trying to keep it as absolutely simple as possible from the beginning and this is why I was actually I started building this snake DOJ's file and Anna and I started doing it in a sort of step-by-step fashion where I implemented one function at a time or it's like okay this function needs to be able to do this rights like if we have a list of snake points and we want to compute the next next snake point given a movement in some direction what would that function look like right then I did that step by step for all of the different pieces and then I'm sort of from the ground up built the the snake implementation but anyways because I did this I want to make use of the module system in node so you can see here in the end that I say module dot exports and then I export a bunch of actually first constants and then three functions and same thing goes for this base libraries if we go to this base library here you can see that this base library exports a bunch of functions so clearly module dot exports is not available in the browser if you're in a similar scenario with some production code you should of course look into something like browser UI or maybe there's something more modern now but but but now I mean we just do it we can just hack it up so so I just simply said that in the global scope I said that the require is a function that returns an empty object right and module is an empty object and module dot exports is also sorry so so let me also say I mean I said require here but I said that the problem in in this right hand was the module exports but actually we have so in snake for example snake actually does require right so so snake the snake implementation or the snake game engine depends on the base functions the functions in the base library the base library helper functions so here we make use of the word require so so the browser does not have the word require and the browser not have the word module nor module exports and when I say word I'm you know of course I mean keyword sorry and so anyways we just say that require is a function that returns an empty object module on the other hand is an empty object and module that exports is a function that returns an empty object just just to make them not fail right they're not doing anything useful and then I'm relying on the fact that I know that both base and snake and web are all defining the functions like this right here's a function here here are a few other functions here are a few other functions and so forth so so if I just run these at this code in the browser they will be exposed as global functions so just a quick and hacky way of getting this to work but anyways that's the web right so what it does so you can think of this as that web is actually the entry point or web KS is the entry point and then web law Jas needs to first require base and then require snake and this is why they're in this order or actually I mean to be perfectly frank rather web needs to require snake and snake needs to require base that's actually the more proper way of saying it so that's that's web HTML let's let's let's now look at web KS right so so what does this actually do so we don't have a ton of lines here right let me just scroll through this file you can see here at ya 53 here I'm saying I mean I added a comment here to say that this is the main portion I'd say let's actually start from this main portion so what web dot J's does so so the JavaScript that will be run in the browser when we run the browser version of the snake game right first we do draw so these are actually two lines right sorry I just like I thought it was pretty good to put them on a single line so first we do draw one initial draw and then we say requestanimationframe I don't actually really know what that is but but it's something like well the next time the browser will redraw redraw using this function please or something along those lines so people who develop games tend to use these so when dotard requestanimationframe and then we pass the function step or the function that's returned by invoking step with zero and so importantly usually people do this right they pass a function but I'm actually invoking the function step because step when invoked will a new function and the reason for that is to avoid one global or another global so so step needs to keep track of how much time has passed since the last step right because so if I'm not mistaken like I mean requestanimationframe might be called different times depending on the I guess framerate or something like that of your browser or the frame rate that you can manage to get on your browser's like depending on the performance of your computer your the the requestanimationframe will be or rather the function that we pass the requestanimationframe will be called with different intervals different time intervals depending on the performance of the computer that you're running it on so in every called two step we need to check the Delta we need to check the diff between the current time and the last time we rendered and that's why I passed this first variable because here I'm saying then that or this first zero because I'm saying that the time since the last redraw was zero so we're saying let's instantly redrawn so so here you can see then that here's the definition of the step method right so the step method or the step function I should say sir it takes a t1 so a time step one our time stamp I should say it times time one at a time step two and then it says okay what's the difference between times time to and time step one and if that's greater than a hundred and I don't really know let's think about this so I can't really remember but I assume this is in milliseconds so that would mean a hundred milliseconds right so a tenth of a second so every tenth of a second we enter this portion and all of the other times we enter this portion and now you can see how this works right because if we do enter this portion if there if if one hundred milliseconds has passed since the last time we we update it order the last time we stepped then what we want to do is we want to say this state global this global state variable argot I'll get to this in a moment equals the next state given the current state right this is kind of typical functional program right instead of doing mutation we're just saying that we have this global state variable global I mean you don't you shouldn't have a global state variable but but like but I mean in this case it happens to be to be global but but essentially the point is containing your state into a single variable and then passing that state through a function that produces the next state right that given state so instead of saying well here's my state here are the actions that I want to do let's change all of the variables so that it matches the new state because then some things are more difficult to reason about because we have mutation all over the place instead we say well here's the old state let me pass you that and then if you compute what the next state would look like given that old state well then I then I know what to do with that right so that's what we're saying state is this this state variable and we say next is a function from snake from the snake engine and when we pan and when we passed state to that it will compute the next state and then we will set this set the state here this mutable state here to this new state and then we will do draw right and draw I haven't even bothered about trying to make immutable in any kind of sense because like like the canvas libraries or they're like the canvas and api's of browsers are like there are tons of there's tons of mutations that would be extremely painful maybe there is a way to do that if you have an idea of how to do that please do something in the comment that would be or in the comment section that would be very interesting to read them out anyways so that's for the if branch or for the true case right for the phone's case what happens well in in the false case or in other words when enough time hasn't passed 100 milliseconds hasn't passed since the last time we updated then we simply do my camera is in the way I can see but but we simply do the same thing or we say well we're a rather i should say we don't compute the new state and we don't read run importantly but we do request another animation frame and to that we tack on or we throw in a function that's that says step again from t1 so notice the difference between this line and this line right in in this line we have t2 and in this line we have t1 to do and t1 and t2 here is because we want to have the new point that the current timestamp as the as the next time step but then we want to compare against also like as the as the next time step that we want to say has a hundred milliseconds passed from this timestamp right that's what we want to do when we do redraw but when we don't redraw we want to compute from the old timestamp we want to say well you started at timestamp zero now 50 has passed 50 is not enough so keep on keep on waiting but you should compare against zero and not against 50 because if we if we keep updating this if we keep changing this to t2 here if I said t2 here we would never redraw it maybe in the old case if it's like it's super laggy or something like that right but but assuming we have good framerate and then we would essentially never call this and then that'd be problematic so this is why we're saying t1 and of course I mean maybe this wasn't clear but what requestanimationframe does is that it passes a timestamp and that is this t2 then and I don't actually know I mean it's not it's not like it's a date because we start from zero but maybe it's something like the time since the the this browser session was initiated there is something like this I mean this is the page was van der it I should maybe say but anyways I mean I have no idea so they don't take my word for that but that's that's the update method but actually let's go up to the top here because I feel like what I what I didn't say is this that the state variable that we talked about is defined here so so note this comment that says mutable state so this is so this is the state variable that contains the state of the game at all times and it's a global quotation mark like it's it's shared across all of these functions in this file and and what you can do is is that the functions or actually the functions don't there are two functions that change it I should say right there's the there's the step method that changes it but there's also this key events thing like there's this listener for key events and that thing also changes the stage right so actually this is let's look at that at once right or or wait first let let's begin here in the top so we can say okay so we say let's state is equal to the initial state and where does this initial state function comes from I mean this is the problem of working with Lobos it's very difficult to see where things are actually coming from since they're not actually sculpt I'm realizing now maybe I should have scoped them so that instead of making these functions all functions in this file I could have put them inside an object and that would give us a namespace here so that we could say snaked out the initial state because that that's kind of actually what we're looking at here like if you would use require and you would get that for free but but anyways yeah I should have fixed that maybe yeah but anyways that's from the from the snake dot J's files so snake the J's file a function called next here all right then we'll talk about this when we get to the snake J is filed but but but suffice to say for now that the next function takes a state and computes the next state given that state oh sorry who's about to talk about initial state and not about next but that's that next right that's what next does but if we look at the initial state that's just a function it's a I think it's called a unit function like it's a constant in some sense so it's a function that takes no arguments so it's no worry and it returns an object that is essentially a state representation but it's the initial State so the initial state is essentially always the same so when when you start a game you need to start from the initial state so yeah that's the way we invoke this this state variable now let's go back down here and let's now talk about these events so we say windowed alt so now my camera is really in the way add add eventlistener key down so that's the first argument and then the second argument is this function that takes a single argument which is this variable named EU which is the event and then we can extract the key variable or the key property from that event and then we switch case over over essentially that variable and then we say okay well we want to be able to control the snake using WASD the keys and H hjkl so here's the WASD and here's hjkl but then we also want to be able to use the arrow keys right so and depending on which one of these we press we want to compute a new state we want to say that the new state is what the old state was but plus quotation marks plus this new direction so these are constants northwest-southeast these are constants that i've defined in this snake dot JS file where we have north and south and east and west right then these are essentially just I guess maybe you could think of them as vectors I'm not really sure but like they have an x direction and y direction or a velocity in these directions right so north means moving zero x but negative 1y that's the definition of north we need context in this game but but anyway sweet so we want to queue those in and we need to pass the current state because again remember the snake game has no clue of the current state that we have right it's it's a actually I should say this it's pure except for that the snake snake OJ's final has some randomness right so we have one function that does some random stuff and I mean remember randomness is is impure because it relies on things such as system date and etc so given the same input you won't get the same output but I mean how is pragmatics here it's just thinking like there's no point in trying to do something more complex here in my mind it makes sense for a function that computes a random position within a grid to be within the snake that Jay has filed and not elsewhere but anyway zero clearly solutions for that but I'm just being pragmatic but but essentially so so then the snake and queue method needs to be passed the current state we need to pass the current state as we do here right and we need to do that because as we do here because again it's it's almost pure so it doesn't know what state we have it doesn't keep track of its own state and and Q is also defined here in this snake snake method and again we'll get to this when we start to talk about the the snake stuff but essentially as you can see here it takes the current state and it takes a move it asks whether the move is valid and if the move is valid it does this if it's not that does that right so if it's not valid it just returns the same state it does it does no changes but if it is valid then it says okay we want to merge yeah we want to compute the new state that is the old state plus that this moves property has changed to the old moves concatenated with an array containing only this new move so this was an extremely complicated way of just saying append but immutably append the last move to the list of moves but we haven't even talked about the state tree so we'll get to that in more detail but that's in queue and this is sort of the basic game right like if you think about this game first from the from the main here we draw one initial time and that sort of does the first drawing but then we say we'll start stepping from 0 and then we keep doing this step path ok step and step and step and step and so forth and whenever we've it's been a hundred milliseconds since the last step we compute the next stage from the old state and then we redraw and then again we just keep on looping rotation locks keep on requesting animation frames so that's that's sort of update and draw but then there's also this other exogenous force that that can enter I'd like there are also key events so like when the user types keys we listen to this and if you happen to type a key that's that's one of the arrow keys then we need to compute a new stay so so then we're also saying well you need to set the new state to the old state Plus this new key that's being pressed so that's that's fairly clear again like the part that the benefits of mutation or the benefits of avoiding a mutation is that whenever you want to change things usually it's pretty easy to change things because you don't have to think about stage so for example I'm not sure if you noticed but when we played this game well sir of course how could you've noticed it's not actually visible and think about that but the way the game worked from the beginning was that if I'm travelling in the direction west as I'm doing now and I press East right I'm traveling leftwards but if I press the opposite direction if I press East right or conversely if I if I'm traveling to the right and I press the left key so if I press the inverse direction or the opposite direction of the direction I'm traveling and I would instantly crash because it's like the snake is moving in this direction right and it's like it's it's immediately just going directly into its own tail right and something games actually did work this way and was extremely frustrating and that's right no this year as well like when I was playing this it just messed me up all the time because I kept on crashing what I wanted to try out the game so I figured ok let's actually remove that functionality from from my sneaking and that's when I added this valid move portion to NQ so previously NQ used to only do this right it used to only just merge in whatever motion you and you added into the queue of motions or into the queue of moves but now I said well actually let's let's avoid this sort of inverse situation so if you pass the inverse so that's what valid move does is it checks whether it's the inverse and if it's not the inverse yes then it's melded but if not if it is an inverse and then we will ignore it and we'll just pass the old state so will not queue the move right so it's these kind of that we often find ourselves in when we use immutability like when we realize okay we'll be cool if the game could do this or actually this is this is a tricky thing let's let's not actually make it do this let's make it do that instead right and then we try it out and it actually turns out to be fairly simple most of the time but anyways okay so so so this is the hole this is the the web portion right we have we have the step function we have the event listeners we have them you here in the main let me just put that back on one line we have this main that that initiates it and then we haven't talked about what draw actually does right so as you can see here is this the comment says game loop draw right so so this function draw and it's also pretty simple like as you can see from the comments here like I was contemplating separating this into multiple functions but given the mutable nature of the canvas library and like given the fact that we probably won't reuse this code I just figured whatever let's just make this let's actually just make this a long function that does does all of these things like one by one so what it does is that it clears the canvas right so so CTX here is another global variable because yeah what to do all right so that let's actually start from the top right so the first line says okay let's let's make a constant variable called canvas and let's from the document get the canvas or let's get the thing that has the ID canvas so if we look at the HTML thing you can see that the canvas here actually has an ID of canvas just to make it simple for myself right so so I grab that canvas and then we get the context where that's that we can draw so so in CT X's is a context variable then we have these position Albers here we'll talk about these in a moment but what we do then is that we first clear this canvas by saying okay let's set the fill style to this like a blackish color i guess and then we we draw a rectangle over the hole over the whole width and the height then the next step is that we draw the snake right so then we say let's set the fill style to something which is greenish and then let's map over the snake so this is where it sort of gets interesting right because it's like the different graphics engines or the different gooeys or the different sort of interfaces can have completely different ways of interacting with but but so the state exposes that snake it exposes actually lots of things like like moves and Apple and and so forth and so forth but but we're just going to make use of snake and of Apple so what we do is just that we map a function over state of snake and so snake is an array of positions so it's it or it's it's a it's an array of points points being objects that have x and y coordinates and of course this is not an actual proper map because I don't care about the return result so this is actually more like a foreach that actually mutates the canvas but map was a shorter word so whatever any and you should these iterations of the function what we do is that we say well using this fill style of fill a rectangle in the context from this x position this Y position over this width and over this height and now you can see why these x and y methods and these x and y methods are just helpers for me so that I'm not duplicating the same math all over this place so if you remember what we did was that we drew rectangles right so notice how the snake so every point in the snake or every piece of the snake or just think of the apples for example take up sort of one quotation marks pixel and a pixel meaning like traversable pixel in this grid maybe I should say tile actually like it covers one tile and so what we do then here is that we compute the position of these tiles so X 1 here will return the width of one tile and Y one here overturn the width of one tile and what happens when we do this Y of some position is that if if the y coordinate is zero then we'll put the little turn to zero right because if we look at the implementation of Y here we say take the canvas height and divide it by the number of rows that we have in the States so like if the sorry I forgot to say then we have the are here as well right so so if R is zero or being the the y coordinate right so let's say that the y coordinate is zero and then we multiply that with the height of the canvas and let's say that the height of the canvas is and then we divide that by let's say 20 which is the the number of rows let's say so the number of tiles in the y-direction now this is not necessarily correct with in regards to this particular scenario but just hypothetically it makes sense then you can see that we get 0 right because that's a zero at position like that's the pixel that we actually want to draw from but if we take 1 we get 5 if we take 2 and we get 10 and so forth and if we take 20 we get a hundred because like that stuff that's the last pixel and actually 20 doesn't exist if we have 20 it's 19 would be the last one because its height will also be 5 and then 5 will take it up to 100 but anyway so those are just two helpers in order to to not have to duplicate that as you can see we use these x and y things here as well so that's the way we draw the snake and then we do that for the Apple as well so we say okay here we don't have to actually iterate we just say fill rect at this x position at this y position using this within this height right and the window height is the same as the Apple it's as large as a tile just as all of the pieces of the snake is is as large as a tile but the position is based on the on the x position of the Apple right so so the x position and the y position so we're saying compute the Y position from this coordinate which is defined by state dot Apple dot Y and then finally we draw the crash so let's just think about this unless let's just look about that at that right like if I if I crash into my own tail notice how the screen flashes quickly read write or quickly flashes in red there you go I did it quickly flashes in red and how we know that that has happened is that I decided that well if we don't have a snake in the game field that means that either the game is just started or there was a crash actually if we just refresh the page you can see that it blinks red and maybe this is an unwanted feature and we should try to solve this but I just figured whatever let's just not think about that now so if there is no snake right so essentially then we're saying that if state dots neglect is equal to 0 or so if we don't have a snake then we should essentially draw this red rectangle over the whole screen and so that's the way we're in the crash and that's the whole drawing thing and I mean a moment to pause and reflect I'd like like the point here is that this is all graphics right like assuming that we can use this state structure for all of the graphics engines assuming that state trees gives enough information this can be completely focused on just the visuals right and it is not sort of intermingled with all of the business logic so if we say I actually just change the color of the snake it's trivial to find the place where we can change the color of the snake or let's make the apples round it's trivial to find the place where we make the apples round instead and we don't have to worry about accidentally messing up some of the other core business logic because that's in a completely different place so we have to be really silly in order to mess that up so don't waste that that's the draw that's the draw function and I think actually we've covered all of the stuff here here that write like first we just grabbed out the canvas we set up this mutable state that's based on the initial state we we construct these two position helpers that will make us position things in this grid or a visual grid in the browser then we construct this draw a function that will take the mutable state or based on the mutable state draw a bunch of stuff on the canvas and then we have this game loop that whenever it's called checks whether it has passed enough time in order to progress to the next state and and redraw or whether it should just keep on ticking and then we have these these listeners that listen for events or listen for keystrokes and produce new states based on these keystrokes where the new states contain the new keystrokes or contain the directions that we want to move in and then finally we have this this main that sort of just initiates and starts the whole thing up and of course I mean we didn't actually need this first this first draw here in the in the end right like the only difference if we have that or if we remove that you can see that there's a quick flash of 100 milliseconds where we don't have a board that's essentially the only difference and that's why I added that because then immediately we should get the board actually now that I think about it's it's almost hardly visibly like you can see I mean loading the page probably takes quite some time anyway so so yeah I mean you might want to remove that okay but that's the web portion let's now actually look at the snake core library it's probably easier to start with that and then look at the command line implementation so let's go to snake dodge a s so again this is we've glanced into this file a tiny bit but but not too much so I think actually there's too much code here right so let's start from the top let's just scroll through quickly firstly to get an idea right like we have about yes 61 lines of code and if you have ways of making this again more elegant and more concise not concise as in cold golf as and just for the sake of conciseness but as in elegance then please do something in the comments that would be massively interesting but what we start by doing is just requiring this base library that we've defined again as we've talked about with some helper methods I'm not sure if we actually ever looked at that but here's the base library right yeah we did quickly look at that but we just define a few a few functions such as for example map and as constant and as identity and all of that stuff but let's forget about that for now right but we load the functions from the base library and then we do this and this is kind of just me hacking again so I apologize for that but what I wanted to do is so base for example exposes the function map right so you have base Dalton map but whenever you use I don't want to have to say based off map I just want to be able to simply say map and refer to based on map because they are helper functions I mean in some sense you could argue that that some of the functions defined in base here are functions that should be defined in JavaScript if JavaScript was actually aiming to be a functional language like so functions such as Map Reduce are defined in the preload of Haskell so they're defined in the base library of Haskell reduces actually called fold in Haskell but and not just that I mean a ton tons of other methods such as identity and constant and lots of other things some of them are not like prop for example like because Haskell is a statically typed language and javascript is dynamically typed language so if we look at the base library here for example I defined the helper method prop which essentially takes a key and then takes an object and extracts the value under that key from that object and that's trickier to define in a statically typed language actually now that I think I'm thinking about it actually it's completely defined upon a statically typed language is just probably not as use all right because you're probably using static typing because it gives you benefits and when you're starting to use this sort of key value pair syntax this dictionary syntax then you are exposing yourself to runtime errors because you might end up seen situations where the key doesn't exist anyways that's tangental what I wanted to say it's just that in JavaScript because of the dynamic nature of JavaScript some of the functions that we might want to put in the base library quotation marks it makes sense to put in the in the base library but but might not be functions that we want to put in the base library in other languages but anyways this is total sidetracked that's why I'm doing this right just to expose all of those functions so I can say K and ID and map and so forth and not have to say base dot map and so forth so that's that okay let's move on then we have these constants right these constants we've already talked about we need some way of talking about the different directions and maybe you could argue that these should be functions that or again like yeah nah Larry functions that take no arguments and return this but anyways I just defined them as constants then we have this point operation and actually because thinking about this a while like this is not excellent because if you could check for deep object equality right like I think in many of the JavaScript libraries you can do that and many of the testing libraries you can it's actually like object deep equals or deep equality of two objects is actually a very useful thing to have in languages so actually I mean the implementation of off point equals should essentially just like if you pass point one and you pass point two then the implementation should essentially be 0.1 equals equals 0.2 if if equality in in JavaScript was based on the deep structure of an object and not the the reference to do that thing in memory or the the pointer to that thing in memory but anyways I mean and even if it didn't look like this it should perhaps be the deep the deep equality between p1 and p2 and maybe if we do that in the current fashion it would look like this right and actually if we have that then point equals is essentially equal to deep equals if you think about that because if I put both of these at the same time you can see I mean what we do is simply that we take one X here one Y here we pass the X here and we pass the wine here so we're just applying that function so so then it's really it's the same thing like we don't even have to take any arguments we can make it point free and and that's the reason I'm complaining about this like point point the quality is nothing special like I want to say that the X point equals the X point at the Y point equals to my point at the two of the two things or not the point I mean the X property of 0.1 equals the X property of 0.2 and the Y property of 0.1 equals the Y property of point two like that's a check I want to make but that's just a check of equality so that's why I'm saying like that's a bit silly and I was disappointing but I think it was just overkill to implement deep equality a function of deep equality in in the base library but at least now you know anyways that's my one so so then we have a bunch of boolean methods right so so what I mean here is I mean a bunch of functions that take some particular or potential arguments but also also the the current state and then given the current state answers questions so the questions that we want to have answered is whether the snake will eat upon the next update given the current state and whether the snake will crash given the current state in the next update so if we have this current state and we have a list of moves so we have a list of left right up down etc given the next move and the current state will the snake crash that's that's the things that we're asking here and then this thing is of course as we talked about before whether a particular move is valid but that's before we enter the the move into the state so I mean maybe we could have done it in a way we could have just blindly added moves into the state and then have a function that computes a clean state without any invalid executive move combinations or without any any sort of inverses in the list of moves but I but I chose to do it this way maybe the other way would have been more elegant but so you pass a move first and then you pass a state and then it says yes if this move is okay given this state so if it's okay to add this move to this state and the way we do this is just that we say okay well there's all this thing about this we do logical or between this thing and between this thing right so let's just let's put these like this just temporarily so that we can think about it right so we're doing the same thing twice we're saying either it either something holds for for the x-axis or something holds for the y-axis or for the Y direction and if either of these hole then it's a valid move and what needs to hold is that the X velocity of the move of the of the last stacked move of the of the next move that we have when when we add that to the new move that we want to add that needs to be different from zero so so what we're saying is that if we are moving in X minus one then if we add one to that then that would give zero because if we're moving in this direction and then we add one so one would be this direction that would be the polar opposite of that thing or of that direction similarly if we're moving one and we add negative one we get zero because we're if we're moving in this direction and we add this direction then these cancel out and we get zero all right and that's the scenario that we don't want where we're crashing into our own head but we're saying that either the x axis need to be different or the Y needs to be different I mean since you can't since you can't move diagonally yeah this is in some sense probably overly complicated because clearly if the y axis has a value then X will be zero and if the x axis has a value or if the x direction has a value then the the Y will be zero so there's probably a more simplement implementation that you could you could look for so let's go backwards by putting this back on one line and then let's look at the next stuff actually before we go any further let's let's look at this stage right so initial state let's go here I mean we said previously that there's a function called initial state that returns the initial state that all of the other states are sort of built from so in the initial state we have just a few things right we have some number of columns and some number of rows and these are just in order to I mean maybe it's not the best place to store these because they won't change or they shouldn't change over the over the course of the game but maybe if you would think in terms of levels and maybe this could actually change but who knows but then we have list of moves and the initial and the initial state we start with east in the list of moves as you can see right like when we refresh the game we always start from the same position here and we always start by moving east and that's because the default move is east and we can't see here because the default snake is empty but what happens after a crash is what causes the snake to start at - - but we'll see that later and then we start with the default apple actually at 16 - so maybe that should be a random position but for some reason I chose to put it there so again you can see that the apple always starts here so will always automatically catch the first apple right by just leaving the snake to do its thing so that's the initial State but let's now jump back now you know you know sort of what that contains and of course the snake thing right like we were saying the snake is an array and it is an array of positions or points right so things that look like this right then have an x coordinate and then have a y coordinate and this is why we're saying point equal or this is why is a point because we call these these things that have Delta X and Delta Y we call these points so yeah actually so let me jump down I think it's actually easier just just to follow along from down here instead instead of going line by line because the order wasn't superb so let's actually just look at the next method here instead so when we looked at the web implementation if we go over to web j/s here you can see we talked about how we call how we call the next function here right when we step the game we say the new state is equal to the old state ran through this the next function and the question is what is the next function do well the next function is and I make use of this library method called spec which the library Ram des I think they call it apply spec or something like that but I just called it spec so you can see spec is a fairly interesting method it has this and has this slightly tricky come implementation where it takes an object and then it takes some value and then it grabs the keys out of this object and maps over the keys constructs a a new object from that key and runs the function that hides under that key applies that function this X that we passed to this value that we passed and then it reduces over merge so it simply merges all of these different different objects that it produced into a single object is a pretty complicated way of saying that essentially what we do here let me just I mean if I would retype this implementation we could retype it as that let's think about it next is a function that takes a state and returns a new object where the new object if I had a spread syntax I could say is the old state right plus or actually let's not use this person let's be explicit so takes take some state and and and returns a new object where Rose is equal to state on Rose calls is equal to State DOT calls moves is equal to next moves while being passed the state and snake a snake and next snake being passed the state and Apple is next Apple being passed the state so so that's essentially that's essentially what we're doing and actually I mean if we would express this in function syntax as well these first two just to make it clear really that why we can use spec and it's lower implementation is because this is essentially been just using the prop method that we talked about before so if I say prop here pass row yes are like this right I passed these props as a string and then we pass the state as the second argument and sorry I'm using curried or functions that allow partial applications we actually need to invoke the function like this so that would be the same thing and now if you look at that right if you look at that implementation you can say oh okay well actually next is a function that takes some state produces a new object where the state needs to be passed to all of the functions rather I should say it produces a next is a function that takes some state and produces a new object where this new object contains a bunch of keys and to determine the values under all of these keys we need to execute a few functions but these functions they are not no Lurie we need to pass them and the argument that we need to pass is essentially the state like if you line this up you can see all of them are past the state so essentially what we did is just that we we sort of made it point three we remove this portion not this how we do this we remove this Boop and then we say okay all of these need to be past state okay but that's that's why we have the speck method so let me say speck and then we pass state here okay that's that's also the same thing but then we're back into the scenario that we looked at before where it's like well actually next is now a function that invokes this this function spec by just passing in the X that we pass it right like next is a function that takes an X and runs another function through that X or over that X and then we don't even need to specify the X we can just we just remove the state here like we remove the explicit call and then but then we remove this explicit variable here and then we just say next is the running of speck over these functions and then we pass it some states so I mean I would argue that this is more clear but yeah I'm totally empathetic to if you have a different opinion like as soon as you start to learn a few of these really common functional programming I wouldn't say parents but like functions essentially these these common functions that we tend to use all the time like constant and ID and map and reduce and pipe and compose and I would argue for dynamic languages this method is this is like super common or this function spec or apply spec is super common so we start to recognize these I actually think this becomes the easier version to read but there's a whole discussion about whether point three is is sensible or not I can shoot some links in the description if you're interested in that but anyways let's move on so so that's that's the next method and we've already talked about in queue what in queue does is essentially that you pass it a state in your pass in a move and if the move is valid then you merge the move into the list of of current moves that you have stacked up right so so the reason we're stacking them up by the way is that if think about it what we can do ah sir let me collect a few of these just to get a tail but what we can do is that we can stack moves like this if I just mash the buttons sorry now I have a tail now I can do this okay so if I just mash the buttons and then I let go that let's see if this works it's not crash I just mash the buttons and I let go you can see that we've stacked a few moves and the reason we do that is because of if you've ever played the game of snake you see like literally in the Indus anarion right like now I just want to go right down for example like immediately right and then immediately down and I think I could just queue down after right so I just press right down and then I've cued it so like if we didn't do something like this maybe we wouldn't be able to get both of them in action now that I think I'm gonna that would probably work still I don't know maybe I'm some opinion opinion about that I mean what wouldn't work is this sort of stacking but then again this stacking isn't necessarily super useful I assume but anyways let's get back to the code that was the original intention of why I did it as a list so yeah not not thank you so we've talked about in queue we've talked about next we've talked about initial state and what we I guess what we should do now is just talk about well first let's talk about these so the next state is computers from the old stage right and rows when we do prop rows and prop calls as we looked at in the other implementation that just equates to the same value like we just say this value is whatever the old value was but however next moves let's actually look in next moves right so the search for cons next moves right and what this does is that it says okay it takes some state and an remembers what it doesn't engine is that it takes the whole state but the intention is that it should produce only the list of moves right so it takes the whole and it produces the part so we take the state and when we say ok state that moves that length is greater than 1 so so we say if we have more moves than 1 then we should drop the first move all right so so drop first is a function that I've defined here in the base drop first which is I mean it's trivial we just call a slice on an Iranian slice and then pass one but I just did this to make it more readable all right because now we actually like very easily see what it does so we said drop the first from state moves or if the if the length of the the moves list is not greater than zero right so so if we have only a single move left then it will simply be stated moves so what this means is that if we have more than one move we will drop the the first move because we've used it like in the next in the next state we won't have this this we won't have the first move left because we've now used it but if we have only a single move and we shouldn't do that because we want to keep repeating that move right that that's how we're achieving this effect where it just keeps moving right I mean I'm not touching anything it just keeps it just keeps moving to the right so that's the because we keep that in and of course let me just quickly mention again that you can using fractional programming you can make a lot of things point three point one and point two I've also kind of makes this this sort of object-oriented dolt syntax with this function syntax where you truly like truly have a function and then pass the data and I have a bit disappointed about myself for doing that but anyways I mean I just feel that stay pragmatic but probably like here you we should do something like this like length of stated moves because this this dot syntax is fine because this is purely data but then length is a function on that but but this is sort of give and take I mean I don't think we're going to be able to get away from this anytime soon in JavaScript because like maybe my terminology is a bit shaky here but if you've tried Haskell for example like you have a dog polymorphism and you have parametric polymorphism and ad hoc polymorphism is kind of like polymorphism in object-oriented programming and so so the so the thing in Haskell is that you can actually define a length function that takes some data and then does different things depending on the type of that data that's a bit tricky in in a language such as JavaScript because we don't have ad hoc polymorphism in that way the way we achieve a dog polymorphism is through object-oriented polymorphism is is by saying that length is a function that exists on an object and in in a dynamically typed language such as JavaScript we say well everything is a type right like if it quacks it's it's it's a duck so everything that responds to length is length double but which implementation of length you get depends on which object you call the method length on so I mean it's a it's a give-and-take like we probably have to mix these two types of syntax before we have facilities for ad hoc polymorphism that looks a bit more like something like Haskell in JavaScript which probably we maybe won't because maybe I'm not realizing how difficult that would be to implement in a dynamic language but anyways now we're way out of the zone so so so let's get back into this game are you but I just wanted to mention that that I'm completely aware that that's it's not it's not superb anyways how do we compute the next position of the Apple well if this is what we get to this we'll eat that we talked about right like if the snake will eat the Apple given the current state then we need to spawn a new position and that's when we're using this or in the post that we talked about that's that's the that is the function in this snake library that is impure or actually I mean then next Apple becomes impure because it's making use of an impure function block matter so if the snake given the current state will eat the apple then we need to spawn a new position for the apple based on the current state if it's not if it's not if the snake is not going to eat the apple then the apple will maintain in the maintain its position that the new position for the apple will be the same so let's actually for the fun of it I mean let's let's actually do the same thing in both of these cases right like we have the cases where it really isn't we have the case where it won't eat so let's change the won't eat case two also spawning a new apple at a random position and then if we refresh this and run this again you can see that it's absolute and total madness right because now we have the Apple the sort of respawning at new locations regardless of whether we're eating the Apple or not so actually I mean I don't know the probabilities here but it's probably I have no idea whether it's higher or lower but girls like it this is a very tricky game to play so this is surely not what we want then and and then also for the fun of it let's try the other thing like let's say instead of when we eat an apple that's spawning it's at around a position that's just spawn it at the same position let's say that the the new position of the Apple the same as the old position at the Apple regardless of whether we hate it or not then we can see that what happens is that we don't spot a new Apple right the game works fine but we can just like hands off and then just leave it like this because we're just going to eat all the apples in that we crash and actually it's probably still tricky again because the snake will take up all the screen and then it's gonna be tricky to eat the apple but anyways I mean it would clearly what we wants isn't random positions we want is this like we spawn a new position when we eat the apple and again there's like this is some of these things are really easy to to think about when you have immutability unlike I would imagine that going back and forth between these two changes would be more difficult if you had it would potentially be more difficult if you have mutable state depending on how you had structure your code clearly next thing let's move to the next head right so next head well let's think about it this way we have next snake and we have next head so maybe these should be in the other direction right so so and I think next snake was called in the next method here right so yeah so we're saying snake is equal to next snake so if we go back to next snake here and next snake makes use of next heads so let's actually talk about next snake first so let's first think about this snake right like look like look at this like let's actually grab one more Apple here so now so now we're a snake of three dots right if you look at it now we're four but so so the snake is a list of positions and now it's a list with four elements that each have positions and I think if I'm not mistaken you will have to see either the first element or the last element in the list is the head of the snake so what we do is that we just when we need to move the snake we remove one element in the end and then we add one element in the beginning and when we eat right think about it look at what happens when we eat then we the snake is prolonged so what happens is that we add one in the front but we don't remove one in the end or I shouldn't say we add one in the front what I mean is we do compute the next position of the head but we don't remove from the tail right so we have the snake here and then we're saying okay if you if you're not eating we should just take the snake and move it here but if you're eating then we should take the head and move it here but this tail should be fixed we should not remove anything from the end because it's this removing that makes it gives the appearance of it moving right because it's actually that we we remove this thing and then it's easier maybe if you just think about one dollar as like if you have one one dot like one one one snake head then what happens when we're not eating is that we're just saying okay remove this poop and make another one here and that gives the appearance of moving right soup and then soup and so forth but if we're eating we're saying create the new one right you have one here create the new one soup but don't remove this one right so so we give the appearance of prolongation and not off of extension and not of moving of course it's moving as well but but anyways that's the way that we structured that by the way I mean I tried this in multiple different approaches that I was a true I mean it's an interesting game because you can also approach it by building up a grid and then sort of making operations on that grid so like you have a 2x2 array like a matrix but anyways maybe if you if you try out the solution along those lines maybe I can shoot something in the comments that would be interesting to look at so to compute the next snake what do we do well what we do is that we have two ifs we have two nested ifs we say okay if the snake will crash right given the current state given the current direction give it the current world if in the next state that we are now computing will the snake crash or not like if it will crash then the next snake is going to be an empty rate then we're going back to this sort of initial State thing we're saying let's go back the snake is an empty array however if the snake won't crash we do any of these things and if it won't crash we're asking this well is it so that the snake is eating because if it's eating then what we want to do is just that we want to add the next head we want to compute the position of the next head like we did here with your hands right like we have one we have this we have a snake here and we want to add the snake here we want to move it here right and if it's eating we just want to compute this new position and then we want to concat that into the current or with the current snake however if it's not eating then we need to move it or we need to give the appearance of moving that means we can't just compute the new position but we also need to drop the last positions we need to remove something from the end and let's just think about whether it sits in the head or not so what we're doing is that we're saying the head is an array and then we conk at that with the old snake yeah so what we're doing is that we're adding into the beginning right so we're not it's not that we're pushing in positions into the end of the snake but it's more like we're adding at index 0 and then we're shifting all of the other ones backwards so I am and at all thought about performance and efficiency but I mean clearly if there are probably multiple ways you could improve this code but so that's what happens when we were when the snake is eating we are adding a new head but we're not removing the tail and if it's not eating we're adding a new head and we are removing the tail so actually I mean if we if we remove this drop last just to try this out right then we're saying it's going to get longer all the time so if we run this now you can see just like it's it's never ending like it doesn't matter if we eat or not the snake is just growing and growing in the ABS tricky for me to help but as you can see right like the snake just keeps on growing and growing and growing again like a trivial thing to change but we go back to actually dropping the last and of course if we did drop last here as well just to be exhausted let's try that as well now would mean that even if we are collecting apples nothing happens like the snake isn't actually prolonged so of course what we want is that like this ok that's the next snake but then ok as we were saying next next next snake makes use of next head so the question is how do we compute the next position and that's actually pretty interesting I said let's look in the next head so again next head the structure is that it takes a state and it produces a single position or it's a so thing about it I mean next snake takes a state and produces a list of snake positions next head takes the full state and produces a sin position that represents the head of the snake and what it does is that it too has to ask an if question and the if question that it asks is that do we currently have a snake or not right like if the length is zero like if you look here right as we talked about in in next snake of course when we crash we completely wipe out the snake so that we don't have a snake so when we compute the next hand it might be that we're trying to compute the next head from from no snake at all so this is why we're asking is the length zero because if it is zero then this we want it's like the default start position maybe I should have had that as a comment or something like that I actually if you look at it if we refresh here you can see that this supposedly is two two actually let's think about that means so x3 and y3 if you start from one so this is why I mean maybe one would assume that it's here but actually this is one one because this is zero zero so here it's zero zero 1 1 and then 2 2 so so yeah we started to - all the time and that's essentially because when the length of the snake is 0 then we spawn at - 2 and again I mean we could do or indeed let's say aren't be pulse given the stay yeah that should work as well then we are starting at random positions we're always starting going east but now we're spawning it at random positions we're spawning the snake at random positions but I figured this this makes sense I mean it's kind of nice to start from from - - and that also it's obvious that you've crashed so that's what happens if this thing is zero like if it's the initial case but if we do have a snake like if the length of the snake is currently longer than zero what do we do well this I found pretty good we create a new position that has an X and then I have that that has a why of course and what we do is that we take the I mean think about how these two look very much the same right like x and y is essentially the same thing but what we do is that we say the x position is the current x position of the snake of the of the current head of the snake right what's in the 0th position plus the ex of this move that we've stacked the next move that we want to grab so the the move that is pending the move that it that were supposed to execute now and remember like the moves that we can have are are any of these north south east west east well west so if we have east stacks right as in the default case then then X will be one so what we're saying here is that we're saying stay thought is snake so we're taking taking the state position is a snake position and supposedly if we start at two and as we've defined as that as the sort of default start position here so if we start at at two and then we say plums and we add one because East is one so the default direction is 1 then we get three right because the current head is at two - or if we only think about the x position the current head is at at x2 and then if we add one we end up in x3 right and that's actually that the next head now the question that and of course maybe what we should mention is that this of course works for negative as well so if you have if the position is two and you add negative one then you get one because you're moving in the other direction and the reason and that we have this modest thing is that we're making use of the modulus operator so actually I mean in the best of worlds I would never had to write that I should have been able to write something like let's think about this so so this so the same calculation mod state dog calls that's the way I wanted to use like we want to use the the mod operator but apparently the modulus operator in JavaScript behaves differently then than I had expected in regards to negative numbers but think about it the reason we use modulus here is that because we want to have this wrapping effect right like notice how when we go out of the right end of the screen like it's a torus right we go out on the right end of the screen and we show up on the left hand and similarly if we go out on the left and we show up on the right and and and that modulus operator is actually everything that's needed to implement that because what we're saying is that if the grid let's say that the grid is 12 right let's think of a clock so if i'm not sure if i can do mod here actually so if I do 10 yeah I can't okay so let's do it in Google so so if we do 10 modulo 12 for example we get 10 because let me be more clear so if 12 is the width of the grid and we're talking about the x position would say so the number of columns that we have to work with is 12 and that means that 0 is an okay position 11 is an okay position 12 is not an okay position so if we do 0 12 modulo 12 that means that it's the 0th position so what I mean is that 12 is the size of the grid and and 0 is the position that we want to position the next head in if we don't think about this wrapping around right like then you could say position 316 right like even if you have a grid of 12 actually I mean we could do that that's that's the point of modulo so if we do 316 modulo 12 that means that if you try to move to position 316 you should actually be at position 4 because you've wrapped around you've looped around and multiple multiple times but if we just think about the edge cases right like the 0 yields 0 so that means the zeroth positions that means we're not changing 0 and if you do 12 you also get 0 because 12 modulo 12 like 12 divided by 12 gives a remainder of 0 so that also puts you back in in position 0 which makes sense because we're saying it's 0 indexed right so so because we have the 0 index the max index is 11 so when we try to go to 12 we should wrap around back to back to 0 so maybe I'm not making a superb job of explaining this but but every number then between 0 and 11 inclusive will be the number that we pass so if we pass 2 we get 2 if we pass 3 we get 3 if we pass 4 we get 4 and so forth but if we pass higher numbers which happens when we are at position 11 and we pass 1 right so or we add 1 right so this is more like that we have we are at position 11 we pass one and then we do modulo 12 of that so that gives us 12 that we do modulo 12 on and that gives us zero that makes us wrap around back and let's jump into node quickly and look at this so actually I should have done this in notes from the beginning so so 10 modulo 12 gives us 10 right and but 12 modulo 12 gives us 0 which wraps us around back similarly 13 and gives us 1 but this is a case where that we were never seen however what I would want is that negative 1 should also wrap so negative 1 should put us in the other end of the 12 and that's the way I was expecting that that modular was behaving so let's see can we actually can we require the base library from here so let's try to require base oh sorry base equals required oh sorry because I now I declared it twice but messed up so let's restart jump back into node so let's require the the base library Wow sorry I'm such a confused person so let's jump into node and let's say Const base is equal to require from the current directory the file called base that should be specific in sage is right and that works then we have the base library and base I mean the base are aware that we've defined in this project and base has the function mauled right so this mod mod however works differently I mean it works the same if you pass 10 and 12 again then you get no sorry it's the common case art for other reasons I did it in the other order so here I pass so this if I pass it this way I mean 10 module 12 then we get 10 and if I pass 12 we wrapped around 0 similarly for 13 but again we never end up in that scenario but now if I pass negative 1 will wrap around in the other direction and end up eleven and modular two and so forth so so yeah and that's a different implementation of of modulo and I think I left a comment here yeah I left a comment here to link that you can check out if you're interested in the source I think that came from a stack overflow post or something like that so anyways the house what are these moments where you go like ah excellent like it's it's awesome when when math can make problems that seem a bit tricky to solve in programming but math makes them trivial those are the good days right but anyways so that's why we do modulo here so what we're saying is that this is the raw quotation marks the raw new position and then we say take that position and do modulo of the the size of the rows or the coals depending on which one we're working with so that we wrap around but yeah that's that's next head and so next snake is based on the on that next head essentially and yet the randomness here we've talked about the initial state we've talked about next we've talked about and Q we've talked about oh my god and we actually talked about everything now so yeah I think we've talked about everything and that sort of covers it in getting to the point where where the snake game is operating something like this because we also looked at the the web HTML and J's file and of course I mean I also have this web dot CSS file with just some basic styling in order to make this look a bit prettier I mean if we remove I'm not even sure like if I remove this stuff it looks like this right I've actually zoomed in so this is the this is what we actually draw on the canvas but this is just some days exciting that I added in order to make it look like this but yeah that's it before we wrap this up let's actually also look at the CLI so the command-line interface so I'm opening up CLI ljs right and let me just map a keystroke here in them to run node on CLI dot j s or to run CLI the J's through node so if I just hit that we're running this and again you can see this works fine and actually I mean this is a pretty short term imitation as well so the game loop is a bit different here we still do this sort of showing and stepping but what I do is I set an interval yes so what we do is we make use of set interval right which is this function in JavaScript that calls a function with a regular interval it's also expressed in in milliseconds but here actually I run it every 80 milliseconds so yeah for some old reason I chose a different interval for the terminal application maybe we should change this to 100 and in order to sort of make it have the same speed but nevermind I mean that we can trivially change but essentially what this set interval does is that it does these two things right if we pass a function to set interval that first calls step and then calls show and actually I mean now that I think about it what we could have done is we could have said pipe step step and then show like so over ad and that should be the same that should be the same thing right and actually that didn't work and I think it has something to do with like the console.log here or something like that so like the context disappears but never mind let's not think about that now and either way I mean piping here is not necessarily a sensible thing to do because a step and show are are mutating functions or there are functions that like I mean compose and pipe usually use when we actually want to pipe or compose functions like because we're actually passing data through now we're just doing saying do this thing and then do that thing so they can never mind but I know it's so set interval what it does is that it calls step and then it calls show and it does that every 18 milliseconds and what show does that let's serve the show and so both of these both showing step are functions that don't take any arguments and show what it does is that it console logs this strange character which works as a clearing right like if you think about it actually if I if I remove this I think that would be the most simple way of describing this like if we remove this and then redraw you can see that the game works right but if I if I scroll out you can see we have multiple versions of the game where the the lower one this one here in the bottom this one is is the active one because that's the freshest one we are the other ones are histories and actually we are we are printing and printing and printing and so it's not like it looks static but it's not actually static I mean it's like I'm moving like these old animated movies like where you draw where you pull past the strip of multiple images and that's actually exactly what happens now like this is strip of multiple images anyway service that's stopped thinking about that now it's but it's pretty interesting but if we add that character we keep clearing the screen all the time so that we're just it's like clearing the canvas so that's that's the first part part of show like we clear and then what we do is that is that we have these these functions on the object matrix right so so here you can see we have a comment that says matrix operations and I'm following that there's a constant called matrix which is sort of a module with a bunch of functions that operates on matrices and actually I mean I used in board matrix here indicative Li like it's not actually truly a matrix but what we have is that we have an array of an array like we have a two-dimensional array where the first I think first I do rows and then I do columns or maybe the other way around so anyway the the things under matrix under the matrix module are then things that operate on things on this type of structure and so what we do is essentially then that we say okay matrix dot to string of mate matrix not from state given the current state so here we probably could actually use pipe in a sensible manner so what we're doing is that we're saying yeah pipe and matrix matrix from state and then matrix to string and we're piping that through the state L so this is probably the reason why I didn't do this because the line turned out to be longer like if you compare the two lines this is actually longer but that should work fine as well and like if we run that all right that doesn't work fine oh sorry my bad this is probably why the other thing didn't work as well we're not using array syntax I should pipe like that yeah sorry so that's probably why the other thing didn't work as well not we didn't have anything to do with console.log but anyways so yeah we could use a pipe there instead but has just said I mean even even if I remove that sort of array here you can see that this line is longer and I just figured nevermind let's just do this but anyways I've both these things are equivalent but yeah maybe maybe this one is more clear so maybe let's keep that one I guess I didn't because of the length of the lines like you can see we're at 88 everywhere at : 88 but anyways let's keep it like that for now so that's show so what it does is essentially it clears the screen it says construct a matrix from the current state remember the state is the same kind of state that we used in the web GUI right like that's what we're saying like we use the same state tree but we do different things based on this state tree and thus we can show our command-line interface and we can look and can show a web interface so from this current state we we produce a matrix and then we take that matrix and we pass it to two strings so what we do is that we produce the matrix and then immediately we turn that matrix into a string so actually I mean maybe you could argue that I've made it unnecessarily complicated in some way and maybe we should find a way which is more similar to the web approach or we sort of just immediately do what we our display what we want to display but to me this seemed like the easiest approach like having this intermediate data structure that's this matrix like thing because I mean if you think about it we print this pretty much as a matrix right like this is this has a number of rows and it has a number of columns and actually if we look at two string here in the matrix module you can see that this is where it becomes obvious that it's it's fairly trivial because to string takes I wrote X's X's here because like usually in functional programming you can you will see people to say like XS or SS or something like that to indicate that you have an array of something so you have an array of XS here but I I tend to write X's X's I'm not sure if that's conventional I hope that's conventional because it seems sensible to indicate that it's an array of an array of X X's so and that's why I say X's X's here on the outside and then X's here on the inside so the way this works is that we map over the the outer array and then for every inner area we join up all of the values with a space if you think about it you can see here that there's a space in between there's a space in between all of the different dots like if we didn't have that space probably this wall yeah and this actually does work right this doesn't look very nice also because columns are a lot narrower than the rows so so we need that extra space in order to sort of make it look a bit more nice so we join it up with space and then we join up each of these rows with a backslash or backslash n which is actually a line break so that we can print it as a single string that that sort of looks like a grid and actually I just realized I used to have a point free or not a point free version of this but rather a version that's more similar to the make function here where instead of actually calling map on the axis of X's we say map and pass the axis of XS because I guess that could be more elegant than this but anyways there are multiple ways you can rewrite this function and some of them are probably a lot more elegant and the way I've written it now but anyways that's the two string function and of course if we think about this method again like what we were saying is that we first do from state and then we do to string so we start with this from state and then we do to strange so from state what that does is that it creates one of these XS of XS it creates one of these grids or one of these matrices containing the appropriate values and how we do that is essentially by piping right we say well we need to pass this state so we take the state as an argument and then we pipe it through a few functions but the first function makes the matrix the second function adds the snake to the matrix this third function has the Apple and the fourth function adds a crash if there is supposed to be any crash so again these are sort of immutable methods in order to just to keep my head straight in this so matrix don't make we can see up here I think matrix does make here I called it table and not state because a table is anything that has holes and rows but I mean actually what we will pass is we will actually pass the state but since we only care about their Colton the rows I figured I'd call it here and what we what make does is essentially that we use this helper function rep right so if we look at the base library cones wrap here we have the implementation of rep and what rep does is that it takes a constant and then it takes an N and then it Maps this constant using the function K which is the Const function over the range from zero to n so this sounds a bit bonkers but what what this actually means is that if if you have actually let's let's jump in to know then let's look at this so a Const base is require right and then we have the base library and then we can say base Dalt rep and if i say a and then i'll pass 10 you can see that we get 10 a's if i get it past 3 you will get 3 a's right then if i instead of a if I have one here I was essentially get an array with this thing repeated the number of times as the second argument that were passing and rep was implemented in terms of range and what range does is just that let's see if I can remember the syntax if we do this right then we get a range from 10 to 20 we can array of all the elements of 10 to 20 so we were doing is that we were mapping over that range in order to have an array of multiple elements and what we were mapping is that we were mapping the function K of the the constant that we were passing so when we did based on rep a 10 I here up in the beginning for example then our constant is a so if I say K of a then I have oh sorry base K then I have a function that accepts an argument but it doesn't care about which argument it it's passed regardless of what argument is passed it's going to respond with a and that's how we can when we map so so actually let's let's do that manually like if I have that range from 10 to 20 and then I map over that and if I map K of a we will replace observe base KN based at K we will replace all of the values in the rear with a and that is essentially the way that a rep works and yeah I guess range and K as well and map but anyways so that's why we do rep rep here so it's pretty interesting I mean what we do is we repeat the repetition of period or this the string period over the number of columns that we have in this table we do that for every row that we have in this table so that's a pretty concise implementation of making a a two-dimensional array of filled with dots right so that's the first thing we do we make the matrix based on the state we make the matrix from the state but then here we have these three functions that we want to use to add additional things to this to this matrix we want to change particular we want to adjust particular cells in this matrix and note how we're passing state to these because essentially this one or only requires the state to be passed once but these actually require the state to be passed twice so we'll invoke them like this so ad snake is a method that can be defined only when we know what the state is and this I mean I was struggling a bit with this I think this didn't turn out super nice so if you have any suggestions of how to rewrite this properly super interesting if you want to post anything about that I was toying with the idea of probably using the S Combinator or converge or something along these lines but probably we could find out more sensible ways of expressing this but anyways what we do essentially is so add snake what it does is that when given some state it will it will produce a function that's essentially a pipeline where we spread the mapping of setting X or rather I should say spread the result of mapping the value x over the snake so essentially this is the thing about it I mean this looks a bit hairy but like actually it's not a stranger I mean so the the thing about it like the snake in a state it is an array it's an array of position so what we do is that we map the function of setting X and setting X in the matrix takes a particular precision and so by mapping that we get a list of function that we then spread over this piping's we spread it into arguments so we make a function that when passed a state will run that state or sorry not when passed the state when passed a matrix will pass will run that matrix through a bunch of matrix not set transformations that set sets x at that particular position and if you look at set I mean set is also fairly straightforward what it does is that it takes a value and then it takes a position and what it does then is so the position is then a point right maybe I should have set points or something that has an x and y and it called adjust on the Y or adjust at the Y position but then passes the adjustment of the x position of the constant of the values so what we do I mean actually I mean if we if we express this in a less strange manner actually what this is is that we're saying why I mean if we would do this in a mutating fashion what it does is that it takes another argument because it's point free right so it takes it also takes the matrix and what we do is that we say the matrix of the position Y and then position X is equal to the value and then we return the matrix like and then we return the matrix so that's essentially what what the set method what the set function does but but now we're mutating and so now we're trying to do that without mutating and when doing that we also got rid of the matrix variable here and and of course adjust is a function that we defined here in in our base based helper library but of course it's also available in a lot of different libraries and what adjust does is simply that it takes an N which is an index it takes a function and then it takes some kind of collection some X's and what it does is that it changes it runs the function at the index n of the XS right so so it's a way of if we go back here right like we say list index equals value or more specifically if we say function over if we run a function over the you add that index right that's what what this version of adjust does and then and then we pass kay here because we're not actually interested in running a function over the value we don't care about the old value we just want to replace it with a new value so that's kind of a general version of adjust and yes so this we do in order to - so essentially what set does is that when given a value when given a position and when given a matrix it produces a new matrix that looks like the old matrix but where the value at the position has been updated to the new value and that's essentially what we do with set right and so that's why we use it in in add snake and we also use it in add Apple and actually not add crash because here we did a more simple implementation I guess but yeah that's how we use it in add snake the way we use it in add Apple is much more simple much more simple then we just simply say matrix that's set and we set to an all at the position of the Apple right so state of Apple contains a position and we're just saying matrix that's set at that particular position right so I mean this we could change to an A and then if we run you can see that we now have a x' instead of always that represent the Apple so that's the Apple and then the crash is very simple here we just say that okay if the snake length is zero in other words if we don't have a snake if we've crashed or if we just started the game then we map the mapping of the constant - so over this grid think about it the grid is a list of lists so over every column or row depending on which word we structured it anything maybe its row so over every row we map but we map the function that is the mapping of every column and over that we map the function K which is constant that returns regardless of what it gets returns whatever we passed it and what we passed it in this case happens to be this - symbol or this this pound sign this hashtag and that's only what we do if we if we don't have a snake so if we've crashed but if we hadn't crashed then we pass ID and why do we pass ID because ID the identity functions that means that well if we haven't crashed and the grid needs to stay the same we didn't do that let's just say let's say we did this in both of the cases so instead of doing ID we do this mapping again then it'd be a constant crash like we're just it's it looks like constant we're actually just redrawing this constant and recomputing this constant all the time but yes so that's why we need to pass the ID and if we didn't do this this mapping of pound that wouldn't be the end of the world it would just not be obvious when we've crashed so what we want to do is visualize like like we did in the web right like here when we crash we have the screen sort of turns red like that right in here in the terminal we want we want every position to be replaced by a dash and that's that right that's so these are the matrix operations that we have we we we can make a matrix we can set something in the matrix we can given some state produce a function that when given a matrix adds that snake to the matrix similarly for the Apple and similar for a crash similarly for a crash and then we can take a matrix and turn it into a representable string and then here we have the from-from state function that essentially first makes a matrix and the snake adds an apple and as a crash all in order so that we can simply just call that from from over here in our game loop or when we want to show okay that lets show then we also have step but step is super trivial similarly if we go up here into the top like similarly to the web right like if we go back to web dot HTML you can sort not so web KS you can see here in the top that we have this mutable state where we say that the state is equal to initial state for some reason I chose lowercase s since uppercase as here that makes no sense but in the CLI here we have a variable called state which is which starts as the initial state from the snake and that is the immutable state and the reason that that's mutable is of course that we need to listen to these key events so I'm not gonna bother you about how this listening to keys work but this seems to be like the way we do it in node when we want to listen to key presses so we capture what's in standard in and if we happen to press ctrl C we need to exit the process because people need to be able to exit the program but if not then I'll just do to upper on it so so we don't have to worry about lower case w or uppercase W and then we just look for WASD as in the web case right and hjkl and we look for up left down right arrows and what we do is actually the same thing let's let's look at this so yes so the state is equal to the nquing of the old state and the new moon and then we break and this is the same thing as we had here in the web where we said okay and Q based on the new state this new move and compute a new state from that and and replace the mutable state with this new state that's computed and that's the way we do that and yeah yes I guess that's pretty much it so to do that red line we needed to read in the base node library red line it seems that there are other ways of doing it but this seems to be the easiest way so yeah it kind of seems like that's it it seems like a lot of cold in some sense right but if you think about it what I would argue and what I would argue is that what we've done is that the snake implementation is actually only this file it's actually only snaked rjs like this is this is the implementation of off snake the other stuff regards how to display the snake game like what graphics version or engine you want to use your snake with you what GUI you want to use your snake game with so which way you want to display snake to the user and which way you want the user to interact with your snake game so again like if we think broader than game programming functional programming is really susceptible arity and so what I mean is when you produce small functions you can really quickly feel how they become very composable and very reusable but yeah I mean but if you actually know exactly what you're building and you know that you're not expecting any change and you have a strict specification then yeah maybe you don't need to be worried about change but if you are worried about change like if you're building an application changed overtime or if you're building something that might be shipped to multiple platforms then approaching something even like game programming kind of like we've been approaching it today I think actually makes a ton of sense what do you think let me know in the comments and let's discuss beyond that if you thought this was interesting and if you want me to decompose any other games let me know like Tetris or pong or whatever I mean shoot something in the comments either way clearly this code can be improved a lot but hopefully you find something useful as we go through this piece of code hey I'm Christopher I'm the guy that you just saw in this video I hope that you like the video and I really hope that you felt that you learned something if you did like the video I would suggest that you check out my youtube channel the link to my youtube channel is in the description so thank you again very much for watching and I hope to see you in another video either here on this channel or over on my channel I'll see you in the next one
Info
Channel: freeCodeCamp.org
Views: 139,983
Rating: undefined out of 5
Keywords: javascript tutorial, functional javascript, functional programming, javascript, javascript advanced, learn javascript, web development, front end, javascript tutorial for beginners, programming tutorial, javascript tutorial advanced, front-end, node, js, javascript for beginners, web development tutorial, java script basics, complete javascript course
Id: bRlvGoWz6Ig
Channel Id: undefined
Length: 92min 58sec (5578 seconds)
Published: Mon May 21 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.