I mentioned in my last post that I’d signed up for a course designed to guide me through the process of going from dabbler to professional grade material in the world of software development. This is no small task so I felt it a wise investment to sign up with someone who had been down the path themselves. And a wise investment it was, indeed.
So, let me get to the subject of this post, my first original build in JavaScript. Let me first say that this will not be about how I built my first app (although I’d be happy to discuss it should someone have an interest) but, more so, what the experience of building it was like. And that is because there is a very valid reason why any decent developer will recommend that, if you really want to improve, you should build something yourself. And the reason is this: in building something original, a deep and organic learning process takes place in your mind that cannot be replaced by anything. This is where you learn how to create code instead of just reading or writing it. But I digress.
For this app I was tasked with building a “Snake Game”, a classic in the world of mobile games but far trickier to build than you might think. You can review my code here on GitHub or play the game for yourself here on CodePen. My instructor was kind enough to give me some resources in the form of online tutorials, articles and recommended books and a solid deadline of 4 weeks from starting to begin with but, after that, it was “go get em’ kid” for I was expected to build from scratch without the help of any libraries. The “deliverables” as they were called were the following:
Snake game ends when:
○ Snake touches itself
○ Snake touches the outside border
- When the game ends, the gameplay should stop and the user should be notified that the game is over
- The snake should grow one length whenever it eats the apple
- The apple should randomly place itself on the board after snake consumes the apple
- The snake should be controlled by the arrow keys on the keyboard
- The game will show a score of how many apples have been eaten
When I first read all of that, I really questioned if I’d gotten in over my head. While I was no stranger to online tutorials or sticking with working on a problem long enough to fix it, the idea of making a responsive game that did things on the screen was beyond me as the extent of my coding experience extended no further than basic HTML and CSS.
However, after working through the recommended tutorials and reading and re-reading the code I’d written in following along, I learned to isolate certain elements in the form of functions and certain syntax that I thought could provide some of the functionality I would need for my snake game. When it came time to start building, however, I began to realize just how much I’d been relying on simply following a tutorial instead of building the code in as I thought it would be needed for the end result. There’s a subtle difference there but it tends to become rather large when you start working sans tutorial. With a tutorial, you can get a certain degree of confidence in building something but you are likely not absorbing the core knowledge and understanding of the structure of the code as deeply as you might think. Actually, you may not be absorbing it at all.
This becomes alarmingly obvious when you find yourself in front of the black screen of the text editor. Everything is up to you and you suddenly take on a new respect and appreciation for the subtleties and details of how software languages actually work. “Oh, yeah”, you think to yourself. “I should look at that again.” And, while code from other projects (whether you wrote it or not) can be helpful there is still the issue of adapting it to the specific structure of what you’re working on. And that can be quite difficult.
You need to be exceedingly clear about how you name and organize the elements of your work. Failure to do so can lead to hours of lost time and dangerous levels of frustration. Another thing I’ve learned is the value of the Chrome and Firefox developer tools as they can give you live feedback on how your code is actually functioning.
Alright, with all of that said, let me get into the nitty gritty of how my project went. But first, a quick disclaimer. While the text below accurately details my journey from the blackness of a new file on VS code to fully functional Snake Game, my repository commit history will likely reveal a much messier and troubled story. And that is because one’s first real original build is generally hard. Like, really hard. And that is not even mentioning the initial learning curve with Git (which is something I am still working on). So, baring that in mind, take my words below to speak not of the exact sequence of events but as of what came out of a tremendous amount of struggle in what often felt like fumbling around in the dark. And so, without further ado, my experience building my first custom JavaScript app:
- Building The Canvas
Nothing happens in the browser without and HTML file to support it. So I referred back to the tutorials I’d been provided to figure out how to get my “snake arena”/HTML canvas to display correctly. In my “snake.html” file I created the following CSS in the head section section:
<style> * { padding: 0; margin: 0; } canvas { background: #000000; display:block; margin:0auto; border-color:#A9A9A9; border-style:dashed; border-width:8px; margin-top:80px; } </style>
In the body section, I wrote the following:
<canvas id="myCanvas" width="600" height="600"></canvas> <script src="snake.js"></script>
2. Building The Animation Structure
This is where the power and majesty of the JavaScript setInterval() method really came to be realized. And where, as my teacher explained, browser animation came to be know as something more like a “flipbook” than an object moving fluidly across the screen. That is, the setInterval() method tells the browser how often to refresh the function being called instead of telling an object how often to “move”. Without this simple method being called, literally nothing would move on the screen. There is more to it, as I would painfully learn later on, but this was enough to get started.
To keep things simple, I created a kind of “Master Function” for all other functions to be called in. I called it “Draw();” and set the refresh interval at 100 microseconds/ one-tenth of a second. This is what it looked like:
Draw () { //call all onscreen functions here :-) } setInterval(Draw, 100);
3. Building The Snake
For this, I started with declaring the snake as a hard coded array of 5 squares with a height and width of 10 pixels named “snake”. I then created a function called drawSnakePart() and called the array “snake” as the parameter/argument for it to work with:
function drawSnakePart(snake) { ctx.fillStyle = 'lightgreen'; ctx.strokestyle = 'darkgreen'; ctx.fillRect(snake.x, snake.y, 10, 10); ctx.strokeRect(snake.x, snake.y, 10, 10); }
function drawSnake() { snake.forEach(drawSnakePart) }
4. Getting it to move
My next step was getting the snake to move. Again, I referred to the tutorials I’d been provided. Here, I believe I should say something about those tutorials. While they were very well chosen and gave me a much deeper understanding of how JavaScript works, both of them involved creating games with a ball. There is, I would learn rather thoroughly, a large difference in coding the movements of a ball (which is simply one element) and coding the movements of a snake/array (which is a successive series of elements). It proved to be exceedingly challenging but my teacher was incredibly gracious and skillful at leading me through it.
Here is what I started with:
function moveSnake() { for(var i = 0; i < snake.length; i++) snake[i}.x += dx }
Basically, this function uses a for loop that creates a variable that moves through the length of the snake array. This allows any functionality you apply to one element of the array to apply to all of it. This can come in handy, let me tell you. Outside the for loop, the line snake[i].x += dx defines the motion of the snake as the value of the variable “dx” (10 pixels to the right) for every tenth of a second. 10 pixels, coincidentally, is also the width of each section of the snake thus giving it the impression of moving exactly one unit’s length each time.
Boy was I excited to test this! And what did I get? I got a bright green “snake” increasing by 10 pixels to the right every tenth of a second. It went from ten, to eleven then twelve, thirteen, fourteen, fifteen, sixteen units and on and on across the screen. Instead of “moving” as I had intended, it was simply “growing” in length. “Stupid snake!” I thought. “You’re supposed to move, not grow! What’s wrong with you?”. I was so frustrated I literally gave up for the day and walked away from the computer honestly wondering how I’d made it this far in life when I was so god damn dumb. But not before making a quick video outlining my situation, commiting my code to GitHub and creating a working replica on codepen for my teacher to see.
Well, later that evening while teaching a class, I remembered a section in a tutorial where after creating a ball in motion it rendered as a line increasing in length rather than a ball moving on the screen. It was the exact same problem. But, of course, in the tutorial it was fixed immediately. So, I went back to the tutorial’s source code and found this simple line of code:
ctx.clearRect(0, 0, myCanvas.width, myCanvas.height);
5. Building the random replacement of apple, score increase and growth of snake into one action
This part of the game was initially quite intimidating. First, I needed an apple. So I wrote a function for it called drawApple();. This is what it looked like:
function drawApple() { ctx.fillStyle = 'red'; ctx.strokestyle = 'darkred'; ctx.fillRect(appleLocation.x, appleLocation.y, appleWidth, appleHeight); ctx.strokeRect(appleLocation.x, appleLocation.y, appleWidth, appleHeight) }
function eatApple() { if (snake[0].x == appleLocation.x && snake[0].y == appleLocation.y) { appleLocation.x = Math.floor(Math.random() * 50) * 10; appleLocation.y = Math.floor(Math.random() * 50) * 10; } }
appleLocation.x = Math.floor(Math.random() * 50) * 10; appleLocation.y = Math.floor(Math.random() * 50) * 10;
Math.floor(Math.random());
Math.floor(Math.random() * 50)
var snakeTail = snake[snake.length - 1]; snake.push({ x: snakeTail.x, y: snakeTail.y }); score++;
function eatApple() { if (snake[0].x == appleLocation.x && snake[0].y == appleLocation.y) { appleLocation.x = Math.floor(Math.random() * 50) * 10; appleLocation.y = Math.floor(Math.random() * 50) * 10; var snakeTail = snake[snake.length - 1]; snake.push({ x: snakeTail.x, y: snakeTail.y }); score++; } }
6. Getting it to turn and follow the head
This was by far the most difficult part of the project. And, if it weren’t for the guidance and support of my teacher, I very likely would’ve given up or, at the very least, lost most of my hair in figuring out how to do it. I’m not joking about that at all, Wow, was this part tricky.
To begin with, I started with building in a “keydown” event listener that looked like this:
document.addEventListener("keydown", keyDownHandler);
function keyDownHandler(e) { if (e.key == "Right" || e.key == "ArrowRight") { snakeDirection = DIRECTION.EAST; } else if (e.key == "Left" || e.key == "ArrowLeft") { snakeDirection = DIRECTION.WEST; } else if (e.key == "Up" || e.key == "ArrowUp") { snakeDirection = DIRECTION.NORTH; } else if (e.key == "Down" || e.key == "ArrowDown") { snakeDirection = DIRECTION.SOUTH; } }
Here, a relationship is established between keys on the keyboard and directions on the canvas. It is essentially a series of if and else statements equating arrow keys on the keyboard with cardinal directions on the canvas. Left equals “West”, Right equals “East”, Up equals “North” and Down equals “South”. “e” is called as an argument to connect back to the the event listener. However, there is, as of yet no exact correlation between the “directions” in the code and any change in the current direction on the canvas. There is only a correspondence between a variable (snake direction), a constant (DIRECTION) and the respective keys on the keyboard associated with each “direction”. The functionality is determined in a separate function, moveSnake();, shown below (relevant code shown in red):
function moveSnake() { //create copy of snake var snakeCopy = []; //loop through snake for (var i = 0; i < snake.length; i++) { //for each iteration, add snake body to snake copy snakeCopy.push({ x: snake[i].x, y: snake[i].y }); } for (var i = 0; i < snake.length; i++) { if (i === 0) { if (snakeDirection === DIRECTION.EAST) { snake[i].x += dx; } if (snakeDirection === DIRECTION.WEST) { snake[i].x -= dx; } if (snakeDirection === DIRECTION.NORTH) { snake[i].y -= dy; } if (snakeDirection === DIRECTION.SOUTH) { snake[i].y += dy; } } else { snake[i].x = snakeCopy[i - 1].x; snake[i].y = snakeCopy[i - 1].y; } } }
if (i === 0)
var snakeCopy = [ ];
for (var i = 0; i < snake.length; i++)
{ //for each iteration, add snake body to snake copy snakeCopy.push({ x: snake[i].x, y: snake[i].y });
{ x: snake[i].x, y: snake[i].y }
snake[i].x = snakeCopy[i - 1].x; snake[i].y = snakeCopy[i - 1].y;
7. Encoding collision functionality
I had some familiarity with how to approach this based on the tutorials I’d been provided. I needed to build something that would stop the game if A) the snake collided with any of the four walls or B) with itself. To do this, I created two separate functions, 1) collision(); and 2) snakeBodyCollision();. Originally, I took a shortcut by relying on an alert call in the browser that would drop down if any of the logic for the collision was met. My instructor advised against this and said, instead, to have the “GAME OVER” functionality appear on the canvas. This naturally led to a more challenging time but was helpful in the end.
Here is what I wrote:
function collision() { var headX = snake[0].x; var headY = snake[0].y; if (headX >= myCanvas.width || headY >= myCanvas.height || headY <= myCanvas.height - 610 || headX <= myCanvas.width - 610) { ctx.fillText("GAME OVER. You hit the wall. Poor snaky.", 120, 300); stopGame(gameInterval);//stop the game gameOver = true; } else { snakeBodyCollision(); } }
function snakeBodyCollision() { for (var i = 1; i < snake.length; i++) if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) { ctx.fillText("Oh, boy. You just bit yourself. GAME OVER.", 120, 300); stopGame(gameInterval);//stop the game gameOver = true; } } }
In the first function, collision();, I declared two variables to represent the x and y coordinates, respectively, of the “head” of the “snake” in order to isolate the first element in the array and limit any further logic to it alone (I’d struggled considerably with this in building the snake like motion of the array in the first place).:
var headX=snake[0].x; var headY = snake[0].y;
if (headX >= myCanvas.width || headY >= myCanvas.height || headY <= myCanvas.height - 610 || headX <= myCanvas.width - 610)
ctx.fillText("GAME OVER. You hit the wall. Poor snaky.", 120, 300); stopGame(gameInterval);//stop the game gameOver = true;
function stopGame(interval) { clearInterval(gameInterval); }
gameInterval = setInterval(Draw, 100);
function snakeBodyCollision() { for (var i = 1; i < snake.length; i++) if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) { ctx.fillText("Oh, boy. You just bit yourself. GAME OVER.", 120, 300); stopGame(gameInterval);//stop the game gameOver = true; } }
if (snake[0].x == snake[i].x && snake[0].y == snake[i].y)
8. Refactoring
By this point, I had spent about 12 days building my Snake Game (after a week of working through tutorials). I probably could have done it faster but, you know, things like family, work and sleep do have a way of taking us away from our computers. Anyway, I couldn’t believe how much I’d learned in this short time or how challenging it had been. With what I’d done I felt it must be ready to go. After all, all of the “deliverables” for the app had been met, so why go against the age old saying “if it ain’t broke, don’t fix it”?
And this is where my instructor proceeded to unload a bedazzling amount of refactoring on my project. Refactoring, I would come to learn, is basically the equivalent of taking something you’ve already said and then saying it better and with fewer words. It’s the equivalent of editing for brevity in writing but no functionality is lost in the app. Really kind of amazing and something that points to just how flexible programming languages really are.
I won’t go too much into the details of how refactoring affected my code but I will share the following. Essentially, my teacher had me add my snake array object, snakeDirection, appleLocation, score variables and setInterval function into a new function called “startGame();”.
function startGame() { snake = [{ x: 150, y: 150 }, { x: 140, y: 150 }, { x: 130, y: 150 }, { x: 120, y: 150 }, { x: 110, y: 150 }, { x: 100, y: 150 }, { x: 90, y: 150 }, { x: 80, y: 150 }, { x: 70, y: 60 }, { x: 50, y: 150 } ]; snakeDirection = DIRECTION.EAST; appleLocation = { x: 250, y: 150 }; score = 0; gameInterval = setInterval(draw, 100); }
This new function was then called at the very bottom of the file. I have read this can increase the efficiency of load times and interactivity as it allows time for the entire file to be read before executing crucial functions.
9. What I Learned Toward Increasing My Coding Gong Fu
As with improving at anything, coding, so I am learning, is a matter of revision, review and practice. It also helps to know how it actually works and why. That is a large part of why I wrote this post. Not just to show you, dear reader, that I understand it but also that I understand it. Unless you are lucky enough to have an audience willing to hear your ideas, there is really no better way to reinforce your understanding than by writing it down. That said, having written it all down, here are a few of the points I picked up in getting through this project:
Pseudo-coding your project can be amazingly helpful: there is a vast difference between two human individuals communicating and an application and the browser. In the former, much can be implied and each other’s understandings can morph and change based on the addition of new information and other, more intangible factors. This is not so with coding. Coding is utterly literal and computers simply do not have the ability to improvise or, in any way whatsoever, “get what you mean”. Having your application sketched out in its entirety before you start coding it can actually save you a lot of time, to say nothing of confusion and frustration, in the end. Pseudo-code is your friend.
Simplicity is beautiful and very difficult: the answer is often far simpler than we realize. The trick is to understand the situation exactly and to understand the solution exactly. Software languages do not allow for the same expression as human languages because they deal in discrete values, not abstract meaning. The more you can understand this, the easier and concise your code will be. I found that it’s often more about a minute detail in the logic than it is the structure of your app as a whole that is causing the trouble. Of course, a simple structure is helpful, too.
Ask for help when you need it: my teacher has been amazing. But, this being a remote course, I am more or less alone in my moments of struggle. When I’m really stuck, I admit it and make a video outlining my situation, what I want to change and what I’ve tried so far. I then push all my code to GitHub and send the repository link and video to my instructor. Document your situation, try things out, problem solve and then ask somebody better than you what to do. Simple.
Google is your friend but not all resources were created equal (or written very well): One thing I really started to cultivate during this project was a deep appreciation for documentation written by people who can write well. I was amazed at how rare this actually is. A lot of stuff out there assumes a level of knowledge and experience that is way beyond the level of Junior Developers. Other resources are half-baked, outdated and sometimes even counterproductive to look through. I’ll never forget the tutorial I found on Medium for a JavaScript Snake Game the code for which didn’t work at all. My point is, you need to be careful or very experienced before you can piece something together based on Stack Overflow comments and Medium Articles. Focus on the basics and ask people you know you can trust for resources and documentation that will be useful.
Learning how to use a software language is roughly on par with learning how to use a human language: I say this because I speak Japanese at a business level. It’s a long story but I’ve been living in Japan since December of 2007 and have been studying the language, at varying degrees of intensity, fairly continuously the whole time. Coding has a lot of similarities but seems to require the same level of effort, discipline and time commitment to make yourself useful and, hopefully, employable.
Coding is an amazingly creative and fun but challenging undertaking: I’ve mentioned before that shortly after starting to learn to code my first thought was that I wished I’d started earlier. This is because it incorporates a really fun blend of creativity, logic and applicability in daily life that would be hard to match any other way. This last point makes me happy because it’s often the underlying passion along with the discipline to keep going that helps us grow and eventually reach our goals in life. And my goal of becoming a Software Developer is definitely one, I am finding, that is going to take passion to achieve.
That’s it for this post. At the time of this writing, I am in the last stages of building an app using React and NodeJs to connect to the Twitter Standard Search API. It’s a real doozy in terms of learning curve and challenge. If it weren’t for the expert guidance and feedback of my teacher, I dare say things would be a lot harder. In my next post, I’ll be going over what it was like to get through it, how it works and what I learned from it.
See code for Snake Game on GitHub here.
Play it for yourself on CodePen here.
[/et_pb_text][/et_pb_column][/et_pb_row][/et_pb_section]