2 · The models
A model is a noun from the subject turned into a class. Four of them describe the whole static map.
From one line of text to one object
This is all the parser does, line by line. Each coloured part of the line becomes one field:
| Part of the line | Field | Type | If missing |
|---|---|---|---|
| corridorA | name | str | parse error |
| 4 3 | x, y | int | parse error |
| zone=priority | zone_type | ZoneType | ZoneType.NORMAL |
| color=green | color | str | None | None |
| max_drones=2 | max_drones | int | 1 |
Result: Zone(name='corridorA', x=4, y=3, zone_type=ZoneType.PRIORITY, color='green', max_drones=2)
Connections work the same way: connection: corridorA-tunnelB [max_link_capacity=2] becomes Connection(a='corridorA', b='tunnelB', capacity=2).
The Python you need (shown on unrelated examples)
Enum
from enum import Enum
class Light(Enum):
RED = "red"
GREEN = "green"
Light("red") # Light.RED
Light("blue") # ValueError!
That ValueError is free validation: the parser catches it and reports "invalid zone type".
dataclass
from dataclasses import dataclass
@dataclass
class Book:
title: str
pages: int
author: str | None = None
Book("Dune", 600)
# Book(title='Dune', pages=600,
# author=None)
Writes __init__, __repr__ and __eq__ for you. Fields with defaults must come last.
property
@dataclass
class Book:
pages: int
@property
def is_long(self) -> bool:
return self.pages > 500 # used as book.is_long, no ()
That's the shape of move_cost on Zone and key / name on Connection.
The questions everyone gets stuck on
Why an Enum and not just the string "restricted"?
A typo like zone.zone_type == "restriced" is silently always False. With ZoneType.RESTRICED, Python and mypy fail immediately. And ZoneType("lava") raising ValueError gives you the "invalid type" check for free.
Why does Connection store zone names and not Zone objects?
Three reasons. The file only contains names, so the parser has them already. The output only prints names. And by default a @dataclass can't go in a set or be a dict key (its __hash__ is turned off), while strings always can. When you need the actual zone: network.zones[name].
Why a frozenset as the connection key, not a tuple?
("a", "b") != ("b", "a"), but frozenset({"a", "b"}) == frozenset({"b", "a"}). Connections have no direction, so the key shouldn't have one either. That makes "b-a is a duplicate of a-b" a one-line check. It must be frozen: a normal set can change, so it can't be a dict key.
Start and end have unlimited capacity. Where does that rule go?
Not in Zone: keep the zone faithful to the file. The role (start/end) is known by the Network, so Network.capacity(name) returns None ("unlimited") for those two and max_drones for everyone else. One method, one place for the exception.
Where do x and y get used?
Only for drawing. Movement cost comes from the zone type, never from distance. Don't compute anything from coordinates in the pathfinder.
Do I need a Drone class now?
No. You don't know yet what it needs. Write it when you build the simulation (page 4–5). Today you're only describing the static map.
Should Zone check that max_drones is positive?
No, the parser does that, because only the parser knows the line number for the error message. Models assume they receive valid data.
Check yourself
You're ready to write models.py when you can answer these without looking:
- What does
ZoneType("lava")do, and how does the parser use that? - What does
Zone("x", 0, 0)contain for type, colour and capacity? - What is
move_costfor a restricted zone? For a priority zone? - Why is
Connection("a","b").key == Connection("b","a").keyTrue? - What does
Connection("a","b").other("b")return?