Does the difficulty for the Google dinosaur game infinitely increase or stop at a certain point?

enter image description here

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.