diff --git a/gen/radios.py b/gen/radios.py index ced4ac9c..ae464622 100644 --- a/gen/radios.py +++ b/gen/radios.py @@ -2,7 +2,7 @@ import itertools import logging from dataclasses import dataclass -from typing import Dict, Iterator, List, Set +from typing import Dict, FrozenSet, Iterator, List, Reversible, Set, Tuple @dataclass(frozen=True) @@ -45,14 +45,8 @@ def kHz(num: int) -> RadioFrequency: @dataclass(frozen=True) -class Radio: - """A radio. - - Defines the minimum (inclusive) and maximum (exclusive) range of the radio. - """ - - #: The name of the radio. - name: str +class RadioRange: + """Defines the minimum (inclusive) and maximum (exclusive) range of the radio.""" #: The minimum (inclusive) frequency tunable by this radio. minimum: RadioFrequency @@ -63,19 +57,51 @@ class Radio: #: The spacing between adjacent frequencies. step: RadioFrequency - def __str__(self) -> str: - return self.name + #: Specific frequencies to exclude. (e.g. Guard channels) + excludes: FrozenSet[RadioFrequency] = frozenset() def range(self) -> Iterator[RadioFrequency]: """Returns an iterator over the usable frequencies of this radio.""" return ( RadioFrequency(x) for x in range(self.minimum.hertz, self.maximum.hertz, self.step.hertz) + if RadioFrequency(x) not in self.excludes ) @property def last_channel(self) -> RadioFrequency: - return RadioFrequency(self.maximum.hertz - self.step.hertz) + return next( + RadioFrequency(x) + for x in reversed( + range(self.minimum.hertz, self.maximum.hertz, self.step.hertz) + ) + if RadioFrequency(x) not in self.excludes + ) + + +@dataclass(frozen=True) +class Radio: + """A radio. + + Defines ranges of usable frequencies of the radio. + """ + + #: The name of the radio. + name: str + + #: List of usable frequency range of this radio. + ranges: Tuple[RadioRange, ...] + + def __str__(self) -> str: + return self.name + + def range(self) -> Iterator[RadioFrequency]: + """Returns an iterator over the usable frequencies of this radio.""" + return itertools.chain.from_iterable(rng.range() for rng in self.ranges) + + @property + def last_channel(self) -> RadioFrequency: + return self.ranges[-1].last_channel class ChannelInUseError(RuntimeError): @@ -88,53 +114,53 @@ class ChannelInUseError(RuntimeError): # TODO: Figure out appropriate steps for each radio. These are just guesses. #: List of all known radios used by aircraft in the game. RADIOS: List[Radio] = [ - Radio("AN/ARC-164", MHz(225), MHz(400), step=MHz(1)), - Radio("AN/ARC-186(V) AM", MHz(116), MHz(152), step=MHz(1)), - Radio("AN/ARC-186(V) FM", MHz(30), MHz(76), step=MHz(1)), + Radio("AN/ARC-164", (RadioRange(MHz(225), MHz(400), step=MHz(1)),)), + Radio("AN/ARC-186(V) AM", (RadioRange(MHz(116), MHz(152), step=MHz(1)),)), + Radio("AN/ARC-186(V) FM", (RadioRange(MHz(30), MHz(76), step=MHz(1)),)), # The AN/ARC-210 can also use [30, 88) and [108, 118), but the current # implementation can't implement the gap and the radio can't transmit on the # latter. There's still plenty of channels between 118 MHz and 400 MHz, so # not worth worrying about. - Radio("AN/ARC-210", MHz(118), MHz(400), step=MHz(1)), - Radio("AN/ARC-222", MHz(116), MHz(174), step=MHz(1)), - Radio("SCR-522", MHz(100), MHz(156), step=MHz(1)), - Radio("A.R.I. 1063", MHz(100), MHz(156), step=MHz(1)), - Radio("BC-1206", kHz(200), kHz(400), step=kHz(10)), + Radio("AN/ARC-210", (RadioRange(MHz(118), MHz(400), step=MHz(1)),)), + Radio("AN/ARC-222", (RadioRange(MHz(116), MHz(174), step=MHz(1)),)), + Radio("SCR-522", (RadioRange(MHz(100), MHz(156), step=MHz(1)),)), + Radio("A.R.I. 1063", (RadioRange(MHz(100), MHz(156), step=MHz(1)),)), + Radio("BC-1206", (RadioRange(kHz(200), kHz(400), step=kHz(10)),)), # Note: The M2000C V/UHF can operate in both ranges, but has a gap between # 150 MHz and 225 MHz. We can't allocate in that gap, and the current # system doesn't model gaps, so just pretend it ends at 150 MHz for now. We # can model gaps later if needed. - Radio("TRT ERA 7000 V/UHF", MHz(118), MHz(150), step=MHz(1)), - Radio("TRT ERA 7200 UHF", MHz(225), MHz(400), step=MHz(1)), + Radio("TRT ERA 7000 V/UHF", (RadioRange(MHz(118), MHz(150), step=MHz(1)),)), + Radio("TRT ERA 7200 UHF", (RadioRange(MHz(225), MHz(400), step=MHz(1)),)), # Tomcat radios # # https://www.heatblur.se/F-14Manual/general.html#an-arc-159-uhf-1-radio - Radio("AN/ARC-159", MHz(225), MHz(400), step=MHz(1)), + Radio("AN/ARC-159", (RadioRange(MHz(225), MHz(400), step=MHz(1)),)), # AN/ARC-182 can also operate from 30 MHz to 88 MHz, as well as from 225 MHz # to 400 MHz range, but we can't model gaps with the current implementation. # https://www.heatblur.se/F-14Manual/general.html#an-arc-182-v-uhf-2-radio - Radio("AN/ARC-182", MHz(108), MHz(174), step=MHz(1)), + Radio("AN/ARC-182", (RadioRange(MHz(108), MHz(174), step=MHz(1)),)), # Also capable of [103, 156) at 25 kHz intervals, but we can't do gaps. - Radio("FR 22", MHz(225), MHz(400), step=kHz(50)), + Radio("FR 22", (RadioRange(MHz(225), MHz(400), step=kHz(50)),)), # P-51 / P-47 Radio # 4 preset channels (A/B/C/D) - Radio("SCR522", MHz(100), MHz(156), step=kHz(25)), - Radio("R&S M3AR VHF", MHz(120), MHz(174), step=MHz(1)), - Radio("R&S M3AR UHF", MHz(225), MHz(400), step=MHz(1)), + Radio("SCR522", (RadioRange(MHz(100), MHz(156), step=kHz(25)),)), + Radio("R&S M3AR VHF", (RadioRange(MHz(120), MHz(174), step=MHz(1)),)), + Radio("R&S M3AR UHF", (RadioRange(MHz(225), MHz(400), step=MHz(1)),)), # MiG-15bis - Radio("RSI-6K HF", MHz(3, 750), MHz(5), step=kHz(25)), + Radio("RSI-6K HF", (RadioRange(MHz(3, 750), MHz(5), step=kHz(25)),)), # MiG-19P - Radio("RSIU-4V", MHz(100), MHz(150), step=MHz(1)), + Radio("RSIU-4V", (RadioRange(MHz(100), MHz(150), step=MHz(1)),)), # MiG-21bis - Radio("RSIU-5V", MHz(118), MHz(140), step=MHz(1)), + Radio("RSIU-5V", (RadioRange(MHz(118), MHz(140), step=MHz(1)),)), # Ka-50 # Note: Also capable of 100MHz-150MHz, but we can't model gaps. - Radio("R-800L1", MHz(220), MHz(400), step=kHz(25)), - Radio("R-828", MHz(20), MHz(60), step=kHz(25)), + Radio("R-800L1", (RadioRange(MHz(220), MHz(400), step=kHz(25)),)), + Radio("R-828", (RadioRange(MHz(20), MHz(60), step=kHz(25)),)), # UH-1H - Radio("AN/ARC-51BX", MHz(225), MHz(400), step=kHz(50)), - Radio("AN/ARC-131", MHz(30), MHz(76), step=kHz(50)), - Radio("AN/ARC-134", MHz(116), MHz(150), step=kHz(25)), - Radio("R&S Series 6000", MHz(100), MHz(156), step=kHz(25)), + Radio("AN/ARC-51BX", (RadioRange(MHz(225), MHz(400), step=kHz(50)),)), + Radio("AN/ARC-131", (RadioRange(MHz(30), MHz(76), step=kHz(50)),)), + Radio("AN/ARC-134", (RadioRange(MHz(116), MHz(150), step=kHz(25)),)), + Radio("R&S Series 6000", (RadioRange(MHz(100), MHz(156), step=kHz(25)),)), ] @@ -175,7 +201,7 @@ class RadioRegistry: # Not a real radio, but useful for allocating a channel usable for # inter-flight communications. - BLUFOR_UHF = Radio("BLUFOR UHF", MHz(225), MHz(400), step=MHz(1)) + BLUFOR_UHF = Radio("BLUFOR UHF", (RadioRange(MHz(225), MHz(400), step=MHz(1)),)) def __init__(self) -> None: self.allocated_channels: Set[RadioFrequency] = set()