Tutorial 1.1 - Step 6

Learn to code with step-by-step lessons. A place for students to work through programming fundamentals and build skills.

Step 6 - Clearing full rows

← Step 5 · Tutorial 1.1


The shortcoming first

With the game as it is now, when a row is completely filled (every cell in that row has a block), the row stays there. The board doesn’t clear the row, and blocks above don’t fall down. The play area fills up and the game becomes unplayable. So we need to detect full rows, remove those blocks, shift any blocks above down, and (optionally) add score.


Goal

Implement row clearing:

  1. After a piece locks (in move("down") when you call update_board()), check whether any rows are full.
  2. A row is full when the number of blocks in that row equals the width of the game area (e.g. 10).
  3. For each full row: remove all blocks in that row from the board, then shift every block that was above that row down by one (decrease each such block’s y by 1).
  4. Optionally: add score (e.g. 100 per row, or more for multiple rows at once).

You can put this logic in a method like check_rows() or clear_rows() on Game, and call it from the piece’s move() when a piece has just locked (e.g. right after update_board() and before new_piece()).


Your task

  1. Implement check_rows() on Game (or a similar name):
    • Build a way to count how many blocks are in each row (e.g. a list of length game_area[1], one count per row).
    • For each block in self.board, increment the count for that block’s row.
    • For each row that has count ≥ game_area[0] (full row):
      • Remove from self.board every block whose row equals that row.
      • For every block that remains and has row less than the cleared row, increase its row by 1 (or equivalently: set block["location"] = (block["location"][0], block["location"][1] + 1) - but careful: if you clear multiple rows, you need to shift by how many rows were cleared below the block).
    • Handle multiple full rows: remove all blocks in those rows, then for each block above, add to its row the number of cleared rows that were below it.
  2. Call check_rows() from the piece’s move() when a down collision has just happened - after update_board() and before new_piece() (so the board is up to date when we check).
  3. (Optional) Add a score attribute to Game and increase it when rows are cleared; display it on the screen.

Hints

Hint 1 - How to count blocks per row? Create a list rows = [0] * game_area[1]. Loop over self.board; for each block, get row = block["location"][1] and do rows[row] += 1. Then any index i where rows[i] >= game_area[0] is a full row.
Hint 2 - Removing blocks in a row and shifting First collect which rows are full (e.g. full_rows = [i for i in range(len(rows)) if rows[i] >= game_area[0]]). Then: remove from self.board every block where block["location"][1] is in full_rows. Then for each remaining block, if its row r is above a cleared row, it must move down: count how many cleared rows are below r (cleared rows with index > r), and add that count to r when you set the new location.
Hint 3 - Order of operations when multiple rows clear Option A: Clear one row at a time in a loop: find a full row, remove its blocks, then add 1 to the row index of every block above it, and repeat. Option B: Find all full rows, remove all blocks in those rows in one go, then for each remaining block set new_row = old_row + (number of full rows that were below old_row). Option B is cleaner for multiple rows.

Run it

Play until you fill a row. When a row becomes full, it should disappear and blocks above should fall. That moment - the row vanishing and the score going up - is the payoff: you wrote the logic that makes it happen. Try clearing two or more rows at once (a “tetris”) and watch your code handle it. Compare with the solution when ready.

→ See the solution for Step 6

You’ve finished Tutorial 1.1. ← Back to Tutorial 1.1