- Purchase and install Plants vs. Zombies: Game of the Year from Steam
- Python environment with required dependencies
- Game resolution: 800×600 (recommended) or custom (requires calibration)
-
Launch Plants vs. Zombies
Start the game manually and ensure the game window title is
"Plants vs. Zombies". -
Navigate to a Level
Select a level (e.g., the first level) and enter the game.
-
Pause the Game
- Press ESC or click the pause button to enter the pause screen
- Ensure the "Back to Game" button is visible
- Do not click "Back to Game" - stay on the pause screen
Why pause? The framework will automatically click the "Back to Game" button to start the game. This ensures synchronized timing and prevents missing the initial game state.
-
Run the Test
Use the following command to start the agent:
python scripts/play_game.py --config ./src/agent_client/configs/pvz/config.yaml
The agent performs the following loop for max_steps iterations:
- Automatically clicks "Back to Game" button (on first step only)
- Captures screenshot every
screenshot_intervalseconds - Sends image to the vision-language model (LLM)
- Parses LLM output into semantic actions:
plant <slot> at (<row>, <col>)- Plant from slot at grid positioncollect- Collect visible sunlightwait- Wait and observe
- Executes actions via simulated mouse clicks
- Runs for exactly
max_stepsiterations (no early termination)
Control test behavior in config.yaml:
runner:
max_steps: 50 # Test duration (number of agent steps)
env:
screenshot_interval: 0.5 # Screenshot frequency (seconds)
action_mode: "semantic" # "semantic" or "gui"The game uses a 5×9 grid system for the lawn:
- 5 rows (top to bottom, 1-indexed for LLM: 1-5, 0-indexed internally: 0-4)
- 9 columns (left to right, 1-indexed for LLM: 1-9, 0-indexed internally: 0-8)
- House on the left, zombies enter from the right
Default Coordinates (800×600 resolution):
The coordinates are defined in src/game_servers/pvz/game/constants.py:
# Grid configuration
GRID_ROWS = 5
GRID_COLS = 9
GRID_CELL_WIDTH = 81 # Cell width (pixels)
GRID_CELL_HEIGHT = 99 # Cell height (pixels)
GRID_OFFSET_X = 37 # Lawn top-left X offset
GRID_OFFSET_Y = 78 # Lawn top-left Y offset
# Plant slot configuration
PLANT_SLOT_WIDTH = 58 # Distance between plant card slots
NUM_PLANT_SLOTS = 8 # Number of available plant slots
# Back to Game button (automatically clicked on start)
BACK_TO_GAME_X = 400 # Button center X
BACK_TO_GAME_Y = 500 # Button center YCoordinate Conversion:
Grid positions are converted to screen coordinates using:
def grid_to_screen(row, col):
x = GRID_OFFSET_X + col * GRID_CELL_WIDTH + GRID_CELL_WIDTH // 2
y = GRID_OFFSET_Y + row * GRID_CELL_HEIGHT + GRID_CELL_HEIGHT // 2
return (x, y)
def get_plant_slot_position(slot_index):
x = 121 + slot_index * PLANT_SLOT_WIDTH
y = 43 # Fixed Y position for all slots
return (x, y)Method: Color and Shape Detection
The system uses OpenCV-based detection (no YOLO model required):
-
HSV Color Filtering
- Detects bright yellow regions (H: 18-42, S: 80-255, V: 180-255)
-
Shape Filtering
- Area range: 1500-4500 pixels
- Circularity threshold: >0.38 (distinguishes sun from sunflowers)
-
Region Exclusion
- Excludes top-left sun counter UI
- Excludes top plant card area
- Only detects in game play area
Key Parameters (in pvz_env.py):
SUN_MIN_AREA = 1500 # Minimum sun area (pixels)
SUN_MAX_AREA = 4500 # Maximum sun area (pixels)
SUN_MIN_CIRCULARITY = 0.38 # Circularity threshold
GAME_AREA_TOP = 90 # Exclude top UI area
SUN_COUNTER_X_MAX = 75 # Exclude sun counter
SUN_COUNTER_Y_MAX = 95Why This Works:
- Sunlight is nearly circular (circularity ~0.4-0.7)
- Sunflowers have petals (circularity ~0.25-0.35)
- Bright yellow HSV values are distinctive
If your game runs at a different resolution than 800×600, you need to adjust the coordinates in constants.py.
Manual Calibration Method:
-
Take a screenshot of the pause screen and a gameplay screen
-
Measure pixel coordinates using an image editor (e.g., Paint, GIMP, Photoshop):
- Center of "Back to Game" button →
BACK_TO_GAME_X,BACK_TO_GAME_Y - Centers of plant slots 1, 2, 3 → Calculate
PLANT_SLOT_WIDTH - Center of grid cell (1,1) (top-left) → Calculate
GRID_OFFSET_X,GRID_OFFSET_Y - Center of grid cell (1,2) (second column) → Calculate
GRID_CELL_WIDTH - Center of grid cell (2,1) (second row) → Calculate
GRID_CELL_HEIGHT
- Center of "Back to Game" button →
-
Update
constants.pywith your measured values:# Example for 1280×720 resolution (adjust based on your measurements) GRID_CELL_WIDTH = 130 GRID_CELL_HEIGHT = 158 GRID_OFFSET_X = 60 GRID_OFFSET_Y = 125 PLANT_SLOT_WIDTH = 93 BACK_TO_GAME_X = 640 BACK_TO_GAME_Y = 600
-
Test by running the agent and verifying clicks land correctly
Tips:
- Measure from cell/button centers, not edges
- Use the pause screen screenshot for "Back to Game" button
- Use a gameplay screenshot for grid and plant slots
- Test with a single plant action to verify accuracy before full runs