← Learning notesFly-in live demoMade by Claude (Anthropic) to teach me while I built Fly-in. Code shown is from that stage of the project.

2 · The models

A model is a noun from the subject turned into a class. Four of them describe the whole static map.

«Enum» ZoneType NORMAL BLOCKED RESTRICTED PRIORITY Network graph.py · the whole map nb_drones: int start, end: str zones: dict[str, Zone] connections: dict[frozenset, Connection] neighbours: dict[str, list[str]] add_zone() add_connection() link(u, v) capacity(name) Zone name: str x, y: int zone_type: ZoneType = NORMAL color: str | None = None max_drones: int = 1 move_cost → 1 or 2 Connection a: str b: str capacity: int = 1 key → frozenset({a, b}) name → "a-b" other(zone) → the other end owns many owns many has a refers to zones by name
Solid arrow: holds the object. Dashed arrow: only stores the zone's name (a string) and looks the zone up through the Network.

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:

hub: corridorA 4 3 [zone=priority color=green max_drones=2]
Part of the lineFieldTypeIf missing
corridorAnamestrparse error
4 3x, yintparse error
zone=priorityzone_typeZoneTypeZoneType.NORMAL
color=greencolorstr | NoneNone
max_drones=2max_dronesint1

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:

  1. What does ZoneType("lava") do, and how does the parser use that?
  2. What does Zone("x", 0, 0) contain for type, colour and capacity?
  3. What is move_cost for a restricted zone? For a priority zone?
  4. Why is Connection("a","b").key == Connection("b","a").key True?
  5. What does Connection("a","b").other("b") return?
← 1 · Big picture3 · The graph →