text: function GameManager(size, InputManager, Actuator, ScoreManager) {
    this.size         = size; // Size of the grid
    this.inputManager = new InputManager;
    this.scoreManager = new ScoreManager;
    this.actuator     = new Actuator;
  
    this.startTiles   = 2;
  
    this.inputManager.on("move", this.move.bind(this));
    this.inputManager.on("restart", this.restart.bind(this));
    this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));
  
    this.setup();
  }
  
  // Restart the game
  GameManager.prototype.restart = function () {
    this.actuator.continue();
    this.setup();
  };
  
  // Keep playing after winning
  GameManager.prototype.keepPlaying = function () {
    this.actuator.continue();
    this.setup();
  };
  
  GameManager.prototype.isGameTerminated = function () {
    if (this.over || (this.won && !this.keepPlaying)) {
      return true;
    } else {
      return false;
    }
  };
  
  // Set up the game
  GameManager.prototype.setup = function () {
    this.grid        = new Grid(this.size);
  
    this.score       = 0;
    this.over        = false;
    this.won         = false;
    this.keepPlaying = false;
  
    // Add the initial tiles
    this.addStartTiles();
  
    // Update the actuator
    this.actuate();
  };
  
  // Set up the initial tiles to start the game with
  GameManager.prototype.addStartTiles = function () {
    for (var i = 0; i < this.startTiles; i++) {
      this.addRandomTile();
    }
  };
  
  // Adds a tile in a random position
  GameManager.prototype.addRandomTile = function () {
    if (this.grid.cellsAvailable()) {
      var self = this;
      var bvalue = 2147483647;
      var bcell = this.grid.randomAvailableCell();
  
      for (var i = 0; i < 8; i++) {
        var cell = this.grid.randomAvailableCell();
  
        function check(x, y, dx, dy) {
          if (x < 0 || y < 0 || x >= self.grid.size || y >= self.grid.size) return;
  
          if (
            !!self.grid.cells[cell.x + x]
            &&
            !!self.grid.cells[cell.x + x][cell.y + y]
          ) {
            var tocheck = self.grid.cells[cell.x + x][cell.y + y];
            if (Math.random() < 0.8 && tocheck.value < bvalue) {
              bcell = cell;
              bvalue = tocheck.value;
            }
          } else check(x + dx, y + dy, dx, dy);
        }
  
        check(-1, 0, -1, 0);
        check(1, 0, 1, 0);
        check(0, -1, 0, -1);
        check(0, 1, 0, 1);
  
        if (bvalue == 2147483647) {bvalue = 2048;}
      }
  
      var tile = new Tile(bcell, 2048);
  
      this.grid.insertTile(tile);
    }
  };
  
  // Sends the updated grid to the actuator
  GameManager.prototype.actuate = function () {
    if (this.scoreManager.get() < this.score) {
      this.scoreManager.set(this.score);
    }
  
    this.actuator.actuate(this.grid, {
      score:      this.score,
      over:       this.over,
      won:        this.won,
      bestScore:  this.scoreManager.get(),
      terminated: this.isGameTerminated()
    });
  
  };
  
  // Save all tile positions and remove merger info
  GameManager.prototype.prepareTiles = function () {
    this.grid.eachCell(function (x, y, tile) {
      if (tile) {
        tile.mergedFrom = null;
        tile.savePosition();
      }
    });
  };
  
  // Move a tile and its representation
  GameManager.prototype.moveTile = function (tile, cell) {
    this.grid.cells[tile.x][tile.y] = null;
    this.grid.cells[cell.x][cell.y] = tile;
    tile.updatePosition(cell);
  };
  
  // Move tiles on the grid in the specified direction
  GameManager.prototype.move = function (direction) {
    // 0: up, 1: right, 2:down, 3: left
    var self = this;
  
    if (this.isGameTerminated()) return; // Don't do anything if the game's over
  
    var cell, tile;
  
    var vector     = this.getVector(direction);
    var traversals = this.buildTraversals(vector);
    var moved      = false;
  
    // Save the current tile positions and remove merger information
    this.prepareTiles();
  
    // Traverse the grid in the right direction and move tiles
    traversals.x.forEach(function (x) {
      traversals.y.forEach(function (y) {
        cell = { x: x, y: y };
        tile = self.grid.cellContent(cell);
  
        if (tile) {
          var positions = self.findFarthestPosition(cell, vector);
          var next      = self.grid.cellContent(positions.next);
  
          // Only one merger per row traversal?
          if (next && next.value === tile.value && !next.mergedFrom) {
            var merged = new Tile(positions.next, tile.value / 2);
            merged.mergedFrom = [tile, next];
  
            self.grid.insertTile(merged);
            self.grid.removeTile(tile);
  
            // Converge the two tiles' positions
            tile.updatePosition(positions.next);
  
            // Update the score
            //self.score += merged.value;
            
            if (merged.value == 1024){self.score += 2}
            if (merged.value == 512){self.score += 4}
            if (merged.value == 256){self.score += 8}
            if (merged.value == 128){self.score += 16}
            if (merged.value == 64){self.score += 32}
            if (merged.value == 32){self.score += 64}
            if (merged.value == 16){self.score += 128}
            if (merged.value == 8){self.score += 256}
            if (merged.value == 4){self.score += 512}
            if (merged.value == 2){self.score += 1024}
            
            // The mighty 2048 tile
            if (merged.value === 2) self.won = true;
          } else {
            self.moveTile(tile, positions.farthest);
          }
  
          if (!self.positionsEqual(cell, tile)) {
            moved = true; // The tile moved from its original cell!
          }
        }
      });
    });
  
    if (moved) {
      this.addRandomTile();
  
      if (!this.movesAvailable()) {
        this.over = true; // Game over!
      }
  
      this.actuate();
    }
  };
  
  // Get the vector representing the chosen direction
  GameManager.prototype.getVector = function (direction) {
    // Vectors representing tile movement
    var map = {
      0: { x: 0,  y: -1 }, // up
      1: { x: 1,  y: 0 },  // right
      2: { x: 0,  y: 1 },  // down
      3: { x: -1, y: 0 }   // left
    };
  
    return map[direction];
  };
  
  // Build a list of positions to traverse in the right order
  GameManager.prototype.buildTraversals = function (vector) {
    var traversals = { x: [], y: [] };
  
    for (var pos = 0; pos < this.size; pos++) {
      traversals.x.push(pos);
      traversals.y.push(pos);
    }
  
    // Always traverse from the farthest cell in the chosen direction
    if (vector.x === 1) traversals.x = traversals.x.reverse();
    if (vector.y === 1) traversals.y = traversals.y.reverse();
  
    return traversals;
  };
  
  GameManager.prototype.findFarthestPosition = function (cell, vector) {
    var previous;
  
    // Progress towards the vector direction until an obstacle is found
    do {
      previous = cell;
      cell     = { x: previous.x + vector.x, y: previous.y + vector.y };
    } while (this.grid.withinBounds(cell) &&
             this.grid.cellAvailable(cell));
  
    return {
      farthest: previous,
      next: cell // Used to check if a merge is required
    };
  };
  
  GameManager.prototype.movesAvailable = function () {
    return this.grid.cellsAvailable() || this.tileMatchesAvailable();
  };
  
  // Check for available matches between tiles (more expensive check)
  GameManager.prototype.tileMatchesAvailable = function () {
    var self = this;
  
    var tile;
  
    for (var x = 0; x < this.size; x++) {
      for (var y = 0; y < this.size; y++) {
        tile = this.grid.cellContent({ x: x, y: y });
  
        if (tile) {
          for (var direction = 0; direction < 4; direction++) {
            var vector = self.getVector(direction);
            var cell   = { x: x + vector.x, y: y + vector.y };
  
            var other  = self.grid.cellContent(cell);
  
            if (other && other.value === tile.value) {
              return true; // These two tiles can be merged
            }
          }
        }
      }
    }
  
    return false;
  };
  
  GameManager.prototype.positionsEqual = function (first, second) {
    return first.x === second.x && first.y === second.y;
  };
  
  // Score reset code if the score sys has updated
  GameVer = 1.005
  if (localStorage.GV == undefined) {
  localStorage.GV = GameVer
  } else {
  if (localStorage.GV < GameVer) {
  localStorage.GV = GameVer
  localStorage.bestScore = 0
  }};
  