 The Snake game, if you're not familiar, is a game where you control a snake, and you go around and you eat these red apples, and each time you eat an apple, the snake grows longer, and the goal is basically to just make your snake as long as possible, without either running into the edges of the screen, or running into the body of the snake itself. So here's a version of Snake written in closure, which I found on GitHub. The last author was Julian Gamble, but he took it from a few other people's versions of Snake, and I've updated it myself to remove a few bugs and make it a little clearer. So first off in this program, we have the NS macro where we're establishing the namespace examples.snake, and in this namespace we're importing these Java classes. First, from the package java.aut, the classes color and dimension, from java.ex.swing, we're importing jpanel, jframe, timer, and joptionpane, those are all swing classes, and then from java.aut.event, action listener, key listener, and key event. If you're not familiar with the swing GUI toolkit, it's an API for creating windowed applications. Don't worry if you're not familiar, we'll just go over what you need. What we're using here is entirely complicated. In any case, the code here is split into three sections. First, the functional model, the part that's purely functional, then the mutable model, the small portion which is not functionally pure, and then the GUI part, the part which actually deals with drawing on the screen and handling user input. First, though, we're just going to look at the functional model because that's where the actual game logic is implemented. That's the general idea in functional programming. If you're writing a game, is it typically the logic of what happens in the game should be expressed mostly in the functional model itself? So our functional model starts out with some definitions of constants using the depth form. First off, we have field width and field height, that's in terms of grid units, how wide and how tall is the play area. Then we have point size, which is for each unit of the grid, how many pixels wide and tall is it, each one is square. Turn milliseconds, that's how often the snake moves in terms of milliseconds, so every 100 milliseconds the snake makes a discrete step. Wind length, that's how long does the snake get before you win the game. Here I've set it to 10, which is quite low. In a real snake game, the snake needs to get quite large before the game is any challenge at all because otherwise there's no real danger of running into the body of the snake. But for now we just have it set to 10. And then we depth to directions, this map of key event constants. The key event class has vk left, vk right, vk up and vk down. vk I think stands for virtual key. Basically they just denote the left, right, up and down arrow keys on the keyboard. And so here we're associating with those directions each a vector, a vector of two numbers denoting a direction in terms of unit vectors. So for example we associate with left a vector of negative one zero, because when the snake moves left it moves one unit in the negative direction of the horizontal axis, the x axis. But it doesn't go either up or down, so the y coordinate is zero. And likewise with the other directions. Notice it's safer up, the y axis runs in the positive direction downwards. So negative one on the y axis is going up by one. Okay, so those are the constants. And then we have these two functions create snake and create apple which as the name implies creates our snake and our apple object representation. We're not going to use any def type or def record we're just going to use an ad hoc map to represent both of these things the snake and the apples. So for example in the create snake function we are returning this map with four key value pairs the keys are keyword body keyword direction keyword type and keyword color. The body is represented as simply a list of coordinate vectors where the the first one represents the head. The next one represents the thing the the segment after the head and so forth all the way to the tail. So this create snake function returns a snake where the head is at coordinate three zero and the tail is at coordinate zero zero where zero zero is the top left of our field of play. And then we have the initial direction of the snake the direction that's headed in for its next move that starts out going off to the right so the vector is one zero x increases by one and y stays the same. We're giving both the snake and the apple a type keyword because that's what we'll use in a particular method to distinguish between these two types of things as we'll discuss later. So that's why that's there to identify that this is the snake map and this is the apple map. And lastly in the snake we have for the keyword color we have an instance of the color class notice here we're instantiating this color class with a special syntax. There's a special syntax enclosure where a symbol ending with a period at the start of a list is just shorthand really for the new special form. So this is the same as new color 15 160 70 and this Java dot a w t dot color class it's a it's a representation of a color as a red green and blue value the red here is 15 the green is 16. And the blue is 70. So because the green is predominant this is a mostly green shade but with some elements of blue and a small hint of red. So that's create snake looking now at create apple this also returns a map this time with the keys location color and type of the type of course is set to apple rather than snake. In this color notice that red predominates red is the biggest number to 10. And so it's a mostly red color. And then the location of the apple is another vector it's another coordinate of X and Y and X is a random integer value from zero up to but not including the field width and Y is a random integer value from zero up to but not including field height. So when we create an apple its location is some random place on the field. This point to screen rectangle function is taking a point a coordinate of the grid of our field and translating it into a vector of four values which are going to be used by the drawing functions actually draw rectangle of pixels in the window. So we're basically translating from field coordinates to pixel coordinates because when we draw a point on the grid we want to render it not as a single pixel but as a square that is point size pixels 15 pixels we set it to tall and wide. So first looking at the parameters of this function notice that we're using destructuring. There's a vector with two symbols inside point X and point Y. So this function is expecting a sequence where the first element gets bound to point X and the second element gets bound to point Y. The body of this function then returns a vector with four elements first the product of point X and point size then the product of point Y and point size and then point size and point size. So for example if we pass in a coordinate of the value say three and zero then we get back a vector while point size has a value 15 as we said so three times 15 is 45. Zero times 15 is zero and then point size and point size both of those are 15 so we get back 45, zero, 15, 15. Again this will be used later by a drawing function where the first two values denote a corner of where to draw a rectangle and 15 and 15 that's denoting the width and height of this rectangle to draw in our window. Next this move function is what actually moves the snake it returns a new version of the snake that has an updated list of body coordinates and this function as well as using destructuring. This time its first argument is expecting a map and remember the syntax for this destruction and maps is a little weird but what that colon keys and then the vector after means is that the map which we pass in it should have two keys body and direction the values of which we're going to assign in this function to these parameters body and direction. So body will have the value of the body key of the first argument map we pass to move and direction will have the value of the direction key of the map we pass in as the first argument to move. And then where we have keyword as snake that keyword as means that the map itself is assigned to the symbol snake. That's a another convenience of keywords and map destruction which we didn't cover earlier. Again the syntax for this is pretty damn weird but it does make for a nice convenience. In any case we then also have a rest parameter for this function called grow. So any arguments to this function past the first will get bundled into a sequence and bound to the simple grow. Otherwise if we invoke move with just one argument well then grow will just be a nil sequence. Looking now at the body of this move function this function returns the result of a soak which is invoked on the snake map which we pass in and a soak is giving a new value for its body keyword. And that new body is created with cons and cons takes two arguments recall it takes first some value and then a sequence because a cons recall is a kind of sequence that starts with that value and continues with the specified sequence. So we're returning a new snake where the body is a new cons created from some value and some other sequence. And that first value is going to be the head of our new snake it needs to be a vector of an X and Y coordinate. And we get that with this let form where we again using destruction we take the first from body that returns the head of the previous snake of the existing snake and assigns its X value to head X and its Y value to head Y. And then we take the direction the direction the snake was heading in and we bind its X value to Dur-X and its Y value to Dur-Y. And this let form returns a new coordinate a two element vector where we get the X from head X plus Dur-X and the Y from head Y plus Dur-Y. Because logically every time the snake takes a step its head is in one position over from where it was in the direction of travel. So that's what we're getting here is the new head from the old head. And once we have the new head we then need to tack on the rest of the snake that's what we get with this if special form. The grow parameter of this function is basically just a flag denoting true or false. If true if the snake is growing with this move then we want the whole pre-existing body because the new head is being tacked on but we're not taking up the tail the tail is staying where it was. Otherwise if the snake isn't growing if it's just moving then we want everything from the body but the last element but the tail. So the if form here if grow is true then it returns body otherwise it returns but last body but last is a sequence function which returns a new sequence with everything but the last element of its argument. So again the let form here is getting us our new head the if form is getting us the rest of the snake and we're concing those two things together and soaking it as the body of a new snake map which is returned by this move function. This turn function takes two parameters a snake map and a direction vector and it simply returns a new snake map with an updated direction. This wind function also uses destructuring it expects a snake map and it binds its body value to the symbol body and if the count of the body is greater than or equal to the wind length well then the game has been won so it returns true otherwise it returns false. The head overlaps body function returns true if the head overlaps the body otherwise returns false. It takes two arguments a head vector and a sequence of the rest of the body and the contains function here will return true if the head is found in the set of the body. The set function recall simply takes an existing sequence here the body and returns a set with the elements of that sequence. So if the head vector matches any of the vectors in the body then contains will return true. The head outside bounds function will return true when the head of the snake is outside the field of play. This function takes just one parameter a head vector but using destructuring the X of that head is bound to head X and the Y of that head is bound to head Y. And then the function body returns the result of this ore where if any of these conditions are true then the ore returns true otherwise it returns false. And the conditions are well as head X greater than the field width then it must be out of bounds or is it less than zero then we've gone out the left side of the field. If head Y is greater than field height then it's gone below the bottom of the field and if head Y is less than Y then it's gone above the top of the field. Remember that the Y axis on our field actually the positive direction is downwards. Next we have the lose function which returns true if the game has been lost otherwise returns false. And it takes just one parameter a snake map and using destructuring it takes the value of body of that map expects it to be a vector and assigns the first element of that vector to head and all the remaining elements to body. Notice the use of the ampersand in destructuring instead of taking just a second element of the vector and assigning the body we're taking all the remaining elements of the vector and assigning them to body as a sequence. So in the body of a lose we simply call head overlaps body and head outside bounds and if either of those things returns true then we've lost the game and so lose returns true. Lastly in our functional model we have the eats function which returns true if the head of our snake overlaps the apple because that's when the snake eats. This function takes two parameters both of which are expected to be maps the first a snake map which using destruction we take its body element which should be a vector and from that vector we're taking the first element and binding it to head. And then the second parameter to this function should be an apple map and we're taking its location value and binding it to apple. So we have simply two coordinates the head coordinate of the snake and the coordinate of the apple's location and then we do an equality test and if they're equal then the snake is eating the apple and we return true. Looking at our mutable model we have three functions for mutable model update positions which moves our snake and our apple update direction which changes the direction of travel for the snake for its next move and reset game which will put everything back to the starting position. So first off looking at update positions we take two arguments snake and apple and these actually are not a snake map and an apple map as you might expect they're actually refs the current snake map and the current apple on our field of play. Those both are hold in refs for reasons that will become clear later but because they are in refs then when we update them we have to use do sync to both read the values of these refs and also to update them. So here in update positions we have this do sync macro and inside the body of the do sync we check if the snake is eating the apple if the head of the snake is overlapping with the location of the apple and so even if test where the condition is a call to eats. Notice that snake and apple are both preceded with an at symbol what that is is closure shorthand syntax for the D ref function. Remember that when we deal with the refs atoms and agents to get the values held in those things we have to use the D ref function and this is just a shorthand syntax for that. So anyway if the snake is eating the apple then we execute this do form in which we first create a new apple with the create apple function which creates a new random apple in a random location and we modify the apple ref to hold this new apple discarding the old apple. We haven't discussed ref set before but that's what it does. It's a symbol function that just gives a rough new value without having to specify some function to update the value as we do with altar altar recall we have to supply some function which we want to invoke to update the value of the ref which is appropriate in some cases but not others. So here when we alter snake we do so with the move function and notice that we're passing in an argument of keyword grow. That's what gets passed to the grow parameter of the move function which remember just acts as a flag of either true or false that grow parameter for this call to move is going to have a value rather than just nil. And so in this call to move the grow parameter is going to be a non empty sequence and therefore will test true and we use it as a condition of the if and so this call to move will grow the snake. It actually really doesn't matter that we pass the keyword grow here we would have passed true or just some random number or anything really it doesn't matter because all we really need is for the parameter not to be an empty sequence. Anyway, if our condition here was not true, if the snake is not eating the apple, then we just want to move the snake we're not going to touch the apple and the snake is not going to grow. So we just update snake with a call to move and no extra argument. Lastly in this function after the do sync we just have the value nil because it just a matter of style these impure functions. We're just doing them for side effects is we're not doing them for some return value so we're just making that explicit by having them return nil. Next this update direction function is very straightforward. It takes the snake ref and a direction and then updates snake with the new direction using the turn function. And notice that we're calling alter inside do sync because otherwise it throws an exception. That's just the rule of refs. Lastly we have reset game which takes the snake ref and the apple ref as arguments and inside do sync using ref set it updates the snake and apple refs to have the values returned by create snake and create apple respectively.