docs: def _shift_cells(self, get_cells, get_deltas):
          """Handles cell shifting."""
          # Don't do anything when there is an overlay.
          if self.lost or self.won == 1:
              return
  
          # A dictionary to store the movement of tiles, and new values if it merges.
          tile_moved = {}
          for y, row in enumerate(self.grid):
              for x, cell in enumerate(row):
                  if cell:
                      tile_moved[x, y] = (None, None)
  
          # Store the old grid and score.
          old_grid = [row[:] for row in self.grid]
          old_score = self.score
          self.old.append((old_grid, self.score))
          if len(self.old) > 10:
              self.old.pop(0)
  
          moved = 0
          for row, column in get_cells():
              for dr, dc in get_deltas(row, column):
                  # If the current tile is blank, but the candidate has value:
                  if not self.grid[row][column] and self.grid[dr][dc]:
                      # Move the candidate to the current tile.
                      self.grid[row][column], self.grid[dr][dc] = self.grid[dr][dc], 0
                      moved += 1
                      tile_moved[dc, dr] = (column, row), None
                  if self.grid[dr][dc]:
                      # If the candidate can merge with the current tile:
                      if self.grid[row][column] == self.grid[dr][dc]:
                          self.grid[row][column] *= 2
                          self.grid[dr][dc] = 0
                          self.score += self.grid[row][column]
                          self.won += self.grid[row][column] == self.WIN_TILE
                          tile_moved[dc, dr] = (column, row), self.grid[row][column]
                          moved += 1
                      # When hitting a tile we stop trying.
                      break
  
          # Submit the high score and get the change.
          delta = self.manager.got_score(self.score)
          free = self.free_cells()
          new_tiles = set()
  
          if moved:
              # Spawn new tiles if there are holes.
              if free:
                  x, y = random.choice(free)
                  value = self.grid[y][x] = random.randint(0, 10) and 2 or 4
                  new_tiles.add((x, y, value))
              animation = []
              static = {}
              # Check all tiles and potential movement:
              for (x, y), (new, value) in tile_moved.items():
                  # If not moved, store as static.
                  if new is None:
                      static[x, y] = old_grid[y][x]
                  else:
                      # Store the moving tile.
                      animation.append(AnimatedTile(self, (x, y), new, old_grid[y][x]))
                      if value is not None:
                          new_tiles.add(new + (value,))
              self.animate(animation, static, self.score - old_score, delta, new_tiles)
          else:
              self.old.pop()
  
          if not self.has_free_cells() and not self.has_free_moves():
              self.lost = True