Handles cell shifting.

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