Step 1 - Why classes?
← Tutorial 1.1 · Step 2 →
Goal
Set up a minimal Pygame window and an empty game area (the grid where Tetris blocks fall). Use a Game class to hold everything the game needs: the screen, grid size, game area size, and the board (for now, an empty list). No falling piece yet - we’re laying the foundation and seeing why putting this in a class keeps things organized.
By the end of this step you should have:
- A window that opens and stays open
- A game area drawn (e.g. a rectangle) in a fixed position
- A
Game class with attributes like screen, grid_size, game_area, game_location, and board = []
Why use a class?
Tetris has two main “things”:
- The game itself - the window, the grid, the list of locked blocks (the board), the score, and the game loop.
- The current piece - its position, shape, and rotation.
If we used only variables and functions, we’d have to pass the screen, grid size, board, and so on into every function. A class lets us group all the “game” data and the functions that use it into one object. Later, a Piece class will hold the piece’s data and methods (like move and rotate), and each piece will need a reference to the game (e.g. self.game) to check the board and draw on the screen. So: classes help us store related data and behavior in one place and avoid passing dozens of arguments around.
Your task
- Create a
Game class with an __init__ that:
- Initializes Pygame and creates a window (e.g. with
pygame.display.set_mode).
- Sets a grid size (e.g. 30 pixels per cell).
- Defines game area width and height in cells (e.g. 10×20).
- Defines game location (where the top-left of the game area is on the screen, in cells).
- Creates an empty board list (we’ll fill it with locked blocks later).
- Add a simple game loop that clears the screen, draws the game area rectangle, and updates the display.
- Draw the game area in the right place using
game_location and grid_size (convert cell coordinates to pixels).
Hints
Hint 1 - What should the Game class store?
The game needs to know: the **screen** (from `pygame.display.set_mode`), the **grid_size** (pixels per cell), the **game_area** (width, height in cells, e.g. `(10, 20)`), and the **game_location** (e.g. `(6, 3)` so the play area isn’t at the very top-left). It also needs a **board** list that will later hold locked blocks. Store these as `self.screen`, `self.grid_size`, etc. in `__init__`.
Hint 2 - How do you draw the game area?
Use `pygame.draw.rect`. The position in pixels is: `game_location[0] * grid_size` for x and `game_location[1] * grid_size` for y. The size in pixels is: `game_area[0] * grid_size` for width and `game_area[1] * grid_size` for height. Pick a colour (e.g. black) so the play area is visible.
Hint 3 - Basic game loop structure
Use a `while True` loop. Inside: handle `pygame.QUIT` so the window can close; clear the screen with `screen.fill(...)`; draw the game area rect; call `pygame.display.update()`; and use `clock.tick(60)` to limit the frame rate. You can put the loop in a method like `run()` or `start()` and call it after creating `Game()`.
Run it
Run your program now. When you run your program you should see a window with a clear “play area” rectangle. Once you’re happy with your solution (or want to compare), open the solution page.
→ See the solution for Step 1
When you’re ready, go to Step 2 - Storing the board and the current piece.