Dinosaur Game (Lonely T-Rex or Chrome Dyno) in 5 minutes

About

Dinosaur game is a browser game embedded into Google Chrome browser creates as an Easter Egg by Chrome UX team in 2014.
It appears when a user is trying to browse internet without an internet connection. In this case you can play this game. Alternatively you can trigger it by going to chrome://dino or chrome://network-error/-106.
The idea of the game is very simple - the dinosaur (sometimes called as Lonely T-Rex) is running through a desert and should avoid obstacles like cacti by jumping or ducking.
I find this a very simple and fun game, so let's make own version of it in just 5 minutes.
We will use JavaScript and will draw everything on HTML canvas. As always this will be a simplified version and you can extend it further by yourself.

Link to Wikipedia: https://en.wikipedia.org/wiki/Dinosaur_Game

Live coding

Here is a live coding session that shows how it can be done.

Duration: 4:57 (coding - 4:34)

Disclaimer: never use this code in production. It was created for fun.

Breakdown

Let's break down the solution and comment on some complex or interesting things.

0:14 - starting with HTML template. As usual it is very simple - just an empty canvas element.
Next in script block we get reference to the canvas and set it's size - 600 by 250 pixels.
This is quite frustrating by HTML canvas does not have a simple way how to draw a line between to points. Basically it's a 4 step process: begin drawing path, move the point to the line start, define where the line end is and finally draw it. As we are planning to draw some lines in this project I decided that it makes sense to introduce a helper function called line that does exactly these 4 steps.

0:40 - now we need to think a bit how we will deal with our game objects. There are 2 types of game objects: the dinosaur and a cactus (an obstacle).
Both of these objects have some common properties (e.g. location and size) and should have similar functions (e.g. they can be drawn on the screen). We can call them sprites (despite Wikipedia define sprite as a bitmap that is integrated into bigger scene, we will call the whole game object as Sprite).
The constructor of the Sprite has 5 parameters: coordinates of top-left corner, dimension and draw function.
Also we are defining a couple of functions that Sprite will have:

Right now it's only a preparation work and we will get to real logics in a second.

1:23 - we need a sprite map for the dinosaur. It's not very hard to find one on the web.
For reference - this is the one we are using in this project.


Source - https://www.quora.com/How-would-I-deploy-a-game-like-Agar-io-or-Slither-io

Nice thing is that it has 6 frames each of them is 88 by 94 pixels. This means that we can have an easy approach how to understand which sub-image we should take.
Imagine that we are on a frame 123 right now. As there are total 6 frames that are cyclic, this means that we are intereted in frame number 123 % 6 = 3 (zero-based).
Then we should cut the sub-image of size 88 by 94 where top-left corner is (264, 0) (where 264 = 88 * 3).
This is exactly what is happening in drawDyno function.

1:48 - now it's time for the game loop implementation.
The main game object will be dyno - it is an instance of a Sprite object with defined dimensions (dynoW and dynoH) and logics how to draw itself (using drawDyno function).
The game loop will run every 50 milliseconds and will consist of following steps:

Notice that we are having x variable - it is how much the game field has moved (by X axis).
Here we are also achiving the effect that the dinosaur is always drawn on the same spot, but everything around is moving.
You can verify in browser that... nothing interesting is happening yet! We see a dinosaur running on the same place, but in reality it's moving. We just need to add some other objects to see it! Let's do it next!

2:18 - let's draw a cactus.
To simplfy, it can be done by combining 3 shapes:

Notice that here we are working with w and h variables - this makes the logics a bit more difficult to read. You can replace it with some specific numbers (e.g. w = 90 and h = 120) to see how it looks.

2:42 - once we know how to draw a cactus we can add it to the game.
We define a cacti array that will contain sprites for each cactus.
Pay attention to lastCactus variable. It contains an x coordinate of a cactus generated last. On each game loop cycle we can check if it's time to generate a new cactus or not.
Remember that we have x that represents the offset for the game scene. If the last cactus is leaving the scene - it's time to generate a new cactus. We add some randomness here and we generate new cactus on some distance away from the dinosaur, basically on a distance from w/3 to 4/3 * w.
You can check in the browser to see how it looks. Expected result is that the dyno is running and seeing new cacti approaching it.
For now T-Rex is just running though the cacti and we will fix it soon.

3:08 - to avoid ostacles the dinosaur should be able to jump over them.
The idea is when a user is pressing space key (with key code 32) the dinosaur should jump. I will not go into details about physics of the jump - refer to other project about fireworks that explains all used formulae.
Basically jumpDy function calculates how high the dinosaur should be at time t after the jump has started.
If you recall, initially our Sprite object had a function called getY - now it comes really handly and we can enhance it and add a logics to offset the y coordinate during the jump.
For this we maintain 2 additional variables: jumping (if the jump is initiated) and jumpTime (how long the jump is taking already). Once we call jump function we start a new jump (and notice that we cannot jump while jumping). Then on each move, if we are jumping, we need to increase the jumpTime. In case we see that jumpDy returned a negative value - it means that the jump is over and the dyno has hit the ground after the jump.
After this step we can verify everything in the browser and see that the dyno can jump.
But nothing is happening when dinosaur hits a cactus. This will be our next and final task.

3:56 - to detect collisions between dinosaur and a cactus we can use multiple strategies, but as always we'll go with most simple approach.
Consider that both dinosaur and cactus sprites are rectangles. We can take a center point of the dyno's sprite and check if it is inside the dimensions of a cactus sprite. If yes - then we have a collision.
Refer to images below. Red dot indicates dyno sprite's center. Left and right situations are considered as collisions. The middle one is ok and dyno's center is not within cactuss' bounds.

This is not a perfect solution, but it is good enough for this game.
This logics is implemented in the Sprite's hits function - it checks that center point is between [x, x + w] and [y, y + h]
And finally - if there is a cactus for which dyno.hits(cactus) then the game should be over. We'll do it in simple way - show an alert and just reload the browser.
This was the final step of the implementation and you can check the final result in the browser.

This was a fun and exciting project. I'm really happy how it all turned out. As usual, there are way how to improve the game and I will leave it to you as a homework:

Resources

Sources: https://github.com/5minute/dyno-game

See live results:https://jsfiddle.net/1rz7m2nc/