🍎 Section 05: Add the Food Object

Section Summary

In this section, students create the Food class in food.py. This class is responsible for placing food on valid empty grid cells and respawning food after it is eaten.

By the end of this section, students will understand how composition works in OOP: the game will use separate Snake and Food objects that collaborate through clear interfaces.

✅ Checklist

  • [ ] Create food.py.
  • [ ] Add imports, type alias, and Food class definition.
  • [ ] Implement __init__ to place food on a free cell.
  • [ ] Implement respawn() to move food to a new free cell.
  • [ ] Ensure food never appears on occupied snake cells.
  • [ ] Run s05_test.py to verify food placement rules.

Core Concepts

1. OOP Composition

Composition means one object owns or uses other objects. In this project, Game will manage both Snake and Food. Each class handles its own responsibility: - Snake manages snake movement and collisions. - Food manages food position and respawning.

This keeps code modular and easier to maintain.

2. Encapsulation of Spawn Rules

The rule "food must be on an empty tile" is handled inside Food. Other files do not need to re-implement spawn logic. They simply provide occupied cells and trust Food to pick a valid position.

3. Reusing Shared Utilities

Food uses random_empty_cell() from utils.py. Reusing tested helper logic avoids duplicate bugs and keeps behavior consistent across the project.

4. Constructor vs Behavior Method

  • __init__(occupied) sets the first valid food position.
  • respawn(occupied) applies the same rule later after food is eaten.

This shows how classes separate one-time setup and repeatable behavior.

Code Students Will Type (food.py)

Type this code by hand so you understand how the Food object controls placement.

Code image: s05-code

Detailed Code Review & Key Concepts

Imports and Type Alias

  • random_empty_cell is imported from utils to reuse the shared spawn rule.
  • Coord = Tuple[int, int] keeps position types readable.

Food.__init__

  • Takes an occupied iterable (usually snake body cells).
  • Immediately chooses one valid empty cell and stores it in self.pos.

Food.respawn

  • Uses the same occupied-cell rule as initialization.
  • Updates self.pos when new food should appear.

Key idea: the class has one clear job, and both methods enforce the same placement contract.

Test File (s05_test.py)

Use this test file to verify that food placement always avoids occupied cells.

Code image: s05-test

This test file verifies the class contract in three ways: - test_food_init_picks_only_free_cell() confirms initialization picks a valid free coordinate. - test_food_respawn_uses_latest_occupied_cells() confirms respawn follows updated occupancy and moves to the correct new free spot. - test_food_init_raises_when_grid_full() confirms full-grid edge handling by checking for ValueError.

These checks ensure food placement is safe before integrating Food into the full game loop.