Does the difficulty for the Google dinosaur game infinitely increase or stop at a certain point?
Does the difficulty for Google Chrome's Dinosaur runner game ever cap off? When I tried to get a high score (somewhere around 2000), I noticed that along the way the difficulty slowly but surely went up, not only in the speed of the dinosaur, but also the grouping of cactii, as well as introducing pterodactyl along the way.
What I'd like to know is whether or not the game scales infinitely? Does it just rapidly get faster and faster and faster? Or does the game cap off in difficulty at a specific score/distance?
TL;DR
There appears to be a maximum speed limit imposed thus "capping" the difficulty. The maximum speed is 13. The speed is also used to determine the gap between obstacles, and as such, should you reach the max speed, you also reach a cap on the gap between obstacles.
The source for the "Downasaur" can be found in a Javascript file called offline.js. It is publicly available through the Chromium GitHub repository. I can't say I've worked on the Chromium project, but I can at least try to interpret the code given its structure.
Towards the top of the file is a configuration area for the game. It begins at line 115:
/**
* Default game configuration.
* @enum {number}
*/
Runner.config = {
ACCELERATION: 0.001,
BG_CLOUD_SPEED: 0.2,
BOTTOM_PAD: 10,
// Scroll Y threshold at which the game can be activated.
CANVAS_IN_VIEW_OFFSET: -10,
CLEAR_TIME: 3000,
CLOUD_FREQUENCY: 0.5,
GAMEOVER_CLEAR_TIME: 750,
GAP_COEFFICIENT: 0.6,
GRAVITY: 0.6,
INITIAL_JUMP_VELOCITY: 12,
INVERT_FADE_DURATION: 12000,
INVERT_DISTANCE: 700,
MAX_BLINK_COUNT: 3,
MAX_CLOUDS: 6,
MAX_OBSTACLE_LENGTH: 3,
MAX_OBSTACLE_DUPLICATION: 2,
MAX_SPEED: 13,
MIN_JUMP_HEIGHT: 35,
MOBILE_SPEED_COEFFICIENT: 1.2,
RESOURCE_TEMPLATE_ID: 'audio-resources',
SPEED: 6,
SPEED_DROP_COEFFICIENT: 3,
ARCADE_MODE_INITIAL_TOP_POSITION: 35,
ARCADE_MODE_TOP_POSITION_PERCENT: 0.1
};
Take note of some of the values within this section, mainly:
-
ACCELERATION
= 0.001 -
SPEED
= 6 -
MAX_SPEED
= 13
Speed
There is an function called update
that begins at line 545 in the file. From the comments in the code for this function, it is the function that updates the game frame and schedules the next frame. Within this function is a collision check that starts at line 578:
// Check for collisions.
var collision = hasObstacles &&
checkForCollision(this.horizon.obstacles[0], this.tRex);
if (!collision) {
this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame;
if (this.currentSpeed < this.config.MAX_SPEED) {
this.currentSpeed += this.config.ACCELERATION;
}
} else {
this.gameOver();
}
The if
statement evaluates the condition !collision
, which means "if the variable collision
is not true" which is defined right before the if
statement itself. If it isn't, then we don't have a collision, so execute the code within the if
block. We can see further within the block there is another if
condition that checks if the current speed is less than the configurations MAX_SPEED
which is set to 13. If the current speed (which is defaulted to the configurations SPEED
value on line 44 of the file) is less than MAX_SPEED
, we drop into the if
code block, which takes the currentSpeed value and adds the configurations ACCELERATION
value of .001 to it. So every game frame you are accelerating by .001 more up to a max of 13.
Obstacles
From what I can tell, all obstacles adhere to the same logic except for the Pterodactyl obstacle. This obstacle is unique in that it can spawn at 3 different Y coordinates randomly (2 if on a Mobile device). The obstacle types can be found on line 1556 of the file. To limit the length of this post, I'm only including the PTERODACTYL
for example purposes:
{
type: 'PTERODACTYL',
width: 46,
height: 40,
yPos: [ 100, 75, 50 ], // Variable height.
yPosMobile: [ 100, 50 ], // Variable height mobile.
multipleSpeed: 999,
minSpeed: 8.5,
minGap: 150,
collisionBoxes: [
new CollisionBox(15, 15, 16, 5),
new CollisionBox(18, 21, 24, 6),
new CollisionBox(2, 14, 4, 3),
new CollisionBox(6, 10, 4, 7),
new CollisionBox(10, 8, 6, 9)
],
numFrames: 2,
frameRate: 1000/6,
speedOffset: .8
}
There is a function on line 2863 called updateObstacles
which is where the actual obstacle is added via another function called addNewObstacle
. The addNewObstacle
function is where the code decides what obstacle type it is (either a small cactus, large cactus, or Pterodactyl) among other things.
When the obstacle is created, another function on line 1388 called init
is called. The init
function is found on line 1409 and this is where it determines what Y coordinate to use for the obstacle through some conditional logic:
// Check if obstacle can be positioned at various heights.
if (Array.isArray(this.typeConfig.yPos)) {
var yPosConfig = IS_MOBILE ? this.typeConfig.yPosMobile :
this.typeConfig.yPos;
this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)];
} else {
this.yPos = this.typeConfig.yPos;
}
The if
statement is checking if the obstacle's (this
) yPos
property is an Array. If it is, it evaluates the ternary operator (the bit of code var yPosConfig = IS_MOBILE ? this.typeConfig.yPosMobile : this.typeConfig.yPos;
). A ternary operator acts much like an if
statement and can be used to determine what a variables value is set to depending on some condition. In this case, if the variable IS_MOBILE
is true (set at line 106), then set yPosConfig
to this.typeConfig.yPosMobile
. If IS_MOBILE
is false, then set yPosConfig
to
this.typeConfig.yPos
. In both cases, yPosConfig
is set to an Array with either 3 elements (not a mobile device) or 2 (mobile device). After yPosConfig
is set, it sets the obstacles yPos
to one of the values found in the yPosConfig
array randomly. In the event that the first if
statement determines that the yPos
is not an Array, it simply sets the obstacles yPos
to the value specified in the obstacles definition.
I also found what I think is the Gap size between obstacles code:
/**
* Calculate a random gap size.
* - Minimum gap gets wider as speed increses
* @param {number} gapCoefficient
* @param {number} speed
* @return {number} The gap size.
*/
getGap: function(gapCoefficient, speed) {
var minGap = Math.round(this.width * speed +
this.typeConfig.minGap * gapCoefficient);
var maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT);
return getRandomNum(minGap, maxGap);
},
Without going into it too much, it does seem the minimum gap size changes between obstacles the further you progress. However, since the MAX_SPEED
is 13, should you reach that speed the gap between obstacles would also cap out I believe. The gap size is set at line 1450 within the init
function.