I decided to start a series of retrospective posts about previous projects I’ve worked on outside of school and work. This one will be about my Space Invaders game. I’m forking the code and placing it on a ‘retrospective’ branch, so if I decide to change main in the future it won’t invalidate this code review.
First, I just want to examine the page and the game without looking at how things are implemented. What seems like it could be done better? What differs from the original?
Starting with the page wrapping the canvas element, there are some pretty obvious changes to be made. The page definitely needs some information on how to play the game. What are the controls? It’s not hard to figure out, but you shouldn’t have to figure them out in the first place. Another problem is that the game isn’t playable on mobile. It expects keyboard input to play, so it won’t let you start the game. Some sort of pop-up needs to show, or the controls need to be changed to accommodate mobile users. The same pop-up should probably be shown if the user has Javascript disabled.
As for the game itself, there are a few obvious missing pieces. The most glaring is the lack of sound. Sound is a huge part of what made Space Invaders so popular, so it’s lack here is jarring. It probably would have been an easy inclusion too, if I had bothered to add it. Extra lives and bonus ships are also missing, and probably would have been easy adds as well. There are a couple other more technical differences too. Enemy bullets in the original fizzle when they hit the bottom. Player bullets and enemy bullets don’t collide in my version, which is an important part of the first game. Finally, the barriers in the original game work very differently from mine. Barriers in my game are just a set of block objects. When a bullet collides with the block, they both vanish. The barrier can take exactly as many hits as there are blocks in the barrier (14). In the original game, that’s not how they work. Each barrier is an image and a bitwise mask is applied to each image to delete pixels off the image in memory (link to specifics). This is why barriers in the original can take a variable number of hits depending on how and where the mask deletes pixels and it’s why barriers in the original look much more ragged after a few hits than mine do. This would be really cool to implement if I had the time.
Hooo boy. The first thing I notice is how all the code is in one, massive, monolithic file. Why did I do it this way? Probably because past me was too lazy to look up how to import JS code from other files and figured I’d refactor it later. Then later came and I put it off even more. The obvious refactor here is to move every class into its own file (maybe keeping the bullets with their spawning classes and the game controller with main?). Speaking of classes, I didn’t use them for some reason? Instead I used functions, which JS apparently treats like objects anyways? As a result, I can’t and don’t have any inheritance. Awesome. That means there’s loads of duplicate code for things like collision checks. Some parent classes would help cut down the size of this project by 50 or more lines. Adding classes would also make it so that many of the variables for these objects aren’t hard-coded, which is a major problem for this code’s maintainability. Just another reason to do things the right way.
Then there’s the game controller and main function. The game controller is a monolith by itself, spanning over 400 lines of code and making up half the project. This should probably be broke up into either sub-objects or functions with responsibility for different things, like enemies, the player and so on. One other thing I noticed here is that a lot of my loops are using the style for (var i = 0; i < list.length; i++)
. This is supposed to be pretty inefficient; the loop recalculates the list length every time it runs. A better fix would be to save the list length as a local variable. It’s a small thing, and probably over-optimization, but it’s a good habit to get into. Moving into the main function, the biggest problem is that nearly everything related to game state is a global variable. These should instead be instance variables attached to the game controller. Finally, I noticed that I attempted to store the high score as a cookie, but this doesn’t seem to be working.
Missing features are things I think the project needed in order to be complete. For this project, music, extra lives and bonus ships are all needed. Future features are less important. They’re things I might implement some day if I have the time and want to work on the project again. One idea might be to change the barrier behavior to match the original. Another might be to store high scores in a SQL database somewhere, so that a global high score list can be maintained. This would also let me play around with various security topics, since I’d have to implement a backend that could verify a score was legitimate and could prevent users from trying SQL injection attacks through the name entry field. Breaking my own database by inserting various fake high scores sounds fun. A final project idea would be to use some of the code written in my other projects to add in networked play. Multiplayer co-op sounds really cool, but even just adding spectators could be interesting.
Overall, my first foray into Javascript has a lot of amateur mistakes and bad design choices. Still, it manages to recreate the feeling of the original pretty well.