├── Tests
├── __init__.py
├── AreaItems
│ ├── __init__.py
│ └── testAreaItem.py
├── Mapping
│ └── __init__.py
├── SystemData
│ ├── __init__.py
│ └── testUtilities.py
├── Pathfinding
│ ├── __init__.py
│ ├── Schemas
│ │ └── testLandmarksExtremesRegression.py
│ └── testTradeMPCalculation.py
├── DeltaFiles
│ ├── ReadSectorsDummy1.sec
│ ├── ReadSectorsDummy2.sec
│ ├── border_blowup_3
│ │ └── Raakaan.sec
│ ├── no_subsectors_named
│ │ ├── Knaeleng empty.sec
│ │ ├── Ngathksirz empty.sec
│ │ ├── Zao Kfeng Ig Grilokh empty.sec
│ │ └── Zao Kfeng Ig Grilokh - subsector P - trimmed.sec
│ ├── sector_subset_blowup_on_vland_empty
│ │ └── Vland.sec
│ ├── border_blowup_2
│ │ └── Zdiedeiant.sec
│ ├── border_blowup_1
│ │ └── Zdiedeiant.sec
│ ├── insufficient_exhaust_value
│ │ └── Core.sec
│ ├── single_system_border
│ │ └── Deneb.sec
│ ├── zao_kfeng_jump_4_template_blowup
│ │ └── Zao Kfeng Ig Grilokh.sec
│ ├── high_pop_worlds_blowup
│ │ └── Spinward Marches.sec
│ ├── Dagudashaag-star-object-no-sector-attribute.sec
│ ├── stat_calc_division_by_zero_population
│ │ └── Dagudashaag-zero.sec
│ ├── stars_node_types
│ │ └── Antares.sec
│ ├── xroute_routes_pass_1_2
│ │ ├── Core.sec
│ │ ├── Diaspora.sec
│ │ ├── Fornast.sec
│ │ └── Massilia.sec
│ ├── Passes
│ │ ├── Dagudashaag-auxiliary-reduction-two-lines.sec
│ │ └── Dagudashaag-subsector-full-reduce.sec
│ ├── dagudashaag-allegiance-pax-balance
│ │ └── Dagudashaag-delta-error.sec
│ ├── xroute_routes_pass_3_4
│ │ └── Dagudashaag.sec
│ ├── xroute_routes_pass_3_2
│ │ └── Zarushagar.sec
│ ├── xroute_routes_pass_1_4
│ │ ├── Delphi.sec
│ │ ├── Core.sec
│ │ └── Massilia.sec
│ ├── dijkstra_restart_blowup
│ │ └── Lishun.sec
│ ├── xroute_calculation_blowups
│ │ └── Antares-Sakhag-Celebes.sec
│ ├── Dagudashaag-subsector-reduced.sec
│ ├── Zarushagar-imbalanced-routes.sec
│ ├── xroute_routes_pass_2_4
│ │ └── Dagudashaag.sec
│ └── duplicate_node_blowup
│ │ └── Verge.sec
├── TradeMPFiles
│ ├── trademplist.txt
│ └── run_trademp_parameters
├── OutputFiles
│ ├── verify_coreward_rimward
│ │ ├── Ngathksirz Sector.pdf
│ │ └── Zao Kfeng Ig Grilokh Sector.pdf
│ ├── verify_spinward_trailing
│ │ ├── Knaeleng Sector.pdf
│ │ └── Zao Kfeng Ig Grilokh Sector.pdf
│ ├── verify_quadripoint_trade_write
│ │ ├── Deneb Sector.pdf
│ │ ├── Corridor Sector.pdf
│ │ ├── Provence Sector.pdf
│ │ └── Tuglikki Sector.pdf
│ ├── verify_single_system_border_write
│ │ └── Deneb Sector.pdf
│ ├── verify_subsector_xroute_write
│ │ └── Zarushagar Sector.pdf
│ ├── verify_empty_sector_write
│ │ └── Zao Kfeng Ig Grilokh empty.pdf
│ ├── verify_subsector_comm_write
│ │ ├── Zao Kfeng Ig Grilokh - subsector P - comm.pdf
│ │ └── comm stars.txt
│ └── verify_subsector_trade_write
│ │ └── Zao Kfeng Ig Grilokh - subsector P - trade.pdf
├── StatCalculation
│ ├── testUWPCollection.py
│ └── testPopulations.py
├── Inputs
│ └── testReadSector.py
├── Hypothesis
│ ├── Inputs
│ │ └── testStarlineTransformer.py
│ ├── testNobles.py
│ └── testTradeBalance.py
├── Utilities
│ └── testNoNoneDefaultDict.py
├── testDeltaDebug.py
├── Dummy
│ └── DeltaDebug
│ │ └── DummyReduce.py
├── testBase.py
├── DeltaDictionary
│ └── testAllegianceSubset.py
├── testNobles.py
├── PathfindingFiles
│ └── single_source_distances_ibara_subsector_from_0101.json
├── testUniqueSectors.py
├── BorderGeneration
│ ├── Far Frontiers-partial-allymap-border.json
│ └── Far Frontiers-partial-allymap-allymap.json
├── testAllyGen.py
├── Outputs
│ ├── testCursor.py
│ └── testClipping.py
├── baseTest.py
└── Pyfakefs
│ ├── baseTest.py
│ └── AreaItems
│ └── testGalaxy.py
├── PyRoute
├── Calculation
│ ├── __init__.py
│ ├── OwnedWorldCalculation.py
│ └── NoneCalculation.py
├── DeltaDebug
│ ├── __init__.py
│ ├── DeltaLogicError.py
│ └── DeltaGalaxy.py
├── StatCalculation
│ ├── __init__.py
│ ├── UWPCollection.py
│ └── Populations.py
├── Fonts
│ ├── FreeMono.ttf
│ ├── Symbola-hint.ttf
│ ├── DejaVuSerifCondensed.ttf
│ ├── LiberationMono-Bold.ttf
│ ├── ZapfDingbats-Regular.ttf
│ └── DejaVuSerifCondensed-Bold.ttf
├── Pathfinding
│ ├── minmaxheap.pyx
│ ├── setup.py
│ ├── minmaxheap.pxd
│ ├── LandmarkSchemes
│ │ ├── LandmarksWTNExtremes.py
│ │ ├── LandmarksQExtremes.py
│ │ ├── LandmarksRExtremes.py
│ │ ├── LandmarksSExtremes.py
│ │ └── LandmarkAvoidHelper.py
│ ├── DistanceBase.py
│ ├── RouteLandmarkGraph.py
│ ├── DistanceGraph.py
│ └── single_source_dijkstra.py
├── Errors
│ └── MultipleWPopError.py
├── Outputs
│ ├── Colour.py
│ ├── LightModePDFSectorMap.py
│ ├── LightModeGraphicSectorMap.py
│ ├── GraphicLine.py
│ ├── DarkModePDFSectorMap.py
│ ├── Cursor.py
│ └── FontLayer.py
├── Inputs
│ ├── StarlineTransformer.py
│ ├── StarlineStationTransformer.py
│ ├── StarlineParser.py
│ └── StarlineStationParser.py
├── __init__.py
├── DataClasses
│ └── ReadSectorOptions.py
├── templates
│ ├── subsectors.wiki
│ ├── allegiances.wiki
│ ├── sector_data.wiki
│ ├── sectors.wiki
│ ├── sector_econ.wiki
│ └── summary.wiki
├── UWPCodes.py
├── DeltaPasses
│ ├── BeyondLineReducer.py
│ ├── FullLineReduce.py
│ ├── IdentityLineReduce.py
│ ├── ImportanceLineReduce.py
│ ├── NBZLineReduce.py
│ ├── BaseLineReduce.py
│ ├── ZoneLineReduce.py
│ ├── AuxiliaryLineReduce.py
│ ├── CapitalLineReduce.py
│ ├── NoblesTrimLineReduce.py
│ ├── BaseTrimLineReduce.py
│ ├── ZoneTrimLineReduce.py
│ ├── TradeCodeLineReduce.py
│ ├── PortAndTlLineReduce.py
│ ├── TradeCodeTrimLineReduce.py
│ ├── Canonicalisation.py
│ ├── WidenHoleReducer.py
│ └── TwoLineReducer.py
├── Utilities
│ ├── UnpackFilename.py
│ └── NoNoneDefaultDict.py
├── AreaItems
│ ├── AreaItem.py
│ └── Subsector.py
├── SystemData
│ └── Utilities.py
├── unique_sectors.py
├── Nobles.py
└── downloadsec.py
├── deepspace.txt
├── sectorttwlist.txt
├── requirements.txt
├── sectorimplist.txt
├── sectorzholist.txt
├── pytest.ini
├── .gitignore
├── LICENSE
├── pyproject.toml
├── .github
└── workflows
│ └── python-package.yml
├── sectorlist.txt
└── RunSuite.txt
/Tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Tests/AreaItems/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Tests/Mapping/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Tests/SystemData/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/PyRoute/Calculation/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/PyRoute/DeltaDebug/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Tests/Pathfinding/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/PyRoute/StatCalculation/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/ReadSectorsDummy1.sec:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/deepspace.txt:
--------------------------------------------------------------------------------
1 | Reft,2528
2 | Reft,0618
3 |
--------------------------------------------------------------------------------
/Tests/TradeMPFiles/trademplist.txt:
--------------------------------------------------------------------------------
1 | Zdiedeiant
2 | Stiatlchepr
--------------------------------------------------------------------------------
/Tests/DeltaFiles/ReadSectorsDummy2.sec:
--------------------------------------------------------------------------------
1 | #
2 | #
3 |
4 | # Downtown Woop Woop
5 | # -999, -999
6 |
--------------------------------------------------------------------------------
/PyRoute/Fonts/FreeMono.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/PyRoute/Fonts/FreeMono.ttf
--------------------------------------------------------------------------------
/PyRoute/Fonts/Symbola-hint.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/PyRoute/Fonts/Symbola-hint.ttf
--------------------------------------------------------------------------------
/PyRoute/Pathfinding/minmaxheap.pyx:
--------------------------------------------------------------------------------
1 | # distutils: language = c++
2 |
3 | from minmaxheap cimport MinMaxHeap, dijkstra_t, astar_t
4 |
--------------------------------------------------------------------------------
/PyRoute/Fonts/DejaVuSerifCondensed.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/PyRoute/Fonts/DejaVuSerifCondensed.ttf
--------------------------------------------------------------------------------
/PyRoute/Fonts/LiberationMono-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/PyRoute/Fonts/LiberationMono-Bold.ttf
--------------------------------------------------------------------------------
/PyRoute/Fonts/ZapfDingbats-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/PyRoute/Fonts/ZapfDingbats-Regular.ttf
--------------------------------------------------------------------------------
/PyRoute/Fonts/DejaVuSerifCondensed-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/PyRoute/Fonts/DejaVuSerifCondensed-Bold.ttf
--------------------------------------------------------------------------------
/Tests/OutputFiles/verify_coreward_rimward/Ngathksirz Sector.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/Tests/OutputFiles/verify_coreward_rimward/Ngathksirz Sector.pdf
--------------------------------------------------------------------------------
/Tests/OutputFiles/verify_spinward_trailing/Knaeleng Sector.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/Tests/OutputFiles/verify_spinward_trailing/Knaeleng Sector.pdf
--------------------------------------------------------------------------------
/PyRoute/Errors/MultipleWPopError.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Jan 01, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 |
7 |
8 | class MultipleWPopError(ValueError):
9 |
10 | pass
11 |
--------------------------------------------------------------------------------
/Tests/OutputFiles/verify_quadripoint_trade_write/Deneb Sector.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/Tests/OutputFiles/verify_quadripoint_trade_write/Deneb Sector.pdf
--------------------------------------------------------------------------------
/Tests/OutputFiles/verify_quadripoint_trade_write/Corridor Sector.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/Tests/OutputFiles/verify_quadripoint_trade_write/Corridor Sector.pdf
--------------------------------------------------------------------------------
/Tests/OutputFiles/verify_quadripoint_trade_write/Provence Sector.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/Tests/OutputFiles/verify_quadripoint_trade_write/Provence Sector.pdf
--------------------------------------------------------------------------------
/Tests/OutputFiles/verify_quadripoint_trade_write/Tuglikki Sector.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/Tests/OutputFiles/verify_quadripoint_trade_write/Tuglikki Sector.pdf
--------------------------------------------------------------------------------
/Tests/OutputFiles/verify_single_system_border_write/Deneb Sector.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/Tests/OutputFiles/verify_single_system_border_write/Deneb Sector.pdf
--------------------------------------------------------------------------------
/Tests/OutputFiles/verify_subsector_xroute_write/Zarushagar Sector.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/Tests/OutputFiles/verify_subsector_xroute_write/Zarushagar Sector.pdf
--------------------------------------------------------------------------------
/Tests/OutputFiles/verify_coreward_rimward/Zao Kfeng Ig Grilokh Sector.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/Tests/OutputFiles/verify_coreward_rimward/Zao Kfeng Ig Grilokh Sector.pdf
--------------------------------------------------------------------------------
/Tests/OutputFiles/verify_empty_sector_write/Zao Kfeng Ig Grilokh empty.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/Tests/OutputFiles/verify_empty_sector_write/Zao Kfeng Ig Grilokh empty.pdf
--------------------------------------------------------------------------------
/Tests/OutputFiles/verify_spinward_trailing/Zao Kfeng Ig Grilokh Sector.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/Tests/OutputFiles/verify_spinward_trailing/Zao Kfeng Ig Grilokh Sector.pdf
--------------------------------------------------------------------------------
/PyRoute/Outputs/Colour.py:
--------------------------------------------------------------------------------
1 |
2 | from typing import Union
3 | from typing_extensions import TypeAlias
4 |
5 |
6 | Colour: TypeAlias = Union[str, tuple[float, float, float], tuple[float, float, float, float], None]
7 |
--------------------------------------------------------------------------------
/Tests/OutputFiles/verify_subsector_comm_write/Zao Kfeng Ig Grilokh - subsector P - comm.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/Tests/OutputFiles/verify_subsector_comm_write/Zao Kfeng Ig Grilokh - subsector P - comm.pdf
--------------------------------------------------------------------------------
/Tests/OutputFiles/verify_subsector_trade_write/Zao Kfeng Ig Grilokh - subsector P - trade.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/makhidkarun/traveller_pyroute/HEAD/Tests/OutputFiles/verify_subsector_trade_write/Zao Kfeng Ig Grilokh - subsector P - trade.pdf
--------------------------------------------------------------------------------
/PyRoute/Inputs/StarlineTransformer.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Dec 27, 2023
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.Inputs.BaseTransformer import BaseTransformer
7 |
8 |
9 | class StarlineTransformer(BaseTransformer):
10 | pass
11 |
--------------------------------------------------------------------------------
/PyRoute/Inputs/StarlineStationTransformer.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on May 20, 2024
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.Inputs.BaseTransformer import BaseTransformer
7 |
8 |
9 | class StarlineStationTransformer(BaseTransformer):
10 | pass
11 |
--------------------------------------------------------------------------------
/PyRoute/DeltaDebug/DeltaLogicError.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Mar 03, 2024
3 |
4 | @author: CyberiaResurrection
5 | """
6 |
7 |
8 | class DeltaLogicError(AssertionError):
9 |
10 | @staticmethod
11 | def delta_assert(condition, message):
12 | if not condition:
13 | raise DeltaLogicError(message)
14 |
--------------------------------------------------------------------------------
/Tests/TradeMPFiles/run_trademp_parameters:
--------------------------------------------------------------------------------
1 | --borders
2 | erode
3 | --min-btn
4 | 18
5 | --pop-code
6 | fixed
7 | --routes
8 | trade-mp
9 | --max-jump
10 | 2
11 | --route-reuse
12 | 10
13 | --speculative-version
14 | None
15 | --output
16 | Tests/maps
17 | --no-subsector-maps
18 | --input
19 | Tests/TradeMPFiles/
20 | --sectors
21 | Tests/TradeMPFiles/trademplist.txt
--------------------------------------------------------------------------------
/Tests/OutputFiles/verify_subsector_comm_write/comm stars.txt:
--------------------------------------------------------------------------------
1 | 16 17 {'distance': 1, 'weight': 95, 'trade': 500000000000, 'btn': 9, 'count': 1}
2 | 16 18 {'distance': 2, 'weight': 82, 'trade': 500000000000, 'btn': 13, 'count': 1}
3 | 17 18 {'distance': 1, 'weight': 87, 'trade': 500000000000, 'btn': 9, 'count': 1}
4 | 20 26 {'distance': 1, 'weight': 54, 'trade': 500000000000, 'btn': 19, 'count': 1}
5 |
--------------------------------------------------------------------------------
/PyRoute/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | __all__ = ["Galaxy", "Star", "SpeculativeTrade", "StatCalculation", "ObjectStatistics"]
3 |
4 | from PyRoute.AreaItems.Galaxy import Galaxy
5 | from PyRoute.SpeculativeTrade import SpeculativeTrade
6 | from PyRoute.Star import Star
7 | from PyRoute.StatCalculation.ObjectStatistics import ObjectStatistics
8 | from PyRoute.StatCalculation.StatCalculation import StatCalculation
9 |
--------------------------------------------------------------------------------
/PyRoute/Pathfinding/setup.py:
--------------------------------------------------------------------------------
1 | import numpy
2 | from setuptools import setup
3 | from Cython.Build import cythonize
4 |
5 |
6 | setup(
7 | ext_modules=cythonize(
8 | ['astar_numpy.py', 'single_source_dijkstra_core.py', 'ApproximateShortestPathForestUnified.py',
9 | 'minmaxheap.pyx'],
10 | annotate=False
11 | ),
12 | include_dirs=[numpy.get_include()]
13 | )
14 |
--------------------------------------------------------------------------------
/PyRoute/Outputs/LightModePDFSectorMap.py:
--------------------------------------------------------------------------------
1 |
2 | from PyRoute.AreaItems.Galaxy import Galaxy
3 |
4 | from PyRoute.Outputs.PDFMap import PDFMap
5 | from PyRoute.Outputs.SectorMap import SectorMap
6 |
7 |
8 | class LightModePDFSectorMap(PDFMap, SectorMap):
9 | def __init__(self, galaxy: Galaxy, routes: str, output_path: str, writer: str):
10 | super(LightModePDFSectorMap, self).__init__(galaxy, routes, output_path, writer)
11 |
--------------------------------------------------------------------------------
/sectorttwlist.txt:
--------------------------------------------------------------------------------
1 | Ktiinz'gat
2 | Mugheen't
3 | Grikr!ng
4 | Ukoarriit!!b
5 | Kring Noor
6 | Mbil!!gh
7 | Ingukrax
8 | Gn'hk'r
9 | Gur
10 | Un'k!!k'ng
11 | Xaagr
12 | Eekrookrigz
13 | Star's End
14 | Gh!hken
15 | Ruupiin
16 | Raakaan
17 | Uuk Sector
18 | Gnaa Iimb'kr
19 | Gateway
20 | Luretiir!girr
21 | X'kug
22 | Kilong
23 | Bar'kakr
24 | Mighabohk
25 | Crucis Margin
26 | Numbis
27 | Gzirr!k'l
28 | K'trekreer
29 | Nuughe
30 | N!!krumbiix
31 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | networkx
2 | inflect
3 | Cython
4 | Pillow >= 10.0
5 | wikitools3==3.0.1
6 | coverage[toml]
7 | hypothesis
8 | jinja2
9 | jsonpickle
10 | lark==1.1.8
11 | mutmut == 3.3.1
12 | mypy
13 | numpy == 2.0.0
14 | pymupdf
15 | pyfakefs
16 | pytest >= 8.0.0
17 | pytest-console-scripts
18 | pytest-randomly
19 | pytest-subtests
20 | pytest-timeout
21 | reportlab >= 4.1.0
22 | ruff >= 0.3.0
23 | setuptools
24 | types-networkx
25 | types-reportlab
26 | typing_extensions
27 |
--------------------------------------------------------------------------------
/sectorimplist.txt:
--------------------------------------------------------------------------------
1 | Alpha Crucis
2 | Antares
3 | Core
4 | Corridor
5 | Dagudashaag
6 | Daibei
7 | Dark Nebula
8 | Delphi
9 | Deneb
10 | Diaspora
11 | Empty Quarter
12 | Fornast
13 | Gateway
14 | Glimmerdrift Reaches
15 | Gushemege
16 | Hinterworlds
17 | Ilelish
18 | Ley Sector
19 | Lishun
20 | Magyar
21 | Massilia
22 | Old Expanses
23 | Reaver's Deep
24 | Reft Sector
25 | Riftspan Reaches
26 | Solomani Rim
27 | Spica
28 | Spinward Marches
29 | Trojan Reach
30 | Verge
31 | Vland
32 | Zarushagar
33 |
--------------------------------------------------------------------------------
/Tests/StatCalculation/testUWPCollection.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Dec 03 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.StatCalculation.UWPCollection import UWPCollection
7 | from Tests.baseTest import baseTest
8 |
9 |
10 | class testUWPCollection(baseTest):
11 |
12 | def test_setitem_1(self) -> None:
13 | collection = UWPCollection()
14 |
15 | collection['Starport'] = {'A': 1}
16 | actual = collection['Starport']
17 | self.assertEqual({'A': 1}, actual)
18 |
--------------------------------------------------------------------------------
/Tests/StatCalculation/testPopulations.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Dec 03, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.StatCalculation.Populations import Populations
7 | from Tests.baseTest import baseTest
8 |
9 |
10 | class testPopulations(baseTest):
11 |
12 | def test_init(self) -> None:
13 | pop = Populations()
14 | self.assertEqual("", pop.code)
15 | self.assertEqual([], pop.homeworlds)
16 | self.assertEqual(0, pop.count)
17 | self.assertEqual(0, pop.population)
18 |
--------------------------------------------------------------------------------
/PyRoute/Outputs/LightModeGraphicSectorMap.py:
--------------------------------------------------------------------------------
1 |
2 | from PyRoute.AreaItems.Galaxy import Galaxy
3 |
4 | from PyRoute.Outputs.GraphicMap import GraphicMap
5 | from PyRoute.Outputs.SectorMap import SectorMap
6 |
7 |
8 | class LightModeGraphicSectorMap(GraphicMap, SectorMap):
9 | def __init__(self, galaxy: Galaxy, routes: str, output_path: str, writer: str):
10 | self.input_scale = 4
11 | self.output_scale = 1
12 |
13 | super(LightModeGraphicSectorMap, self).__init__(galaxy, routes, output_path, writer)
14 |
--------------------------------------------------------------------------------
/Tests/Inputs/testReadSector.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on May 18, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaDebug.DeltaDictionary import SectorDictionary
7 | from Tests.baseTest import baseTest
8 |
9 |
10 | class testReadSector(baseTest):
11 |
12 | def testReadKilongSector(self) -> None:
13 | sourcefile = self.unpack_filename('DeltaFiles/read_kilong_sector/Kilong.sec')
14 |
15 | sector = SectorDictionary.load_traveller_map_file(sourcefile)
16 | self.assertEqual(518, len(sector.lines), "Unexpected # of lines")
17 |
--------------------------------------------------------------------------------
/PyRoute/DataClasses/ReadSectorOptions.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Apr 04, 2023
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from dataclasses import dataclass
7 |
8 |
9 | @dataclass(frozen=True)
10 | class ReadSectorOptions:
11 | sectors: list
12 | pop_code: str = 'scaled'
13 | ru_calc: str = 'scaled'
14 | route_reuse: int = 10
15 | trade_choice: str = 'trade'
16 | route_btn: int = 13
17 | mp_threads: int = 1
18 | debug_flag: bool = False
19 | fix_pop: bool = False
20 | deep_space: dict = None
21 | map_type: str = 'classic'
22 |
--------------------------------------------------------------------------------
/Tests/Hypothesis/Inputs/testStarlineTransformer.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Jan 02, 2024
3 |
4 | @author: CyberiaResurrection
5 | """
6 |
7 | import unittest
8 |
9 | from PyRoute.Inputs.StarlineTransformer import StarlineTransformer
10 |
11 |
12 | class testStarlineTransformer(unittest.TestCase):
13 | def test_boil_down_double_spaces(self) -> None:
14 | original = '{ 2}'
15 | expected = '{ 2}'
16 | actual = StarlineTransformer.boil_down_double_spaces(original)
17 | self.assertEqual(expected, actual)
18 |
19 |
20 | if __name__ == '__main__':
21 | unittest.main()
22 |
--------------------------------------------------------------------------------
/PyRoute/templates/subsectors.wiki:
--------------------------------------------------------------------------------
1 | __NOTOC__
2 | {% import 'statistics_table.wiki' as stats %}
3 | {% for sector in sectors.values() -%}
4 | == {{sector.name}} statistics ==
5 | {{ stats.text_area_long('sector', sector, plural) }}
6 |
7 | === Polities ===
8 | {{ stats.allegiance_statistics(plural, sector) }}
9 |
10 | {%- for subsector in sector.subsectors.values() | sort (attribute="position") %}
11 | === {{ subsector.subsector_name() }} statistics ===
12 | {{ stats.text_area_long('subsector', subsector, plural) }}
13 |
14 | {{ stats.allegiance_statistics(plural, subsector) }}
15 | {%- endfor -%}
16 | {%- endfor -%}
17 |
18 |
--------------------------------------------------------------------------------
/sectorzholist.txt:
--------------------------------------------------------------------------------
1 | Stinj Tianz
2 | Bliardlie
3 | Zhiensh
4 | Savria
5 | Datsatl
6 | Gakghang
7 | Thaku Fung
8 | Viajlefliez
9 | Bleblqansh
10 | Driasera
11 | Dalchie Jdatl
12 | Chit Boshti
13 | Ghoekhnael
14 | Ksinanirz
15 | Brieplanz
16 | Sidiadl
17 | Zdiedeiant
18 | Stiatlchepr
19 | Itvikiastaf
20 | Knoellighz
21 | Dhuerorrg
22 | Pliabriebl
23 | Eiaplial
24 | Zhdant
25 | Tienspevnekr
26 | Ziafrplians
27 | Gvurrdon
28 | Tuglikki
29 | Tsadra Davr
30 | Tsadra
31 | Yiklerzdanzh
32 | Far Frontiers
33 | Spinward Marches
34 | Deneb
35 | Chiep Zhez
36 | Astron
37 | Fulani
38 | Vanguard Reaches
39 | The Beyond
40 | Trojan Reach
41 | Reft Sector
42 |
--------------------------------------------------------------------------------
/Tests/Utilities/testNoNoneDefaultDict.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from PyRoute.Utilities.NoNoneDefaultDict import NoNoneDefaultDict
4 |
5 |
6 | class testNoNoneDefaultDict(unittest.TestCase):
7 | def test_reject_none_key(self) -> None:
8 | foo = NoNoneDefaultDict(int)
9 | exc = None
10 |
11 | try:
12 | foo[None] += 1
13 | except ValueError as e:
14 | exc = e
15 |
16 | self.assertTrue(isinstance(exc, ValueError), "ValueError not raised")
17 | self.assertEqual("Supplied key must not be NoneType", exc.args[0])
18 |
19 |
20 | if __name__ == '__main__':
21 | unittest.main()
22 |
--------------------------------------------------------------------------------
/Tests/testDeltaDebug.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 |
4 | from Tests.baseTest import baseTest
5 | from pytest_console_scripts import ScriptRunner
6 |
7 |
8 | class testDeltaDebug(baseTest):
9 |
10 | def test_delta_debug_command_empty_args(self) -> None:
11 | fullpath = self.unpack_filename('../PyRoute/DeltaDebug/DeltaDebug.py')
12 |
13 | cwd = os.getcwd()
14 | runner = ScriptRunner(launch_mode="inprocess", rootdir=cwd)
15 |
16 | foo = runner.run(command=fullpath)
17 |
18 | self.assertEqual(0, foo.returncode, "DeltaDebug did not complete successfully: " + foo.stderr)
19 |
20 |
21 | if __name__ == '__main__':
22 | unittest.main()
23 |
--------------------------------------------------------------------------------
/Tests/Dummy/DeltaDebug/DummyReduce.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Oct 09, 2023
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaDebug.DeltaReduce import DeltaReduce
7 |
8 |
9 | class DummyReduce(DeltaReduce):
10 |
11 | def __init__(self, sectors, args, interesting_line=None, interesting_type=None):
12 | super().__init__(sectors, args, interesting_line=interesting_line, interesting_type=interesting_type)
13 | self.jam_interesting = None
14 |
15 | def _check_interesting(self, args, sectors):
16 | if self.jam_interesting is not None:
17 | return self.jam_interesting, "", None
18 |
19 | return DeltaReduce._check_interesting(args, sectors)
20 |
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | python_files = test*
3 | python_classes = test*
4 | pythonpath =
5 | ./
6 |
7 | testpaths =
8 | Tests/**.py
9 | Tests/Allies/**.py
10 | Tests/AreaItems/**.py
11 | Tests/Calculation/**.py
12 | Tests/DeltaDictionary/**.py
13 | Tests/Hypothesis/**.py
14 | Tests/Hypothesis/Inputs/**.py
15 | Tests/Hypothesis/Position/**.py
16 | Tests/Inputs/**.py
17 | Tests/Mapping/**.py
18 | Tests/Outputs/**.py
19 | Tests/Pathfinding/**.py
20 | Tests/Pathfinding/Schemas/**.py
21 | Tests/Position/**.py
22 | Tests/StatCalculation/**.py
23 | Tests/SystemData/**.py
24 | Tests/Utilities/**.py
25 | Tests/Pyfakefs/AreaItems/**.py
26 |
--------------------------------------------------------------------------------
/PyRoute/Outputs/GraphicLine.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Sep 12, 2023
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PIL import ImageColor # type: ignore
7 |
8 | from PyRoute.Outputs.Cursor import Cursor
9 |
10 |
11 | class GraphicLine(object):
12 | def __init__(self, image, lineStart: Cursor, lineEnd: Cursor, colorname: str, width: int):
13 | self.image = image
14 | self.lineStart: Cursor = lineStart
15 | self.lineEnd: Cursor = lineEnd
16 | self.color = ImageColor.getrgb(colorname)
17 | self.width = max(1, int(width))
18 |
19 | def _draw(self):
20 | self.image.line([self.lineStart.as_tuple(), self.lineEnd.as_tuple()], self.color,
21 | self.width)
22 |
--------------------------------------------------------------------------------
/PyRoute/Pathfinding/minmaxheap.pxd:
--------------------------------------------------------------------------------
1 | # distutils: language = c++
2 | # Adapted from https://github.com/kilian-gebhardt/MinMaxHeap
3 |
4 | from libcpp.vector cimport vector
5 |
6 | cdef extern from "_minmaxheap.h" namespace "minmaxheap":
7 | cdef struct astar_t:
8 | double augment;
9 | double dist;
10 | int curnode;
11 | int parent;
12 |
13 | cdef struct dijkstra_t:
14 | double act_wt;
15 | int act_nod;
16 |
17 | cdef cppclass MinMaxHeap[T]:
18 | MinMaxHeap()
19 | # MinMaxHeap(size_t reserve)
20 | # short level(size_t n)
21 | size_t size()
22 | void insert(T key)
23 | void clear()
24 | T peekmin()
25 | T peekmax()
26 | T popmin()
27 | T popmax()
28 | void reserve(size_t n)
29 | vector[T] getheap()
30 |
--------------------------------------------------------------------------------
/PyRoute/Outputs/DarkModePDFSectorMap.py:
--------------------------------------------------------------------------------
1 |
2 | from PyRoute.AreaItems.Galaxy import Galaxy
3 |
4 | from PyRoute.Outputs.PDFMap import PDFMap
5 | from PyRoute.Outputs.SectorMap import SectorMap
6 |
7 |
8 | class DarkModePDFSectorMap(PDFMap, SectorMap):
9 | def __init__(self, galaxy: Galaxy, routes: str, output_path: str, writer: str):
10 | super(DarkModePDFSectorMap, self).__init__(galaxy, routes, output_path, writer)
11 | self.colours['background'] = 'black'
12 | self.colours['sector'] = 'white'
13 | self.colours['system_port'] = (242, 242, 242)
14 | self.colours['system_uwp'] = (242, 242, 242)
15 | self.colours['system_name'] = (242, 242, 242)
16 | self.colours['base code'] = (242, 242, 242)
17 |
--------------------------------------------------------------------------------
/PyRoute/StatCalculation/UWPCollection.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Dec 03, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from collections import OrderedDict
7 |
8 | from PyRoute.UWPCodes import UWPCodes
9 | from PyRoute.StatCalculation.ObjectStatistics import ObjectStatistics
10 |
11 |
12 | class UWPCollection(object):
13 | def __init__(self):
14 | self.uwp = OrderedDict()
15 | for uwpCode in UWPCodes.uwpCodes:
16 | self.uwp[uwpCode] = {}
17 |
18 | def stats(self, code, value) -> ObjectStatistics:
19 | return self.uwp[code].setdefault(value, ObjectStatistics())
20 |
21 | def __getitem__(self, index):
22 | return self.uwp[index]
23 |
24 | def __setitem__(self, index, value):
25 | self.uwp[index] = value
26 |
--------------------------------------------------------------------------------
/PyRoute/UWPCodes.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Dec 15, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from collections import OrderedDict
7 |
8 |
9 | class UWPCodes(object):
10 | uwpCodes = ['Starport',
11 | 'Size',
12 | 'Atmosphere',
13 | 'Hydrographics',
14 | 'Population',
15 | 'Government',
16 | 'Law Level',
17 | 'Tech Level',
18 | 'Pop Code',
19 | 'Starport Size',
20 | 'Primary Type',
21 | 'Importance',
22 | 'Resources']
23 |
24 | def __init__(self):
25 | self.codes = OrderedDict()
26 | for uwpCode in UWPCodes.uwpCodes:
27 | self.codes[uwpCode] = "X"
28 |
--------------------------------------------------------------------------------
/PyRoute/templates/allegiances.wiki:
--------------------------------------------------------------------------------
1 | {% import 'statistics_table.wiki' as stats %}
2 |
3 | == Allegiances ==
4 | {| class="wikitable sortable" width="90%"
5 | |+ Analysis per Polity
6 | ! Name !! Code !! Worlds !! Population (millions) !! % of Population !! Economy (Bcr) !! Per Capita (Cr) !! RU !! Trade Volume (BCr / year) !! Int. Trade (BCr / year) !! Int. Trade (MDton / year) !! Ext. Trade (BCr/year) !! Ext. Trade (MDton / year) !! Shipyard Capacity (MTons) !! Colonial Army (BEs) !! Travellers (M / year) !! SPA Pop
7 | {% for alleg in global_alg if alleg.world_count() >= min_alg_count -%}
8 | |-
9 | | {{alleg.wiki_name()}}
10 | | {{alleg.code}}
11 | {{ stats.stats_table(alleg.stats, global_stats) }}
12 | {%- endfor %}
13 | |}
14 |
15 | {{ stats.allegiance_statistics(plural, area) }}
16 |
--------------------------------------------------------------------------------
/Tests/SystemData/testUtilities.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Dec 15, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | import unittest
7 |
8 | from PyRoute.SystemData.Utilities import Utilities
9 |
10 |
11 | class testUtilities(unittest.TestCase):
12 |
13 | def test_ehex_to_int_bad_value(self) -> None:
14 | msg = None
15 | try:
16 | Utilities.ehex_to_int(None)
17 | except ValueError as e:
18 | msg = str(e)
19 | self.assertEqual("Value must be string", msg)
20 |
21 | def test_int_to_ehex_bad_value(self) -> None:
22 | msg = None
23 | try:
24 | Utilities.int_to_ehex(None)
25 | except ValueError as e:
26 | msg = str(e)
27 | self.assertEqual("Value must be integer", msg)
28 |
--------------------------------------------------------------------------------
/PyRoute/Pathfinding/LandmarkSchemes/LandmarksWTNExtremes.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Feb 19, 2024
3 |
4 | @author: CyberiaResurrection
5 | """
6 |
7 |
8 | class LandmarksWTNExtremes:
9 |
10 | def __init__(self, galaxy):
11 | self.galaxy = galaxy
12 |
13 | def get_landmarks(self, index=False) -> list[dict]:
14 | result = []
15 |
16 | result.append(dict())
17 | for component_id in self.galaxy.trade.components:
18 | stars = [item for item in self.galaxy.star_mapping.values() if component_id == item.component]
19 | source = max(stars, key=lambda item: item.wtn)
20 | if index:
21 | result[0][component_id] = source.index
22 | else:
23 | result[0][component_id] = source
24 |
25 | return result
26 |
--------------------------------------------------------------------------------
/Tests/AreaItems/testAreaItem.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Nov 27, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 |
7 | import unittest
8 |
9 | from PyRoute.AreaItems.AreaItem import AreaItem
10 |
11 |
12 | class testAreaItem(unittest.TestCase):
13 | def test_init(self) -> None:
14 | foo = AreaItem('foobar')
15 | self.assertEqual('foobar', foo.name)
16 | self.assertEqual([], foo.worlds)
17 | self.assertEqual({}, foo.alg)
18 | self.assertEqual([], foo.alg_sorted)
19 | self.assertFalse(foo.debug_flag)
20 | self.assertEqual('foobar', str(foo))
21 |
22 | def test_is_well_formed(self) -> None:
23 | foo = AreaItem('foobar')
24 | well_formed, msg = foo.is_well_formed()
25 | self.assertTrue(well_formed)
26 | self.assertEqual("", msg)
27 |
28 |
29 | if __name__ == '__main__':
30 | unittest.main()
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/__pycache__/
2 | PyRoute/*.pyc
3 | PyRoute/DeltaDebug/*.pyc
4 | PyRoute/Pathfinding/*.pyc
5 | PyRoute/Calculation/*.pyc
6 | PyRoute/Position/*.pyc
7 | PyRoute/DeltaPasses/*.pyc
8 | Tests/*.pyc
9 | PyRoute/*.class
10 | PyRoute/DeltaDebug/*.class
11 | PyRoute/Pathfinding/*.class
12 | PyRoute/Position/*.class
13 | Tests/*.class
14 | Tests/.pytest_cache/
15 |
16 | # ignore venvs
17 | venv/
18 | venv2/
19 |
20 | # ignore IDE-related detritus
21 | .idea/
22 | .project
23 | .pydevproject
24 | .settings/
25 | .hypothesis/
26 |
27 | # ignore downloaded/generated files
28 | sectors/
29 | maps*/
30 | diffs/
31 | test_data/
32 | mutants/
33 | PyRoute/Pathfinding/*.c
34 | PyRoute/Pathfinding/*.cpp
35 | PyRoute/Pathfinding/*.cpython*
36 | PyRoute/Pathfinding/*.html
37 | b*/
38 | c*/
39 | d*/
40 | D*/
41 | f*/
42 | k*/
43 | n*/
44 | p*/
45 | s*/
46 | t*/
47 | .coverage
48 | *.log
49 | *.wiki
50 | *.png
51 | *.lprof
52 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/BeyondLineReducer.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Apr 05, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaDebug.DeltaDictionary import DeltaDictionary
7 | from PyRoute.DeltaPasses.WidenHoleReducer import WidenHoleReducer
8 |
9 |
10 | class BeyondLineReducer(object):
11 |
12 | def __init__(self, reducer):
13 | from PyRoute.DeltaDebug.DeltaReduce import DeltaReduce
14 | self.reducer: DeltaReduce = reducer
15 | self.breacher = WidenHoleReducer(reducer)
16 |
17 | def preflight(self) -> bool:
18 | return self.reducer is not None and self.reducer.sectors is not None and 0 < len(self.reducer.sectors.lines)
19 |
20 | def write_files(self, sectors=None) -> None:
21 | if isinstance(sectors, DeltaDictionary):
22 | sectors.write_files(self.reducer.args.mindir)
23 | else:
24 | self.reducer.sectors.write_files(self.reducer.args.mindir)
25 |
--------------------------------------------------------------------------------
/PyRoute/templates/sector_data.wiki:
--------------------------------------------------------------------------------
1 |
2 | {| class="wikitable sortable" width="90%"
3 | |+ {{sector.sector_name()}} sector data
4 | !Hex!!Name!!UWP!!Remarks!!{Ix}!!(Ex)!![Cx]!!Noble!!Base!!Zone!!PBG!!Worlds!!Alg!!Stellar
5 | {%- for subsector in sector.subsectors.values() -%}
6 |
7 | {%- for world in subsector.worlds %}
8 | |-
9 | | {{ world.position }}
10 | | [[{{ world.wiki_short_name() }}]]
11 | | {{ world.uwp }}
12 | | {{ world.tradeCode }}
13 | | { {{world.importance }} }
14 | | {{ world. economics }}
15 | | {{ world.social }}
16 | | {{ world.nobles }}
17 | | {{ world.baseCode }}
18 | | {{ world.zone }}
19 | | {{ world.popM}}{{world.belts}}{{world.ggCount}}
20 | | {{ world.worlds }}
21 | | {{ world.alg }}
22 | | {{ world.stars }}
23 | {% endfor -%}
24 |
25 | {%- endfor %}
26 | |}
27 | [[Category: Sectors with sector data]]
28 |
--------------------------------------------------------------------------------
/PyRoute/Utilities/UnpackFilename.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Jan 19, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 |
7 | import os
8 |
9 |
10 | class UnpackFilename(object):
11 |
12 | @staticmethod
13 | def unpack_filename(filename: str) -> str:
14 | # try unpacked filename directly
15 | sourcefile = os.path.abspath(filename)
16 | if not os.path.isfile(sourcefile):
17 | sourcefile = os.path.abspath('../' + filename)
18 | if not os.path.isfile(sourcefile):
19 | sourcefile = os.path.abspath('Tests/' + filename)
20 | if not os.path.isfile(sourcefile):
21 | sourcefile = os.path.abspath('../Tests/' + filename)
22 | if not os.path.isfile(sourcefile):
23 | sourcefile = os.path.abspath('Tests/Tests/' + filename)
24 | if not os.path.isfile(sourcefile):
25 | raise BaseException(sourcefile + " not mapped to a file")
26 |
27 | return sourcefile
28 |
--------------------------------------------------------------------------------
/PyRoute/AreaItems/AreaItem.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on 21 Jul, 2024
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.StatCalculation.ObjectStatistics import ObjectStatistics
7 |
8 |
9 | class AreaItem(object):
10 | def __init__(self, name):
11 | self.name = name
12 | self.worlds = []
13 | self.stats = ObjectStatistics()
14 | self.alg = {}
15 | self.alg_sorted = []
16 | self._wiki_name = '[[{}]]'.format(name)
17 | self.debug_flag = False
18 |
19 | def wiki_title(self) -> str:
20 | return self.wiki_name()
21 |
22 | def wiki_name(self) -> str:
23 | return self._wiki_name
24 |
25 | def __str__(self):
26 | return self.name
27 |
28 | def world_count(self) -> int:
29 | return len(self.worlds)
30 |
31 | def __getitem__(self, item):
32 | return getattr(self, item)
33 |
34 | def __setitem__(self, key, value):
35 | setattr(self, key, value)
36 |
37 | def is_well_formed(self) -> tuple[bool, str]:
38 | return True, ""
39 |
--------------------------------------------------------------------------------
/PyRoute/Utilities/NoNoneDefaultDict.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Nov 30, 2023
3 |
4 | @author: CyberiaResurrection
5 |
6 | An extension of defaultdict that, as the name says, loudly refuses to accept NoneType keys.
7 | """
8 | from collections import defaultdict
9 |
10 |
11 | class NoNoneDefaultDict(defaultdict):
12 |
13 | def __init__(self, default_factory=None):
14 | if default_factory is None:
15 | raise ValueError("Supplied factory must not be NoneType")
16 | super(NoNoneDefaultDict, self).__init__(default_factory)
17 |
18 | def __eq__(self, other) -> bool:
19 | if not isinstance(other, NoNoneDefaultDict):
20 | return False
21 | if self.default_factory != other.default_factory:
22 | return False
23 | return super().__eq__(other)
24 |
25 | def __hash__(self) -> int:
26 | return 0
27 |
28 | def __missing__(self, key):
29 | if key is None:
30 | raise ValueError("Supplied key must not be NoneType")
31 | return super().__missing__(key)
32 |
--------------------------------------------------------------------------------
/PyRoute/Pathfinding/LandmarkSchemes/LandmarksQExtremes.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Feb 19, 2024
3 |
4 | @author: CyberiaResurrection
5 | """
6 |
7 |
8 | class LandmarksQExtremes:
9 |
10 | def __init__(self, galaxy):
11 | self.galaxy = galaxy
12 |
13 | def get_landmarks(self, index=False) -> list[dict]:
14 | result = []
15 |
16 | result.append(dict())
17 | result.append(dict())
18 | for component_id in self.galaxy.trade.components:
19 | stars = [item for item in self.galaxy.star_mapping.values() if component_id == item.component]
20 | source = max(stars, key=lambda item: item.hex.q)
21 | if index:
22 | result[0][component_id] = source.index
23 | else:
24 | result[0][component_id] = source
25 |
26 | source = min(stars, key=lambda item: item.hex.q)
27 | if index:
28 | result[1][component_id] = source.index
29 | else:
30 | result[1][component_id] = source
31 |
32 | return result
33 |
--------------------------------------------------------------------------------
/PyRoute/Pathfinding/LandmarkSchemes/LandmarksRExtremes.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Feb 19, 2024
3 |
4 | @author: CyberiaResurrection
5 | """
6 |
7 |
8 | class LandmarksRExtremes:
9 |
10 | def __init__(self, galaxy):
11 | self.galaxy = galaxy
12 |
13 | def get_landmarks(self, index=False) -> list[dict]:
14 | result = []
15 |
16 | result.append(dict())
17 | result.append(dict())
18 | for component_id in self.galaxy.trade.components:
19 | stars = [item for item in self.galaxy.star_mapping.values() if component_id == item.component]
20 | source = max(stars, key=lambda item: item.hex.r)
21 | if index:
22 | result[0][component_id] = source.index
23 | else:
24 | result[0][component_id] = source
25 |
26 | source = min(stars, key=lambda item: item.hex.r)
27 | if index:
28 | result[1][component_id] = source.index
29 | else:
30 | result[1][component_id] = source
31 |
32 | return result
33 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/FullLineReduce.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Jun 13, 2023
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaPasses.WithinLineReducer import WithinLineReducer
7 | from PyRoute.DeltaStar import DeltaStar
8 |
9 |
10 | class FullLineReduce(WithinLineReducer):
11 |
12 | def _build_subs_list(self):
13 | self.full_msg = "Reduction found with full line reduction"
14 | self.start_msg = "Commencing full within-line reduction"
15 | # build substitution list - reduce _everything_
16 | subs_list = []
17 | segment = []
18 | for line in self.reducer.sectors.lines:
19 | canon = DeltaStar.reduce_all(line)
20 | assert isinstance(canon,
21 | str), "Candidate line " + line + " was not reduced to a string. Got " + canon + " instead."
22 | # Skip already-reduced lines
23 | if line.startswith(canon):
24 | continue
25 | subs_list.append((line, canon))
26 | segment.append(line)
27 | return segment, subs_list
28 |
--------------------------------------------------------------------------------
/PyRoute/Pathfinding/LandmarkSchemes/LandmarksSExtremes.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Feb 19, 2024
3 |
4 | @author: CyberiaResurrection
5 | """
6 |
7 |
8 | class LandmarksSExtremes:
9 |
10 | def __init__(self, galaxy):
11 | self.galaxy = galaxy
12 |
13 | def get_landmarks(self, index=False) -> list[dict]:
14 | result = []
15 |
16 | result.append(dict())
17 | result.append(dict())
18 | for component_id in self.galaxy.trade.components:
19 | stars = [item for item in self.galaxy.star_mapping.values() if component_id == item.component]
20 | source = max(stars, key=lambda item: -item.hex.q - item.hex.r)
21 | if index:
22 | result[0][component_id] = source.index
23 | else:
24 | result[0][component_id] = source
25 |
26 | source = min(stars, key=lambda item: -item.hex.q - item.hex.r)
27 | if index:
28 | result[1][component_id] = source.index
29 | else:
30 | result[1][component_id] = source
31 |
32 | return result
33 |
--------------------------------------------------------------------------------
/PyRoute/Calculation/OwnedWorldCalculation.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Aug 09, 2023
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.Allies.AllyGen import AllyGen
7 | from PyRoute.Calculation.RouteCalculation import RouteCalculation
8 |
9 |
10 | class OwnedWorldCalculation(RouteCalculation):
11 | def __init__(self, galaxy):
12 | super(OwnedWorldCalculation, self).__init__(galaxy)
13 |
14 | # Pure HG Base jump distance cost
15 | distance_weight = [0, 30, 50, 75, 130, 230, 490]
16 |
17 | def generate_routes(self) -> None:
18 | self.generate_base_routes()
19 | pass
20 |
21 | def calculate_routes(self) -> None:
22 | pass
23 |
24 | def base_route_filter(self, star, neighbor) -> bool:
25 | return not AllyGen.are_owned_allies(star.alg_code, neighbor.alg_code)
26 |
27 | def base_range_routes(self, star, neighbor) -> int:
28 | return star.distance(neighbor)
29 |
30 | def route_weight(self, star, target) -> float:
31 | dist = star.distance(target)
32 | weight = self.distance_weight[dist]
33 | return weight
34 |
--------------------------------------------------------------------------------
/Tests/testBase.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import tempfile
3 | import unittest
4 |
5 |
6 | class testBase(unittest.TestCase):
7 | def _make_args(self) -> argparse.ArgumentParser:
8 | args = argparse.ArgumentParser(description='PyRoute input minimiser.')
9 | args.btn = 8
10 | args.max_jump = 2
11 | args.route_btn = 13
12 | args.pop_code = 'scaled'
13 | args.ru_calc = 'scaled'
14 | args.routes = 'trade'
15 | args.route_reuse = 10
16 | args.interestingline = "Weight of edge"
17 | args.interestingtype = None
18 | args.maps = None
19 | args.borders = 'range'
20 | args.ally_match = 'collapse'
21 | args.owned = False
22 | args.trade = True
23 | args.speculative_version = 'CT'
24 | args.ally_count = 10
25 | args.json_data = False
26 | args.output = tempfile.gettempdir()
27 | return args
28 |
29 | def _make_args_no_line(self) -> argparse.ArgumentParser:
30 | args = self._make_args()
31 | args.interestingline = None
32 |
33 | return args
34 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/IdentityLineReduce.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on May 04, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaPasses.WithinLineReducer import WithinLineReducer
7 | from PyRoute.DeltaStar import DeltaStar
8 |
9 |
10 | class IdentityLineReduce(WithinLineReducer):
11 |
12 | def _build_subs_list(self):
13 | self.full_msg = "Reduction found with identity reduction"
14 | self.start_msg = "Commencing identity within-line reduction"
15 |
16 | subs_list = []
17 | segment = []
18 | num_lines = len(self.reducer.sectors.lines)
19 | for line in self.reducer.sectors.lines:
20 | canon = DeltaStar.reduce(line)
21 | assert isinstance(canon,
22 | str), "Candidate line " + line + " was not reduced to a string. Got " + canon + " instead."
23 | # Skip already-reduced lines
24 | if 2 < num_lines and line.startswith(canon):
25 | continue
26 | subs_list.append((line, canon))
27 | segment.append(line)
28 | return segment, subs_list
29 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/ImportanceLineReduce.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Jul 14, 2023
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaPasses.WithinLineReducer import WithinLineReducer
7 | from PyRoute.DeltaStar import DeltaStar
8 |
9 |
10 | class ImportanceLineReduce(WithinLineReducer):
11 |
12 | def _build_subs_list(self):
13 | self.full_msg = "Reduction found with importance line reduction"
14 | self.start_msg = "Commencing importance-related within-line reduction"
15 | # build substitution list - reduce _everything_
16 | subs_list = []
17 | segment = []
18 | for line in self.reducer.sectors.lines:
19 | canon = DeltaStar.reduce_importance(line)
20 | assert isinstance(canon,
21 | str), "Candidate line " + line + " was not reduced to a string. Got " + canon + " instead."
22 | # Skip already-reduced lines
23 | if line.startswith(canon):
24 | continue
25 | subs_list.append((line, canon))
26 | segment.append(line)
27 | return segment, subs_list
28 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/border_blowup_3/Raakaan.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2024-09-28T17:24:06-07:00
3 |
4 | # Raakaan
5 | # 6,1
6 |
7 | # Name: Raakaan (kk)
8 |
9 | # Abbreviation: Raak
10 |
11 | # Milieu: M1105
12 |
13 | # Credits: Raakaan sector was designed by Jim Kundert.
14 |
15 | # Author: Jim Kundert
16 |
17 | # Subsector A: Miataatrat
18 | # Subsector B: Rarroo'ghat
19 | # Subsector C: Teeghaa
20 | # Subsector D: Gn'koorr
21 | # Subsector E: Aar'krex
22 | # Subsector F: U'keeghan
23 | # Subsector G: Xtulak'
24 | # Subsector H: Graakrakook'
25 | # Subsector I: Kaxuut
26 | # Subsector J: Urr'ka
27 | # Subsector K: Rrrut!
28 | # Subsector L: Atarate
29 | # Subsector M: Karaxt!m
30 | # Subsector N: Tari
31 | # Subsector O: Kariake
32 | # Subsector P: Nak'grar
33 |
34 | # Alleg: Kk: "The Two Thousand Worlds"
35 |
36 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar
37 | ---- -------------------- --------- -------------------- ---- ---- ---- - - - --- - -- -------
38 | 1109 ?537???-? - - - ?24 Kk
39 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/no_subsectors_named/Knaeleng empty.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2023-12-23T00:50:36-08:00
3 |
4 | # Knaeleng
5 | # -1,4
6 |
7 | # Name: Knaeleng (va)
8 |
9 | # Abbreviation: Knae
10 |
11 | # Milieu: M1105
12 |
13 | # Author: John G. Wood
14 | # Source: Underdeveloped Sectors
15 | # Ref: https://web.archive.org/web/20160205040546/http://homepage.ntlworld.com/elvwood/Traveller/Sectors/
16 |
17 | # Subsector A:
18 | # Subsector B:
19 | # Subsector C:
20 | # Subsector D:
21 | # Subsector E:
22 | # Subsector F:
23 | # Subsector G:
24 | # Subsector H:
25 | # Subsector I:
26 | # Subsector J:
27 | # Subsector K:
28 | # Subsector L:
29 | # Subsector M:
30 | # Subsector N:
31 | # Subsector O:
32 | # Subsector P:
33 |
34 | # Alleg: Va: "Non-Aligned, Vargr-dominated"
35 | # Alleg: VA: "Alliance of Tju"
36 | # Alleg: Ve: "Empire of Varroerth"
37 | # Alleg: VK: "Koenotz Empire"
38 |
39 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar
40 | ---- -------------------- --------- -------------------- ---- ---- ---- - -- - --- - -- ------------------------
41 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/NBZLineReduce.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Apr 03, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaPasses.WithinLineReducer import WithinLineReducer
7 | from PyRoute.DeltaStar import DeltaStar
8 |
9 |
10 | class NBZLineReduce(WithinLineReducer):
11 |
12 | def _build_subs_list(self):
13 | self.full_msg = "Reduction found with nbz reduction"
14 | self.start_msg = "Commencing nbz within-line reduction"
15 | # build substitution list - reduce _everything_
16 | subs_list = []
17 | segment = []
18 | num_lines = len(self.reducer.sectors.lines)
19 | for line in self.reducer.sectors.lines:
20 | canon = DeltaStar.reduce_nbz(line)
21 | assert isinstance(canon,
22 | str), "Candidate line " + line + " was not reduced to a string. Got " + canon + " instead."
23 | # Skip already-reduced lines
24 | if 2 < num_lines and line.startswith(canon):
25 | continue
26 | subs_list.append((line, canon))
27 | segment.append(line)
28 | return segment, subs_list
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Thomas Jones-Low
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/PyRoute/Calculation/NoneCalculation.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Aug 09, 2023
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.Allies.AllyGen import AllyGen
7 | from PyRoute.Calculation.RouteCalculation import RouteCalculation
8 |
9 |
10 | class NoneCalculation(RouteCalculation):
11 | def __init__(self, galaxy):
12 | super(NoneCalculation, self).__init__(galaxy)
13 |
14 | # Pure HG Base jump distance cost
15 | distance_weight = [0, 30, 50, 75, 130, 230, 490]
16 |
17 | def generate_routes(self) -> None:
18 | # self.generate_base_routes()
19 | pass
20 |
21 | def calculate_routes(self) -> None:
22 | pass
23 |
24 | def base_route_filter(self, star, neighbor) -> bool:
25 | return not AllyGen.are_owned_allies(star.alg_code, neighbor.alg_code)
26 |
27 | def base_range_routes(self, star, neighbor) -> int:
28 | return star.distance(neighbor)
29 |
30 | def route_weight(self, star, target) -> float:
31 | dist = star.distance(target)
32 | weight = self.distance_weight[dist]
33 | return weight
34 |
35 | def cross_check_totals(self) -> None:
36 | pass
37 |
--------------------------------------------------------------------------------
/Tests/DeltaDictionary/testAllegianceSubset.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Apr 23, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaDebug.DeltaDictionary import SectorDictionary, DeltaDictionary
7 | from PyRoute.Inputs.ParseStarInput import ParseStarInput
8 | from Tests.baseTest import baseTest
9 |
10 |
11 | class testAllegianceSubset(baseTest):
12 |
13 | def setUp(self):
14 | ParseStarInput.deep_space = {}
15 |
16 | def test_blowup_1(self) -> None:
17 | source = self.unpack_filename('DeltaFiles/allegiance_subset_blowup_1/Reaver\'s Deep.sec')
18 |
19 | delta = DeltaDictionary()
20 | for src in [source]:
21 | sector = SectorDictionary.load_traveller_map_file(src)
22 | delta[sector.name] = sector
23 | result, msg = delta.is_well_formed()
24 | self.assertTrue(result, msg)
25 |
26 | allegiances = delta.allegiance_list()
27 | nu_allegiances = allegiances.copy()
28 | nu_allegiances.remove("CaTe")
29 |
30 | nu_delta = delta.allegiance_subset(nu_allegiances)
31 | result, msg = nu_delta.is_well_formed()
32 | self.assertTrue(result, msg)
33 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/BaseLineReduce.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on May 06, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaPasses.WithinLineReducer import WithinLineReducer
7 | from PyRoute.DeltaStar import DeltaStar
8 |
9 |
10 | class BaseLineReduce(WithinLineReducer):
11 |
12 | def _build_subs_list(self):
13 | self.full_msg = "Reduction found with base reduction"
14 | self.start_msg = "Commencing base within-line reduction"
15 |
16 | # build substitution list - reduce _everything_
17 | subs_list = []
18 | segment = []
19 | num_lines = len(self.reducer.sectors.lines)
20 | for line in self.reducer.sectors.lines:
21 | canon = DeltaStar.reduce(line, drop_base_codes=True)
22 | assert isinstance(canon,
23 | str), "Candidate line " + line + " was not reduced to a string. Got " + canon + " instead."
24 | # Skip already-reduced lines
25 | if 2 < num_lines and line.startswith(canon):
26 | continue
27 | subs_list.append((line, canon))
28 | segment.append(line)
29 | return segment, subs_list
30 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/ZoneLineReduce.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on May 07, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaPasses.WithinLineReducer import WithinLineReducer
7 | from PyRoute.DeltaStar import DeltaStar
8 |
9 |
10 | class ZoneLineReduce(WithinLineReducer):
11 |
12 | def _build_subs_list(self):
13 | self.full_msg = "Reduction found with zone reduction"
14 | self.start_msg = "Commencing zone within-line reduction"
15 |
16 | # build substitution list - reduce _everything_
17 | subs_list = []
18 | segment = []
19 | num_lines = len(self.reducer.sectors.lines)
20 | for line in self.reducer.sectors.lines:
21 | canon = DeltaStar.reduce(line, drop_trade_zone=True)
22 | assert isinstance(canon,
23 | str), "Candidate line " + line + " was not reduced to a string. Got " + canon + " instead."
24 | # Skip already-reduced lines
25 | if 2 < num_lines and line.startswith(canon):
26 | continue
27 | subs_list.append((line, canon))
28 | segment.append(line)
29 | return segment, subs_list
30 |
--------------------------------------------------------------------------------
/PyRoute/templates/sectors.wiki:
--------------------------------------------------------------------------------
1 | {% import 'statistics_table.wiki' as stats %}
2 |
3 | == Sectors ==
4 | {| class="wikitable sortable" width="90%"
5 | |+ Analysis per Sector
6 | !Sector!! X,Y !! Worlds !! Population (millions) !! % of Population !! Economy (Bcr) !! Per Capita (Cr) !! RU !! Trade Volume (BCr / year) !! Int. Trade (BCr / year) !! Int. Trade (MDton / year) !! Ext. Trade (BCr/year) !! Ext. Trade (MDton / year) !! Shipyard Capacity (MTons) !! Colonial Army (BEs) !! Travellers (M / year) !! SPA Pop
7 | {% for sector in sectors.values() -%}
8 | |-
9 | | {{sector.wiki_name()}}
10 | | {{sector.x}},{{sector.y}}
11 | {{ stats.stats_table(sector.stats, global_stats) }}
12 | {%- endfor -%}
13 | {%- if im_stats %}
14 | |-
15 | | Imperial Totals ||
16 | {{ stats.stats_table(im_stats.stats, global_stats) }}
17 | {%- endif -%}
18 | |-
19 | | Global Totals ||
20 | {{ stats.stats_table(global_stats, global_stats) }}
21 | |}
22 |
23 |
24 | {% for sector in sectors.values() -%}
25 |
26 | == {{sector.name}} statistics ==
27 | {{ stats.text_area_long('sector', sector, plural) }}
28 |
29 | === Polities ===
30 | {{ stats.allegiance_statistics(plural, sector) }}
31 | {%- endfor -%}
32 |
33 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/AuxiliaryLineReduce.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Jul 13, 2023
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaPasses.WithinLineReducer import WithinLineReducer
7 | from PyRoute.DeltaStar import DeltaStar
8 |
9 |
10 | class AuxiliaryLineReduce(WithinLineReducer):
11 |
12 | def _build_subs_list(self):
13 | self.full_msg = "Reduction found with auxiliary line reduction"
14 | self.start_msg = "Commencing auxiliary within-line reduction"
15 | # build substitution list - reduce _everything_
16 | subs_list = []
17 | segment = []
18 | num_lines = len(self.reducer.sectors.lines)
19 | for line in self.reducer.sectors.lines:
20 | canon = DeltaStar.reduce_auxiliary(line)
21 | assert isinstance(canon,
22 | str), "Candidate line " + line + " was not reduced to a string. Got " + canon + " instead."
23 | # Skip already-reduced lines
24 | if 2 < num_lines and line.startswith(canon):
25 | continue
26 | subs_list.append((line, canon))
27 | segment.append(line)
28 | return segment, subs_list
29 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/CapitalLineReduce.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Jul 17, 2023
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaPasses.WithinLineReducer import WithinLineReducer
7 | from PyRoute.DeltaStar import DeltaStar
8 |
9 |
10 | class CapitalLineReduce(WithinLineReducer):
11 |
12 | def _build_subs_list(self):
13 | self.full_msg = "Reduction found with capital line reduction"
14 | self.start_msg = "Commencing capital within-line reduction"
15 | # build substitution list - reduce _everything_
16 | subs_list = []
17 | segment = []
18 | num_lines = len(self.reducer.sectors.lines)
19 | for line in self.reducer.sectors.lines:
20 | canon = DeltaStar.reduce(line, reset_capitals=True)
21 | assert isinstance(canon,
22 | str), "Candidate line " + line + " was not reduced to a string. Got " + canon + " instead."
23 | # Skip already-reduced lines
24 | if 2 < num_lines and line.startswith(canon):
25 | continue
26 | subs_list.append((line, canon))
27 | segment.append(line)
28 | return segment, subs_list
29 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/NoblesTrimLineReduce.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on May 05, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaPasses.WithinLineReducer import WithinLineReducer
7 | from PyRoute.DeltaStar import DeltaStar
8 |
9 |
10 | class NoblesTrimLineReduce(WithinLineReducer):
11 |
12 | def _build_subs_list(self):
13 | self.full_msg = "Reduction found with nobles trim"
14 | self.start_msg = "Commencing nobles trim within-line reduction"
15 |
16 | # build substitution list - reduce _everything_
17 | subs_list = []
18 | segment = []
19 | num_lines = len(self.reducer.sectors.lines)
20 | for line in self.reducer.sectors.lines:
21 | canon = DeltaStar.reduce(line, trim_noble_codes=True)
22 | assert isinstance(canon,
23 | str), "Candidate line " + line + " was not reduced to a string. Got " + canon + " instead."
24 | # Skip already-reduced lines
25 | if 2 < num_lines and line.startswith(canon):
26 | continue
27 | subs_list.append((line, canon))
28 | segment.append(line)
29 | return segment, subs_list
30 |
--------------------------------------------------------------------------------
/PyRoute/StatCalculation/Populations.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Dec 03, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.Star import Star
7 |
8 |
9 | class Populations(object):
10 | def __init__(self) -> None:
11 | self.code = ""
12 | self.homeworlds: list[Star] = []
13 | self.count = 0
14 | self.population = 0
15 |
16 | def add_population(self, population, homeworld) -> None:
17 | self.count += 1
18 | self.population += population
19 | if homeworld:
20 | self.homeworlds.append(homeworld)
21 |
22 | def __lt__(self, other):
23 | return self.population < other.population
24 |
25 | def __eq__(self, other):
26 | if self.__hash__() != other.__hash__():
27 | return False
28 | if self.code != other.code:
29 | return False
30 | if self.count != other.count:
31 | return False
32 | if self.population != other.population:
33 | return False
34 | return self.homeworlds == other.homeworlds
35 |
36 | def __hash__(self) -> int:
37 | key = (self.code, self.count, self.population)
38 | return hash(key)
39 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/BaseTrimLineReduce.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on May 08, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaPasses.WithinLineReducer import WithinLineReducer
7 | from PyRoute.DeltaStar import DeltaStar
8 |
9 |
10 | class BaseTrimLineReduce(WithinLineReducer):
11 |
12 | def _build_subs_list(self):
13 | self.full_msg = "Reduction found with base trim reduction"
14 | self.start_msg = "Commencing base trim within-line reduction"
15 |
16 | # build substitution list - reduce _everything_
17 | subs_list = []
18 | segment = []
19 | num_lines = len(self.reducer.sectors.lines)
20 | for line in self.reducer.sectors.lines:
21 | canon = DeltaStar.reduce(line, trim_base_codes=True)
22 | assert isinstance(canon,
23 | str), "Candidate line " + line + " was not reduced to a string. Got " + canon + " instead."
24 | # Skip already-reduced lines
25 | if 2 < num_lines and line.startswith(canon):
26 | continue
27 | subs_list.append((line, canon))
28 | segment.append(line)
29 | return segment, subs_list
30 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/ZoneTrimLineReduce.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on May 07, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaPasses.WithinLineReducer import WithinLineReducer
7 | from PyRoute.DeltaStar import DeltaStar
8 |
9 |
10 | class ZoneTrimLineReduce(WithinLineReducer):
11 |
12 | def _build_subs_list(self):
13 | self.full_msg = "Reduction found with zone trim reduction"
14 | self.start_msg = "Commencing zone trim within-line reduction"
15 |
16 | # build substitution list - reduce _everything_
17 | subs_list = []
18 | segment = []
19 | num_lines = len(self.reducer.sectors.lines)
20 | for line in self.reducer.sectors.lines:
21 | canon = DeltaStar.reduce(line, trim_trade_zone=True)
22 | assert isinstance(canon,
23 | str), "Candidate line " + line + " was not reduced to a string. Got " + canon + " instead."
24 | # Skip already-reduced lines
25 | if 2 < num_lines and line.startswith(canon):
26 | continue
27 | subs_list.append((line, canon))
28 | segment.append(line)
29 | return segment, subs_list
30 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/TradeCodeLineReduce.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on May 04, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaPasses.WithinLineReducer import WithinLineReducer
7 | from PyRoute.DeltaStar import DeltaStar
8 |
9 |
10 | class TradeCodeLineReduce(WithinLineReducer):
11 |
12 | def _build_subs_list(self):
13 | self.full_msg = "Reduction found with trade-code reduction"
14 | self.start_msg = "Commencing trade-code within-line reduction"
15 |
16 | # build substitution list - reduce _everything_
17 | subs_list = []
18 | segment = []
19 | num_lines = len(self.reducer.sectors.lines)
20 | for line in self.reducer.sectors.lines:
21 | canon = DeltaStar.reduce(line, drop_trade_codes=True)
22 | assert isinstance(canon,
23 | str), "Candidate line " + line + " was not reduced to a string. Got " + canon + " instead."
24 | # Skip already-reduced lines
25 | if 2 < num_lines and line.startswith(canon):
26 | continue
27 | subs_list.append((line, canon))
28 | segment.append(line)
29 | return segment, subs_list
30 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/PortAndTlLineReduce.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on May 07, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaPasses.WithinLineReducer import WithinLineReducer
7 | from PyRoute.DeltaStar import DeltaStar
8 |
9 |
10 | class PortAndTlLineReduce(WithinLineReducer):
11 |
12 | def _build_subs_list(self):
13 | self.full_msg = "Reduction found with port and TL reduction"
14 | self.start_msg = "Commencing port and TL within-line reduction"
15 |
16 | # build substitution list - reduce _everything_
17 | subs_list = []
18 | segment = []
19 | num_lines = len(self.reducer.sectors.lines)
20 | for line in self.reducer.sectors.lines:
21 | canon = DeltaStar.reduce(line, reset_port=True, reset_tl=True)
22 | assert isinstance(canon,
23 | str), "Candidate line " + line + " was not reduced to a string. Got " + canon + " instead."
24 | # Skip already-reduced lines
25 | if 2 < num_lines and line.startswith(canon):
26 | continue
27 | subs_list.append((line, canon))
28 | segment.append(line)
29 | return segment, subs_list
30 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/TradeCodeTrimLineReduce.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on May 05, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaPasses.WithinLineReducer import WithinLineReducer
7 | from PyRoute.DeltaStar import DeltaStar
8 |
9 |
10 | class TradeCodeTrimLineReduce(WithinLineReducer):
11 |
12 | def _build_subs_list(self):
13 | self.full_msg = "Reduction found with trade codes trim"
14 | self.start_msg = "Commencing trade codes trim within-line reduction"
15 |
16 | # build substitution list - reduce _everything_
17 | subs_list = []
18 | segment = []
19 | num_lines = len(self.reducer.sectors.lines)
20 | for line in self.reducer.sectors.lines:
21 | canon = DeltaStar.reduce(line, trim_trade_codes=True)
22 | assert isinstance(canon,
23 | str), "Candidate line " + line + " was not reduced to a string. Got " + canon + " instead."
24 | # Skip already-reduced lines
25 | if 2 < num_lines and line.startswith(canon):
26 | continue
27 | subs_list.append((line, canon))
28 | segment.append(line)
29 | return segment, subs_list
30 |
--------------------------------------------------------------------------------
/Tests/testNobles.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Jan 1, 2019
3 |
4 | @author: CyberiaResurrection
5 | """
6 |
7 | import unittest
8 |
9 | from PyRoute.Nobles import Nobles
10 |
11 |
12 | class TestNobles(unittest.TestCase):
13 | def testDefaultString(self) -> None:
14 | nobles = Nobles()
15 | expected = '-'
16 | self.assertEqual(expected, nobles.__str__())
17 |
18 | def testStringWithOneViscount(self) -> None:
19 | nobles = Nobles()
20 | nobles.nobles['Viscounts'] = 1
21 |
22 | expected = 'e'
23 | self.assertEqual(expected, nobles.__str__())
24 |
25 | def testCountWithViscount(self) -> None:
26 | nobles = Nobles()
27 | nobles.count(['e'])
28 |
29 | expected = 1
30 | actual = nobles.nobles['Viscounts']
31 |
32 | self.assertEqual(expected, actual)
33 |
34 | def testAccumulateSelf(self) -> None:
35 | nobles = Nobles()
36 | nobles.nobles['Viscounts'] = 1
37 |
38 | nobles.accumulate(nobles)
39 |
40 | expected = 2
41 | actual = nobles.nobles['Viscounts']
42 |
43 | self.assertEqual(expected, actual)
44 |
45 |
46 | if __name__ == '__main__':
47 | unittest.main()
48 |
--------------------------------------------------------------------------------
/Tests/Hypothesis/testNobles.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from hypothesis import given, assume, settings, HealthCheck
4 | from hypothesis.strategies import text
5 |
6 | from PyRoute.Nobles import Nobles
7 |
8 |
9 | class testNobles(unittest.TestCase):
10 |
11 | @given(text(min_size=1, max_size=20, alphabet='ABcCDEfFGHI'))
12 | @settings(suppress_health_check=[HealthCheck(10)])
13 | def test_nobles_creation(self, noble_line) -> None:
14 | hyp_line = "Hypothesis input: " + noble_line
15 | foo = Nobles()
16 | foo.count(noble_line)
17 |
18 | result, msg = foo.is_well_formed()
19 | self.assertTrue(result, msg + '. ' + hyp_line)
20 |
21 | @given(text(min_size=1, max_size=6, alphabet='ABcCDEfFGHI'))
22 | @settings(suppress_health_check=[HealthCheck(10)])
23 | def test_nobles_str_round_trip(self, noble_line) -> None:
24 | hyp_line = "Hypothesis input: " + noble_line
25 | foo = Nobles()
26 | foo.count(noble_line)
27 | assume(0 < foo.sum_value)
28 |
29 | str_rep = str(foo)
30 |
31 | nu_foo = Nobles()
32 | nu_foo.count(str_rep)
33 |
34 | nu_rep = str(nu_foo)
35 | self.assertEqual(str_rep, nu_rep, hyp_line)
36 |
--------------------------------------------------------------------------------
/PyRoute/templates/sector_econ.wiki:
--------------------------------------------------------------------------------
1 |
2 | {| class="wikitable sortable" width="90%"
3 | |+ {{ sector.sector_name() }} economic data
4 | !Hex!!Name!!UWP!!PBG!!{Ix}!!WTN!!MSPR!!RU!!Per Capita (Cr)!!GWP (BCr)!!Trade (MCr/year)
5 | !Passengers (per year)!!Build!!Army!!Port!!SPA Population
6 | !Trade Goods!!Subsector
7 | {%- for subsector in sector.subsectors.values() -%}
8 |
9 | {%- for world in subsector.worlds %}
10 | |-
11 | | {{ world.position }}
12 | | {{ "[[:s|]]".format(world.wiki_short_name()) }}
13 | | {{ world.uwp }}
14 | | {{ world.popM }}{{world.belts}}{{world.ggCount}}
15 | | { {{world.importance}} }
16 | | {{world.wtn}}
17 | | {{world.mspr}}
18 | | {{"{:,d}".format(world.ru_int)}}
19 | | {{"{:,d}".format(world.perCapita)}}
20 | | {{"{:,d}".format(world.gwp)}}
21 | | {{"{:,d}".format(world.tradeIn // 10000000) }}
22 | | {{"{:,d}".format(world.passIn) }}
23 | | {{"{:,d}".format(world.ship_capacity) }}
24 | | {{"{:,d}".format(world.raw_be) }}
25 | | {{ world.starportSize }}
26 | | {{"{:,d}".format(world.starportPop) }}
27 | | {{ world.trade_id }}
28 | | {{ subsector.wiki_name() }}
29 | {% endfor -%}
30 |
31 | {%- endfor %}
32 | |}
33 |
34 | [[Category: Sectors with sector economic data]]
35 |
--------------------------------------------------------------------------------
/PyRoute/templates/summary.wiki:
--------------------------------------------------------------------------------
1 | {% import 'statistics_table.wiki' as stats %}
2 | == Top Level Summary ==
3 | {{ stats.top_level_summary(global_stats, sectors) }}
4 |
5 | {|
6 | |- style="vertical-align:top;text-align:center;"
7 | |
8 | {{ stats.uwp_table ('Starport', uwp.uwp['Starport'], global_stats) }}
9 | |
10 | {{ stats.uwp_table ('Size', uwp.uwp['Size'], global_stats) }}
11 | |
12 | {{ stats.uwp_table ('Atmosphere', uwp.uwp['Atmosphere'], global_stats) }}
13 | |
14 | {{ stats.uwp_table ('Hydrographics', uwp.uwp['Hydrographics'], global_stats) }}
15 | |- style="vertical-align:top;text-align:center;"
16 | |
17 | {{ stats.uwp_table ('Population', uwp.uwp['Population'], global_stats) }}
18 | |
19 | {{ stats.uwp_table ('Government', uwp.uwp['Government'], global_stats) }}
20 | |
21 | {{ stats.uwp_table ('Law Level', uwp.uwp['Law Level'], global_stats) }}
22 | |
23 | {{ stats.uwp_table ('Tech Level', uwp.uwp['Tech Level'], global_stats) }}
24 | |- style="vertical-align:top;text-align:center;"
25 | |
26 | {{ stats.uwp_table ('Importance', uwp.uwp['Importance'], global_stats) }}
27 | |
28 | {{ stats.uwp_table ('Resources', uwp.uwp['Resources'], global_stats) }}
29 | |
30 | {{ stats.uwp_table ('Starport Size', uwp.uwp['Starport Size'], global_stats) }}
31 | |
32 | {{ stats.uwp_table ('Primary Type', uwp.uwp['Primary Type'], global_stats) }}
33 | |}
34 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/sector_subset_blowup_on_vland_empty/Vland.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2021-11-28T10:36:38-08:00
3 |
4 | # Vland
5 | # -1,1
6 |
7 | # Name: Vland
8 |
9 | # Abbreviation: Vlan
10 |
11 | # Milieu: M1105
12 |
13 | # Credits: Vland sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by Joe D. Fugate Sr. and appears in The Travellers Digest and The MegaTraveller Alien, Volume 1: Vilani & Vargr (DGP, 1990)
14 |
15 | # Source: Traveller 5 Second Survey
16 |
17 | # Subsector A: Voskhod
18 | # Subsector B: Vhodan
19 | # Subsector C: Anarsi
20 | # Subsector D: Theton
21 | # Subsector E: Lalaki Kharir
22 | # Subsector F: Kagamira
23 | # Subsector G: Vland
24 | # Subsector H: Shiigus
25 | # Subsector I: Dusa
26 | # Subsector J: Akumid
27 | # Subsector K: Kasear
28 | # Subsector L: Anakod
29 | # Subsector M: Parsi
30 | # Subsector N: Daangiilu
31 | # Subsector O: Nulisud
32 | # Subsector P: Kakadan
33 |
34 | # Alleg: CsIm: "Client state, Third Imperium"
35 |
36 |
37 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes
38 | ---- -------------------- --------- ------------------------- ------ ------- ------ ---- -- - --- -- ---- --------------- ---------------------------------------
--------------------------------------------------------------------------------
/Tests/DeltaFiles/border_blowup_2/Zdiedeiant.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2024-09-28T17:30:10-07:00
3 |
4 | # Zdiedeiant
5 | # -7,3
6 |
7 | # Name: Zdiedeiant (zh)
8 |
9 | # Abbreviation: Zdie
10 |
11 | # Milieu: M1105
12 |
13 | # Credits: Zdiedeiant sector was designed by P-O Bergstedt.
14 |
15 | # Author: P-O Bergstedt
16 | # Source: The Zhodani Base
17 | # Ref: http://zho.berka.com/data/CLASSIC/sector.pl?sector=ZDIEDEIA
18 |
19 | # Subsector A: Iefla
20 | # Subsector B: Qliamria
21 | # Subsector C: Kletsshavri
22 | # Subsector D: Jetlta
23 | # Subsector E: Krietieqatl
24 | # Subsector F: A'letl
25 | # Subsector G: Kretlshtiazh
26 | # Subsector H: Etsia
27 | # Subsector I: Leo
28 | # Subsector J: Adliev
29 | # Subsector K: Sadliavl
30 | # Subsector L: Tlabqlel
31 | # Subsector M: Qrtlievl
32 | # Subsector N: Ieprsidr
33 | # Subsector O: Abrrnzh
34 | # Subsector P: Tliekrbliepl
35 |
36 | # Alleg: Na: "Non-aligned"
37 | # Alleg: Dr: "Droyne"
38 |
39 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar
40 | ---- -------------------- --------- -------------------- ---- ---- ---- - -- - --- - ---- -------
41 | 0332 Plit-79 X252000-0 Ba Po - - R 020 Na
42 | 0432 Uevro C3515X6-9 Ni Po - - R 322 Dr
43 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/border_blowup_1/Zdiedeiant.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2024-09-28T17:30:10-07:00
3 |
4 | # Zdiedeiant
5 | # -7,3
6 |
7 | # Name: Zdiedeiant (zh)
8 |
9 | # Abbreviation: Zdie
10 |
11 | # Milieu: M1105
12 |
13 | # Credits: Zdiedeiant sector was designed by P-O Bergstedt.
14 |
15 | # Author: P-O Bergstedt
16 | # Source: The Zhodani Base
17 | # Ref: http://zho.berka.com/data/CLASSIC/sector.pl?sector=ZDIEDEIA
18 |
19 | # Subsector A: Iefla
20 | # Subsector B: Qliamria
21 | # Subsector C: Kletsshavri
22 | # Subsector D: Jetlta
23 | # Subsector E: Krietieqatl
24 | # Subsector F: A'letl
25 | # Subsector G: Kretlshtiazh
26 | # Subsector H: Etsia
27 | # Subsector I: Leo
28 | # Subsector J: Adliev
29 | # Subsector K: Sadliavl
30 | # Subsector L: Tlabqlel
31 | # Subsector M: Qrtlievl
32 | # Subsector N: Ieprsidr
33 | # Subsector O: Abrrnzh
34 | # Subsector P: Tliekrbliepl
35 |
36 | # Alleg: Dr: "Droyne"
37 | # Alleg: ZhIa: "Zhodani Consulate, Iabrensh Province"
38 |
39 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar
40 | ---- -------------------- --------- -------------------- ---- ---- ---- - -- - --- - ---- -------
41 | 0332 Plit-79 X252000-0 Ba Po - - R 020 ZhIa
42 | 0432 Uevro C3515X6-9 Ni Po - - R 322 Dr
43 |
--------------------------------------------------------------------------------
/Tests/PathfindingFiles/single_source_distances_ibara_subsector_from_0101.json:
--------------------------------------------------------------------------------
1 | {"Engaki (Zarushagar 0502)": 175.0, "Strela (Zarushagar 0407)": 210.0, "Ymirial (Zarushagar 0106)": 193.0,
2 | "San Nuska Kilna (Zarushagar 0108)": 239.0, "Zed (Zarushagar 0109)": 263.0, "Imled (Zarushagar 0406)": 190.0,
3 | "Dugemaa (Zarushagar 0503)": 174.0, "Leniv Fenl (Zarushagar 0504)": 176.0, "Villenuve (Zarushagar 0507)": 213.0,
4 | "Toulon-Cadiz (Zarushagar 0510)": 302.0, "Aslungi (Zarushagar 0605)": 191.0, "Apkenumir (Zarushagar 0606)": 273.0,
5 | "Miller's World (Zarushagar 0607)": 234.0, "Norsec (Zarushagar 0110)": 285.0, "Allis (Zarushagar 0204)": 145.0,
6 | "Selsinia (Zarushagar 0201)": 54.0, "Arxani (Zarushagar 0209)": 283.0, "Point Zulu (Zarushagar 0302)": 74.0,
7 | "Makkus (Zarushagar 0304)": 119.0, "Freeport (Zarushagar 0305)": 145.0, "Woden (Zarushagar 0306)": 165.0,
8 | "Ichiban (Zarushagar 0309)": 255.0, "Shadishi (Zarushagar 0310)": 283.0, "Rogreis (Zarushagar 0402)": 101.0,
9 | "Bolten (Zarushagar 0404)": 144.0, "Ulaun (Zarushagar 0405)": 191.0, "Nedadzia (Zarushagar 0701)": 277.0,
10 | "Ledadzia (Zarushagar 0704)": 239.0, "Dorevann (Zarushagar 0708)": 313.0, "New Orlando (Zarushagar 0710)": 403.0,
11 | "Airvae (Zarushagar 0801)": 305.0, "Sufizi (Zarushagar 0802)": 281.0, "Cyrpus (Zarushagar 0803)": 261.0,
12 | "Gishin (Zarushagar 0804)": 236.0, "Ginshe (Zarushagar 0805)": 263.0, "Madagast (Zarushagar 0806)": 239.0}
--------------------------------------------------------------------------------
/Tests/DeltaFiles/insufficient_exhaust_value/Core.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2024-09-28T17:11:16-07:00
3 |
4 | # Core
5 | # 0,0
6 |
7 | # Name: Core
8 | # Name: Ukan (vi)
9 |
10 | # Abbreviation: Core
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: Core sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984).
15 |
16 | # Source: Traveller 5 Second Survey
17 |
18 | # Subsector A: Apge
19 | # Subsector B: Perite
20 | # Subsector C: Ameros
21 | # Subsector D: Shinkan
22 | # Subsector E: Sanches
23 | # Subsector F: Mekee
24 | # Subsector G: Core
25 | # Subsector H: Kaskii
26 | # Subsector I: Bunkeria
27 | # Subsector J: Cemplas
28 | # Subsector K: Chant
29 | # Subsector L: Dingtra
30 | # Subsector M: Cadion
31 | # Subsector N: Ch'naar
32 | # Subsector O: Dunea
33 | # Subsector P: Saregon
34 |
35 | # Alleg: ImDc: "Third Imperium, Domain of Sylea"
36 |
37 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar
38 | ---- -------------------- --------- ------------------------------------- ------ ------- ------ ----- -- - --- -- ---- ---------------
39 | 1108 Uumeaxan B765995-C Hi Ga Pr Ht { 4 } (G8F+2) [7D3A] BcEf NS - 414 11 ImDc M2 V
40 | 1109 Uuruun Kuu C987346-8 Lo { -2 } (A20-3) [2147] B - - 114 10 ImDc M0 V
41 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/no_subsectors_named/Ngathksirz empty.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2023-12-23T00:53:40-08:00
3 |
4 | # Ngathksirz
5 | # -2,3
6 |
7 | # Name: Ngathksirz
8 |
9 | # Abbreviation: Ngat
10 |
11 | # Milieu: M1105
12 |
13 | # Credits: Ngathksirz sector was designed by John G. Wood. Stellar positions were developed by Joe D. Fugate Sr. and appear in The MegaTraveller Alien, Volume 1: Vilani & Vargr (Digest Group Publications, 1990).
14 |
15 | # Author: John G. Wood
16 | # Source: Underdeveloped Sectors
17 | # Ref: https://web.archive.org/web/20160205040546/http://homepage.ntlworld.com/elvwood/Traveller/Sectors/
18 |
19 | # Subsector A: Odzgoegh
20 | # Subsector B: Kfirzin
21 | # Subsector C: Kueroe
22 | # Subsector D: Aengangvuer
23 | # Subsector E: Dhaendzouts
24 | # Subsector F: Ruevoga
25 | # Subsector G: Aenael
26 | # Subsector H: Roenvoegazdu
27 | # Subsector I: Zarrkoe
28 | # Subsector J: Thafo
29 | # Subsector K: Uengou
30 | # Subsector L: Raez
31 | # Subsector M: Vaerr
32 | # Subsector N: Nouksaezor
33 | # Subsector O: Oegerag
34 | # Subsector P: Serrken
35 |
36 | # Alleg: Va: "Non-Aligned, Vargr-dominated"
37 | # Alleg: Ve: "Empire of Varroerth"
38 |
39 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar
40 | ---- ------------------------------ --------- ----------------------- ---- ------- ------ - - - --- -- -- --------------------------
41 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/no_subsectors_named/Zao Kfeng Ig Grilokh empty.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2021-11-28T10:37:18-08:00
3 |
4 | # Zao Kfeng Ig Grilokh
5 | # -2,4
6 |
7 | # Name: Zao Kfeng Ig Grilokh (va)
8 |
9 | # Abbreviation: Zaok
10 |
11 | # Milieu: M1105
12 |
13 | # Credits: Zao Kfeng Ig Grilokh sector was designed by John G. Wood and WG Zeist. Stellar positions were developed by Joe D. Fugate Sr. and appear in The MegaTraveller Alien, Volume 1: Vilani & Vargr (Digest Group Publications, 1990).
14 |
15 | # Author: John G. Wood, WG Zeist
16 | # Source: Underdeveloped Sectors
17 | # Ref: https://web.archive.org/web/20160205040546/http://homepage.ntlworld.com/elvwood/Traveller/Sectors/
18 |
19 | # Subsector A:
20 | # Subsector B:
21 | # Subsector C:
22 | # Subsector D:
23 | # Subsector E:
24 | # Subsector F:
25 | # Subsector G:
26 | # Subsector H:
27 | # Subsector I:
28 | # Subsector J:
29 | # Subsector K:
30 | # Subsector L:
31 | # Subsector M:
32 | # Subsector N:
33 | # Subsector O:
34 | # Subsector P:
35 |
36 | # Alleg: Va: "Non-Aligned, Vargr-dominated"
37 | # Alleg: Ve: "Empire of Varroerth"
38 | # Alleg: VF: "Far Stars Unanimity"
39 | # Alleg: VK: "Koenotz Empire"
40 | # Alleg: VS: "Jarrgh Subjugate"
41 |
42 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes
43 | ---- -------------------- --------- -------------------- ---- ---- ---- - -- - --- - -- ---------------- ------
44 |
--------------------------------------------------------------------------------
/PyRoute/Pathfinding/DistanceBase.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Feb 25, 2024
3 |
4 | @author: CyberiaResurrection
5 | """
6 | import numpy as np
7 | from networkx.classes import Graph
8 |
9 |
10 | class DistanceBase:
11 |
12 | def __init__(self, graph: Graph):
13 | raw_nodes = graph.nodes()
14 | self._nodes = list(raw_nodes)
15 | num_nodes = len(self._nodes)
16 | self._indexes = {node: i for (i, node) in enumerate(self._nodes)}
17 | positions = [
18 | raw_nodes[u]['star'].hex.hex_position() for u in self._nodes
19 | ]
20 | self._positions = np.zeros((num_nodes, 2), dtype=int)
21 | for i in range(num_nodes):
22 | self._positions[i, :] = np.array(positions[i])
23 |
24 | def __len__(self) -> int:
25 | return len(self._nodes)
26 |
27 | def lighten_edge(self, u: int, v: int, weight: float) -> None:
28 | raise NotImplementedError("Base Class")
29 |
30 | def distances_from_target(self, active_nodes, target: int) -> int:
31 | if len(active_nodes) == len(self):
32 | dq = self._positions[:, 0] - self._positions[target, 0]
33 | dr = self._positions[:, 1] - self._positions[target, 1]
34 | else:
35 | dq = self._positions[active_nodes, 0] - self._positions[target, 0]
36 | dr = self._positions[active_nodes, 1] - self._positions[target, 1]
37 |
38 | return (abs(dq) + abs(dr) + abs(dq + dr)) // 2
39 |
40 | def _lighten_arc(self, u: int, v: int, weight: float) -> None:
41 | self._arcs[u][1][self._arcs[u][0] == v] = weight
42 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/single_system_border/Deneb.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2023-12-23T00:43:40-08:00
3 |
4 | # Deneb
5 | # -3,1
6 |
7 | # Name: Deneb
8 | # Name: Nieklsdia (zh)
9 |
10 | # Abbreviation: Dene
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: Deneb sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by James Holden. Portions appear in The Travellers Digest and The MegaTraveller Journal #3 (Digest Group Publications, 1992).
15 |
16 | # Source: Traveller 5 Second Survey
17 |
18 | # Subsector A: Pretoria
19 | # Subsector B: Lamas
20 | # Subsector C: Antra
21 | # Subsector D: Million
22 | # Subsector E: Sabine
23 | # Subsector F: Inar
24 | # Subsector G: Dunmag
25 | # Subsector H: Atsah
26 | # Subsector I: Star Lane
27 | # Subsector J: Vincennes
28 | # Subsector K: Usani
29 | # Subsector L: Geniishir
30 | # Subsector M: Gulf
31 | # Subsector N: Zeng
32 | # Subsector O: Kamlar
33 | # Subsector P: Vast Heavens
34 |
35 | # Alleg: CsIm: "Client state, Third Imperium"
36 | # Alleg: ImDd: "Third Imperium, Domain of Deneb"
37 | # Alleg: NaHu: "Non-Aligned, Human-dominated"
38 | # Alleg: NaVa: "Non-Aligned, Vargr-dominated"
39 | # Alleg: VAug: "United Followers of Augurgh"
40 | # Alleg: VDzF: "Dzarrgh Federate"
41 |
42 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar
43 | ---- -------------------- --------- -------------------------------- ------ ------- ------ ---- -- - --- -- ---- --------------
44 | 1018 Hessel B658100-C Lo Ht { 1 } (401-3) [1217] B N - 801 12 ImDd F4 V K4 V
45 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/zao_kfeng_jump_4_template_blowup/Zao Kfeng Ig Grilokh.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2023-11-02T04:28:16-07:00
3 |
4 | # Zao Kfeng Ig Grilokh
5 | # -2,4
6 |
7 | # Name: Zao Kfeng Ig Grilokh (va)
8 |
9 | # Abbreviation: Zaok
10 |
11 | # Milieu: M1105
12 |
13 | # Credits: Zao Kfeng Ig Grilokh sector was designed by John G. Wood and WG Zeist. Stellar positions were developed by Joe D. Fugate Sr. and appear in The MegaTraveller Alien, Volume 1: Vilani & Vargr (Digest Group Publications, 1990).
14 |
15 | # Author: John G. Wood, WG Zeist
16 | # Source: Underdeveloped Sectors
17 | # Ref: https://web.archive.org/web/20160205040546/http://homepage.ntlworld.com/elvwood/Traveller/Sectors/
18 |
19 | # Subsector A:
20 | # Subsector B:
21 | # Subsector C:
22 | # Subsector D:
23 | # Subsector E:
24 | # Subsector F:
25 | # Subsector G:
26 | # Subsector H:
27 | # Subsector I:
28 | # Subsector J:
29 | # Subsector K:
30 | # Subsector L:
31 | # Subsector M:
32 | # Subsector N:
33 | # Subsector O:
34 | # Subsector P:
35 |
36 | # Alleg: Va: "Non-Aligned, Vargr-dominated"
37 | # Alleg: Ve: "Empire of Varroerth"
38 | # Alleg: VF: "Far Stars Unanimity"
39 | # Alleg: VK: "Koenotz Empire"
40 | # Alleg: VS: "Jarrgh Subjugate"
41 |
42 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar
43 | ---- -------------------- --------- -------------------- ---- ---- ---- - -- - --- - -- ----------------
44 | 2739 Sueuvorrerr C000848-8 As Na - C - 404 Ve D
45 | 2835 Uekhngaeng C211888-5 Ic Na - C - 900 Ve F5 V
46 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/high_pop_worlds_blowup/Spinward Marches.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2021-11-28T10:33:42-08:00
3 |
4 | # Spinward Marches
5 | # -4,1
6 |
7 | # Name: Spinward Marches
8 | # Name: Tloql (zh)
9 |
10 | # Abbreviation: Spin
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: The Spinward Marches were designed by Marc W. Miller and appear in Traveller Supplement 3: The Spinward Marches (GDW, 1979).
15 |
16 | # Source: Traveller 5 Second Survey
17 |
18 | # Subsector A: Cronor
19 | # Subsector B: Jewell
20 | # Subsector C: Regina
21 | # Subsector D: Aramis
22 | # Subsector E: Querion
23 | # Subsector F: Vilis
24 | # Subsector G: Lanth
25 | # Subsector H: Rhylanor
26 | # Subsector I: Darrian
27 | # Subsector J: Sword Worlds
28 | # Subsector K: Lunion
29 | # Subsector L: Mora
30 | # Subsector M: Five Sisters
31 | # Subsector N: District 268
32 | # Subsector O: Glisten
33 | # Subsector P: Trin's Veil
34 |
35 | # Alleg: CsIm: "Client state, Third Imperium"
36 | # Alleg: CsZh: "Client state, Zhodani Consulate"
37 | # Alleg: DaCf: "Darrian Confederation"
38 | # Alleg: ImDd: "Third Imperium, Domain of Deneb"
39 | # Alleg: NaHu: "Non-Aligned, Human-dominated"
40 | # Alleg: NaXX: "Non-Aligned, unclaimed"
41 | # Alleg: SwCf: "Sword Worlds Confederation"
42 | # Alleg: ZhIN: "Zhodani Consulate, Iadr Nsobl Province"
43 |
44 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes
45 | ---- -------------------- --------- ---------------------------------------- ------ ------- ------ ----- -- - --- -- ---- -------------- ---------------------------------
46 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/no_subsectors_named/Zao Kfeng Ig Grilokh - subsector P - trimmed.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2021-11-28T10:37:18-08:00
3 |
4 | # Zao Kfeng Ig Grilokh
5 | # -2,4
6 |
7 | # Name: Zao Kfeng Ig Grilokh (va)
8 |
9 | # Abbreviation: Zaok
10 |
11 | # Milieu: M1105
12 |
13 | # Credits: Zao Kfeng Ig Grilokh sector was designed by John G. Wood and WG Zeist. Stellar positions were developed by Joe D. Fugate Sr. and appear in The MegaTraveller Alien, Volume 1: Vilani & Vargr (Digest Group Publications, 1990).
14 |
15 | # Author: John G. Wood, WG Zeist
16 | # Source: Underdeveloped Sectors
17 | # Ref: https://web.archive.org/web/20160205040546/http://homepage.ntlworld.com/elvwood/Traveller/Sectors/
18 |
19 | # Subsector A:
20 | # Subsector B:
21 | # Subsector C:
22 | # Subsector D:
23 | # Subsector E:
24 | # Subsector F:
25 | # Subsector G:
26 | # Subsector H:
27 | # Subsector I:
28 | # Subsector J:
29 | # Subsector K:
30 | # Subsector L:
31 | # Subsector M:
32 | # Subsector N:
33 | # Subsector O:
34 | # Subsector P:
35 |
36 | # Alleg: Va: "Non-Aligned, Vargr-dominated"
37 | # Alleg: Ve: "Empire of Varroerth"
38 | # Alleg: VF: "Far Stars Unanimity"
39 | # Alleg: VK: "Koenotz Empire"
40 | # Alleg: VS: "Jarrgh Subjugate"
41 |
42 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes
43 | ---- -------------------- --------- -------------------- ---- ---- ---- - -- - --- - -- ---------------- ------
44 | 2633 Khaenukh E400559-8 Ni Va - C - 502 Va G4 V
45 | 2734 Afousoerr C456975-5 Hi In - K - 803 Va F0 V
46 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/Dagudashaag-star-object-no-sector-attribute.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2021-11-28T10:21:52-08:00
3 |
4 | # Dagudashaag
5 | # -1,0
6 |
7 | # Name: Dagudashaag
8 | # Name: Dagudashag
9 |
10 | # Abbreviation: Dagu
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: Dagudashaag sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by Duncan Law-Green, Leighton Piper and Jae Campbell, and appears in Signal GK Magazine.
15 |
16 | # Author: Duncan Law-Green, Leighton Piper and Jae Campbell; Martin J. Dougherty
17 | # Source: Traveller 5 Second Survey
18 |
19 | # Subsector A: Mimu
20 | # Subsector B: Old Suns
21 | # Subsector C: Arnakhish
22 | # Subsector D: Iiradu
23 | # Subsector E: Shallows
24 | # Subsector F: Ushra
25 | # Subsector G: Khandi
26 | # Subsector H: Kuriishe
27 | # Subsector I: Zeda
28 | # Subsector J: Remnants
29 | # Subsector K: Pact
30 | # Subsector L: Gadde
31 | # Subsector M: Bolivar
32 | # Subsector N: Argi
33 | # Subsector O: Sapphyre
34 | # Subsector P: Laraa
35 |
36 | # Alleg: ImAp: "Third Imperium, Amec Protectorate"
37 | # Alleg: ImDv: "Third Imperium, Domain of Vland"
38 | # Alleg: ImLc: "Third Imperium, Lancian Cultural Region"
39 |
40 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes
41 | ---- -------------------- --------- ------------------------------------- ------ ------- ------ ---- -- - --- -- ---- --------------- -----------------------------------------
42 | 3240 Saven A5A0734-D He { 2 } (D6D+1) [593B] B N - 613 11 ImDv G2 IV Xb:3139 Xb:Core-0139 Xb:Zaru-3202
43 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/stat_calc_division_by_zero_population/Dagudashaag-zero.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2021-11-28T10:21:52-08:00
3 |
4 | # Dagudashaag
5 | # -1,0
6 |
7 | # Name: Dagudashaag
8 | # Name: Dagudashag
9 |
10 | # Abbreviation: Dagu
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: Dagudashaag sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by Duncan Law-Green, Leighton Piper and Jae Campbell, and appears in Signal GK Magazine.
15 |
16 | # Author: Duncan Law-Green, Leighton Piper and Jae Campbell; Martin J. Dougherty
17 | # Source: Traveller 5 Second Survey
18 |
19 | # Subsector A: Mimu
20 | # Subsector B: Old Suns
21 | # Subsector C: Arnakhish
22 | # Subsector D: Iiradu
23 | # Subsector E: Shallows
24 | # Subsector F: Ushra
25 | # Subsector G: Khandi
26 | # Subsector H: Kuriishe
27 | # Subsector I: Zeda
28 | # Subsector J: Remnants
29 | # Subsector K: Pact
30 | # Subsector L: Gadde
31 | # Subsector M: Bolivar
32 | # Subsector N: Argi
33 | # Subsector O: Sapphyre
34 | # Subsector P: Laraa
35 |
36 | # Alleg: ImAp: "Third Imperium, Amec Protectorate"
37 | # Alleg: ImDv: "Third Imperium, Domain of Vland"
38 | # Alleg: ImLc: "Third Imperium, Lancian Cultural Region"
39 |
40 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes
41 | ---- -------------------- --------- ------------------------------------- ------ ------- ------ ---- -- - --- -- ---- --------------- -----------------------------------------
42 | 2123 Kediiga B778411-8 Ni Pa { -1 } (832-5) [1314] Bc - - 920 9 ImDv G6 V
43 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/stars_node_types/Antares.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2021-11-28T10:20:28-08:00
3 |
4 | # Antares
5 | # 1,1
6 |
7 | # Name: Antares
8 | # Name: Mikasirka (vi)
9 |
10 | # Abbreviation: Anta
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: Antares sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984).
15 |
16 | # Source: Traveller 5 Second Survey
17 |
18 | # Subsector A: Pelusium
19 | # Subsector B: Ninik
20 | # Subsector C: Dartho
21 | # Subsector D: Oulduktak
22 | # Subsector E: Shurlarlem
23 | # Subsector F: Sarar
24 | # Subsector G: Gimgir
25 | # Subsector H: Kiirkandin
26 | # Subsector I: Urunishu
27 | # Subsector J: Uradnim
28 | # Subsector K: Antares
29 | # Subsector L: Mukusi
30 | # Subsector M: Urgusap
31 | # Subsector N: Gaakish
32 | # Subsector O: Sakhag
33 | # Subsector P: Celebes
34 |
35 | # Alleg: CsIm: "Client state, Third Imperium"
36 | # Alleg: ImDa: "Third Imperium, Domain of Antares"
37 | # Alleg: ImLa: "Third Imperium, League of Antares"
38 | # Alleg: JuRu: "Julian Protectorate, Rukadukaz Republic"
39 | # Alleg: NaHu: "Non-Aligned, Human-dominated"
40 | # Alleg: NaVa: "Non-Aligned, Vargr-dominated"
41 |
42 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes
43 | ---- -------------------- --------- ------------------------- ------ ------- ------ ---- -- - --- -- ---- -------------- ------------------------------------
44 | 0823 Oerchgwe E4228A6-7 He Na Po Ph Pi { -2 } (A76-3) [7646] BDe - - 223 13 ImDa G6 V
45 | 0824 Wyshamshen C999657-6 Ni { -2 } (852-2) [6456] B S - 602 13 ImDa K4 V
46 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/xroute_routes_pass_1_2/Core.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2024-09-28T17:11:16-07:00
3 |
4 | # Core
5 | # 0,0
6 |
7 | # Name: Core
8 | # Name: Ukan (vi)
9 |
10 | # Abbreviation: Core
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: Core sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984).
15 |
16 | # Source: Traveller 5 Second Survey
17 |
18 | # Subsector A: Apge
19 | # Subsector B: Perite
20 | # Subsector C: Ameros
21 | # Subsector D: Shinkan
22 | # Subsector E: Sanches
23 | # Subsector F: Mekee
24 | # Subsector G: Core
25 | # Subsector H: Kaskii
26 | # Subsector I: Bunkeria
27 | # Subsector J: Cemplas
28 | # Subsector K: Chant
29 | # Subsector L: Dingtra
30 | # Subsector M: Cadion
31 | # Subsector N: Ch'naar
32 | # Subsector O: Dunea
33 | # Subsector P: Saregon
34 |
35 | # Alleg: ImDc: "Third Imperium, Domain of Sylea"
36 | # Alleg: ImSy: "Third Imperium, Sylean Worlds"
37 |
38 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar
39 | ---- -------------------- --------- ------------------------------------- ------ ------- ------ ----- -- - --- -- ---- ---------------
40 | 2118 Capital A586A98-F Hi Cx (Syleans)5 Ht { 4 } (H9G+4) [AE5F] BEFG NW - 605 15 ImSy G2 V
41 | 2121 Affinity B98A661-B Ni Ri Wa O:2219 { 2 } (C56-2) [2817] BC - - 213 10 ImDc G1 V
42 | 2521 Shashuua E560463-7 De Ni O:2320 { -3 } (631-5) [1124] B - - 201 13 ImDc K4 V
43 | 2922 Ekugush B652A68-A Hi Po O:3021 { 3 } (G9D+3) [AD5A] BE - - 904 11 ImDc M1 V M9 V
44 |
--------------------------------------------------------------------------------
/Tests/testUniqueSectors.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Jan 12, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | import codecs
7 | import os
8 | import tempfile
9 |
10 | from Tests.baseTest import baseTest
11 | from pytest_console_scripts import ScriptRunner
12 |
13 |
14 | class testUniqueSectors(baseTest):
15 |
16 | def testSectorListIsUnique(self) -> None:
17 | fullpath = self.unpack_filename('../PyRoute/unique_sectors.py')
18 |
19 | cases = [
20 | ("sectorlist.txt", "../sectorlist.txt"),
21 | ("sectorimplist.txt", "../sectorimplist.txt")
22 | ]
23 |
24 | for filename, fullname in cases:
25 | with self.subTest(filename):
26 | with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8") as outfile:
27 | srcfile = self.unpack_filename(fullname)
28 |
29 | cwd = os.getcwd()
30 | runner = ScriptRunner(launch_mode="subprocess", rootdir=cwd, print_result=False)
31 |
32 | foo = runner.run([fullpath, "--infile", srcfile, "--outfile", outfile.name])
33 | self.assertEqual(0, foo.returncode, "unique_sectors did not complete successfully: " + foo.stderr)
34 |
35 | inlines = []
36 | with codecs.open(srcfile, mode="r", encoding="utf-8") as infile:
37 | inlines = [line for line in infile]
38 |
39 | outlines = []
40 | with codecs.open(outfile.name, mode="r", encoding="utf-8") as outfile_foo:
41 | outlines = [line for line in outfile_foo]
42 |
43 | self.assertEqual(len(outlines), len(inlines), filename + " has at least one duplicate")
44 | self.assertEqual(outlines, inlines, "Lines in " + filename + " not correctly sorted")
45 |
--------------------------------------------------------------------------------
/PyRoute/Pathfinding/RouteLandmarkGraph.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Feb 25, 2024
3 |
4 | @author: CyberiaResurrection
5 | """
6 | import functools
7 | import numpy as np
8 |
9 | from PyRoute.Pathfinding.DistanceBase import DistanceBase
10 |
11 |
12 | class RouteLandmarkGraph(DistanceBase):
13 |
14 | def __init__(self, graph):
15 | super().__init__(graph)
16 | self._arcs = [
17 | (np.array([], dtype=int), np.array([], dtype=float), dict())
18 | for u in self._nodes
19 | ]
20 |
21 | def __getitem__(self, item):
22 | self._check_index(item)
23 | return self._arcs[item]
24 |
25 | def add_edge(self, u, v, weight) -> None:
26 | self._check_index(u)
27 | self._check_index(v)
28 | self._extend_arc(u, v, weight)
29 | self._extend_arc(v, u, weight)
30 |
31 | def _extend_arc(self, u, v, weight):
32 | arcs = self._arcs[u]
33 | u_dict = arcs[2]
34 | if v not in u_dict:
35 | u_dict[v] = len(u_dict)
36 | u_first = np.append(arcs[0], [v], 0)
37 | u_last = np.append(arcs[1], [weight], 0)
38 | self._arcs[u] = (u_first, u_last, u_dict)
39 | else:
40 | self._lighten_arc(u, v, weight)
41 |
42 | @functools.cache
43 | def _check_index(self, item):
44 | if not isinstance(item, int):
45 | raise IndexError("Index must be integer between 0 and " + str(len(self) - 1))
46 | if 0 > item:
47 | raise IndexError("Index must be integer between 0 and " + str(len(self) - 1))
48 | if len(self) <= item:
49 | raise IndexError("Index must be integer between 0 and " + str(len(self) - 1))
50 |
51 | def lighten_edge(self, u, v, weight) -> None:
52 | self._lighten_arc(u, v, weight)
53 | self._lighten_arc(v, u, weight)
54 |
--------------------------------------------------------------------------------
/PyRoute/SystemData/Utilities.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Nov 21, 2023
3 |
4 | Supporting utilities needed by various SystemData components
5 |
6 | @author: CyberiaResurrection
7 | """
8 |
9 |
10 | class Utilities:
11 |
12 | tax_rate = {'0': 0.50, '1': 0.8, '2': 1.0, '3': 0.9, '4': 0.85,
13 | '5': 0.95, '6': 1.0, '7': 1.0, '8': 1.1, '9': 1.15,
14 | 'A': 1.20, 'B': 1.1, 'C': 1.2, 'D': 0.75, 'E': 0.75,
15 | 'F': 0.75,
16 | # Aslan Government codes
17 | 'G': 1.0, 'H': 1.0, 'J': 1.2, 'K': 1.1, 'L': 1.0,
18 | 'M': 1.1, 'N': 1.2,
19 | # Unknown Gov Codes
20 | 'I': 1.0, 'P': 1.0, 'Q': 1.0, 'R': 1.0, 'S': 1.0, 'T': 1.0,
21 | '': 1.0, 'U': 1.0, 'V': 1.0, 'W': 1.0, 'X': 1.0, '?': 0.0
22 | }
23 |
24 | @staticmethod
25 | def ehex_to_int(value) -> int:
26 | if not isinstance(value, str):
27 | raise ValueError("Value must be string")
28 | val = int(value, 36) if value in '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' else 0
29 | val -= 1 if val > 18 else 0
30 | val -= 1 if val > 22 else 0
31 | return val
32 |
33 | @staticmethod
34 | def int_to_ehex(value: int) -> str:
35 | if not isinstance(value, int):
36 | raise ValueError("Value must be integer")
37 | if 10 > value:
38 | return str(value)
39 | # Ehex doesn't use I, as it's too easily confused with the numeric 1, likewise with 0 and O
40 | if 9 < value < 18:
41 | valstring = 'ABCDEFGH'
42 | return valstring[value - 10]
43 | if 17 < value:
44 | valstring = 'JKLMNOPQRSTUVWXYZ'
45 | value += 1 if 22 < value else 0
46 | return valstring[value - 18]
47 | assert False # pragma: no mutate
48 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/Passes/Dagudashaag-auxiliary-reduction-two-lines.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2021-11-28T10:21:52-08:00
3 |
4 | # Dagudashaag
5 | # -1,0
6 |
7 | # Name: Dagudashaag
8 | # Name: Dagudashag
9 |
10 | # Abbreviation: Dagu
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: Dagudashaag sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by Duncan Law-Green, Leighton Piper and Jae Campbell, and appears in Signal GK Magazine.
15 |
16 | # Author: Duncan Law-Green, Leighton Piper and Jae Campbell; Martin J. Dougherty
17 | # Source: Traveller 5 Second Survey
18 |
19 | # Subsector A: Mimu
20 | # Subsector B: Old Suns
21 | # Subsector C: Arnakhish
22 | # Subsector D: Iiradu
23 | # Subsector E: Shallows
24 | # Subsector F: Ushra
25 | # Subsector G: Khandi
26 | # Subsector H: Kuriishe
27 | # Subsector I: Zeda
28 | # Subsector J: Remnants
29 | # Subsector K: Pact
30 | # Subsector L: Gadde
31 | # Subsector M: Bolivar
32 | # Subsector N: Argi
33 | # Subsector O: Sapphyre
34 | # Subsector P: Laraa
35 |
36 | # Alleg: ImAp: "Third Imperium, Amec Protectorate"
37 | # Alleg: ImDv: "Third Imperium, Domain of Vland"
38 | # Alleg: ImLc: "Third Imperium, Lancian Cultural Region"
39 |
40 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes
41 | ---- -------------------- --------- ------------------------------------- ------ ------- ------ ---- -- - --- -- ---- --------------- -----------------------------------------
42 | 2123 Medurma A9D7954-C An Asla1 Cs Di(Miyavine) Hi S'mr0 { 3 } (G8E+1) [7C3A] BEF - - 823 12 ImDv G0 V Xb:1823 Xb:1926 Xb:2223 Xb:2225 Xb:2322
43 | 2123 Kediiga C778411-8 { -2 } (832-5) [1314] - - 100 0 ImDv G6 V
44 |
--------------------------------------------------------------------------------
/Tests/Pathfinding/Schemas/testLandmarksExtremesRegression.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on 05 Sep, 2024
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.AreaItems.Galaxy import Galaxy
7 | from PyRoute.DataClasses.ReadSectorOptions import ReadSectorOptions
8 | from PyRoute.Pathfinding.LandmarkSchemes.LandmarksTriaxialExtremes import LandmarksTriaxialExtremes
9 | from PyRoute.Inputs.ParseStarInput import ParseStarInput
10 | from Tests.baseTest import baseTest
11 |
12 |
13 | class testLandmarksExtremesRegression(baseTest):
14 |
15 | def setUp(self) -> None:
16 | ParseStarInput.deep_space = {}
17 |
18 | def testZeroArgsInTransposeLandmarks(self) -> None:
19 | sourcefile = self.unpack_filename('../DeltaFiles/zero_arg_in_transpose_landmarks/Bar\'kakr.sec')
20 |
21 | args = self._make_args()
22 | args.route_btn = 15
23 | readparms = ReadSectorOptions(sectors=[sourcefile], pop_code=args.pop_code, ru_calc=args.ru_calc,
24 | route_reuse=args.route_reuse, trade_choice=args.routes, route_btn=args.route_btn,
25 | mp_threads=args.mp_threads, debug_flag=args.debug_flag, fix_pop=False,
26 | deep_space={}, map_type=args.map_type)
27 |
28 | galaxy = Galaxy(min_btn=15, max_jump=4)
29 | galaxy.read_sectors(readparms)
30 | galaxy.output_path = args.output
31 |
32 | galaxy.generate_routes()
33 | galaxy.trade.calculate_components()
34 |
35 | btn = [(s, n, d) for (s, n, d) in galaxy.ranges.edges(data=True)]
36 | btn.sort(key=lambda tn: tn[2]['btn'], reverse=True)
37 |
38 | foo = LandmarksTriaxialExtremes(galaxy)
39 | expected = [
40 | {0: 56}, {0: 95}, {0: 43}, {0: 59}, {0: 0}
41 | ]
42 | actual, _ = foo.get_landmarks(index=True, btn=btn)
43 | self.assertEqual(expected, actual)
44 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.ruff]
2 | # Enable the pycodestyle (`E`) and Pyflakes (`F`) rules by default.
3 | # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
4 | # McCabe complexity (`C901`) by default.
5 | lint.select = ["E", "F", "TID252", "NPY201", "W", "PLE", "PLW", "B007", "SIM", "ANN201"]
6 | lint.ignore = ["E501", "SIM113", "SIM300"]
7 |
8 | # Allow autofix for all enabled rules (when `--fix`) is provided.
9 | lint.fixable = ["ALL"]
10 | lint.unfixable = []
11 |
12 | # Specify github format
13 | output-format = "github"
14 |
15 | # Exclude a variety of commonly ignored directories.
16 | exclude = [
17 | ".bzr",
18 | ".direnv",
19 | ".eggs",
20 | ".git",
21 | ".git-rewrite",
22 | ".hg",
23 | ".mypy_cache",
24 | ".nox",
25 | ".pants.d",
26 | ".pytype",
27 | ".ruff_cache",
28 | ".svn",
29 | ".tox",
30 | ".venv",
31 | "__pypackages__",
32 | "_build",
33 | "buck-out",
34 | "build",
35 | "dist",
36 | "node_modules",
37 | "venv",
38 | ]
39 | lint.per-file-ignores = {'__init__.py' = ['F822'], 'PyRoute/Calculation/TradeMPCalculation.py' = ['PLW0602', 'PLW0603']}
40 |
41 | # Same as Black.
42 | line-length = 120
43 |
44 | # Allow unused variables when underscore-prefixed.
45 | lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
46 |
47 | # Assume Python 3.9
48 | target-version = "py39"
49 |
50 | [tool.mypy]
51 | warn_unused_configs = true
52 | warn_redundant_casts = true
53 | warn_unused_ignores = true
54 |
55 | follow_imports = "silent"
56 |
57 | # Assume Python 3.9
58 | python_version = "3.9"
59 |
60 |
61 | [tool.coverage.run]
62 | source = ["PyRoute/"]
63 |
64 | [tool.mutmut]
65 | paths_to_mutate = ["PyRoute/"]
66 | do_not_mutate = ["PyRoute/downloadsec.py"]
67 | tests_dir = ["Tests/"]
68 | also_copy = ["Tests/", "PyRoute/Pathfinding/*.so", "pytest.ini"]
69 | max_stack_depth = 20
70 | pytest_add_cli_args_test_selection = ['--ignore=Tests/Hypothesis/']
71 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/dagudashaag-allegiance-pax-balance/Dagudashaag-delta-error.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2021-11-28T10:21:52-08:00
3 |
4 | # Dagudashaag
5 | # -1,0
6 |
7 | # Name: Dagudashaag
8 | # Name: Dagudashag
9 |
10 | # Abbreviation: Dagu
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: Dagudashaag sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by Duncan Law-Green, Leighton Piper and Jae Campbell, and appears in Signal GK Magazine.
15 |
16 | # Author: Duncan Law-Green, Leighton Piper and Jae Campbell; Martin J. Dougherty
17 | # Source: Traveller 5 Second Survey
18 |
19 | # Subsector A: Mimu
20 | # Subsector B: Old Suns
21 | # Subsector C: Arnakhish
22 | # Subsector D: Iiradu
23 | # Subsector E: Shallows
24 | # Subsector F: Ushra
25 | # Subsector G: Khandi
26 | # Subsector H: Kuriishe
27 | # Subsector I: Zeda
28 | # Subsector J: Remnants
29 | # Subsector K: Pact
30 | # Subsector L: Gadde
31 | # Subsector M: Bolivar
32 | # Subsector N: Argi
33 | # Subsector O: Sapphyre
34 | # Subsector P: Laraa
35 |
36 | # Alleg: ImAp: "Third Imperium, Amec Protectorate"
37 | # Alleg: ImDv: "Third Imperium, Domain of Vland"
38 | # Alleg: ImLc: "Third Imperium, Lancian Cultural Region"
39 |
40 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes
41 | ---- -------------------- --------- ------------------------------------- ------ ------- ------ ---- -- - --- -- ---- --------------- -----------------------------------------
42 | 3014 Kherse A200536-D Ni Va { 2 } (746+1) [474C] B NS - 500 7 ImDv M0 II Xb:2615 Xb:3111 Xb:3114 Xb:3115
43 | 3015 Akhashmaas D786498-7 Ni Ga Pa { -3 } (631-3) [4157] Bc - - 423 8 ImDv G1 V
44 | 3016 Larmai C797534-8 Ag Ni { -1 } (B43-3) [3436] BC - - 704 9 ImDv M0 V
45 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/xroute_routes_pass_3_4/Dagudashaag.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2021-11-28T10:21:52-08:00
3 |
4 | # Dagudashaag
5 | # -1,0
6 |
7 | # Name: Dagudashaag
8 | # Name: Dagudashag
9 |
10 | # Abbreviation: Dagu
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: Dagudashaag sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by Duncan Law-Green, Leighton Piper and Jae Campbell, and appears in Signal GK Magazine.
15 |
16 | # Author: Duncan Law-Green, Leighton Piper and Jae Campbell; Martin J. Dougherty
17 | # Source: Traveller 5 Second Survey
18 |
19 | # Subsector A: Mimu
20 | # Subsector B: Old Suns
21 | # Subsector C: Arnakhish
22 | # Subsector D: Iiradu
23 | # Subsector E: Shallows
24 | # Subsector F: Ushra
25 | # Subsector G: Khandi
26 | # Subsector H: Kuriishe
27 | # Subsector I: Zeda
28 | # Subsector J: Remnants
29 | # Subsector K: Pact
30 | # Subsector L: Gadde
31 | # Subsector M: Bolivar
32 | # Subsector N: Argi
33 | # Subsector O: Sapphyre
34 | # Subsector P: Laraa
35 |
36 | # Alleg: ImAp: "Third Imperium, Amec Protectorate"
37 | # Alleg: ImDv: "Third Imperium, Domain of Vland"
38 | # Alleg: ImLc: "Third Imperium, Lancian Cultural Region"
39 |
40 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes
41 | ---- -------------------- --------- ------------------------------------- ------ ------- ------ ---- -- - --- -- ---- --------------- -----------------------------------------
42 | 1016 Ushra B561766-C Pr Mr Ht { 3 } (E6F+3) [8A4B] BcEF W - 703 13 ImDc M3 V
43 | 2124 Medurma A9D7954-C Hi An Cs Di(Miyavine) Asla1 S'mr0 { 3 } (G8E+1) [7C3A] BEF - - 823 12 ImDv G0 V Xb:1823 Xb:1926 Xb:2223 Xb:2225 Xb:2322
44 | 3121 Depot A31046A-F Ni Da Mr { 2 } (735+4) [667H] B D A 310 13 ImDv M1 V
45 |
--------------------------------------------------------------------------------
/Tests/BorderGeneration/Far Frontiers-partial-allymap-border.json:
--------------------------------------------------------------------------------
1 | {"(-175, 138)": ["white", null, null], "(-173, 137)": ["white", "white", "white"], "(-174, 137)": ["white", "white", null], "(-173, 141)": ["white", null, null], "(-173, 138)": ["white", null, null], "(-172, 137)": [null, "white", "white"], "(-175, 142)": ["white", "white", "white"], "(-175, 143)": [null, null, "white"], "(-176, 144)": ["white", "white", null], "(-176, 141)": ["white", "white", null], "(-175, 140)": ["white", null, "white"], "(-174, 140)": [null, "white", "white"], "(-174, 141)": ["white", "white", "white"], "(-176, 138)": ["white", null, "white"], "(-175, 137)": [null, "white", null], "(-177, 138)": ["white", null, "white"], "(-173, 136)": ["white", null, "white"], "(-172, 136)": ["white", null, "white"], "(-171, 136)": [null, "white", "white"], "(-171, 135)": [null, "white", null], "(-177, 142)": ["white", null, null], "(-177, 141)": [null, "white", null], "(-178, 142)": ["white", null, "white"], "(-178, 141)": [null, "white", "white"], "(-178, 140)": [null, "white", "white"], "(-178, 139)": ["white", "white", null], "(-173, 140)": [null, "white", null], "(-172, 140)": ["white", "white", null], "(-170, 140)": ["white", null, "white"], "(-177, 146)": [null, "white", "white"], "(-177, 145)": ["white", "white", "white"], "(-171, 137)": ["white", null, null], "(-170, 137)": [null, "white", "white"], "(-171, 139)": ["white", null, "white"], "(-170, 138)": [null, "white", "white"], "(-170, 139)": [null, "white", "white"], "(-169, 141)": [null, "white", "white"], "(-169, 140)": [null, "white", "white"], "(-169, 142)": [null, "white", "white"], "(-169, 143)": [null, "white", "white"], "(-170, 145)": ["white", "white", null], "(-169, 144)": [null, null, "white"], "(-171, 146)": ["white", null, "white"], "(-172, 147)": ["white", "white", null], "(-173, 148)": ["white", null, "white"], "(-169, 139)": [null, "white", null], "(-174, 149)": ["white", null, "white"], "(-175, 149)": ["white", null, null], "(-175, 148)": [null, "white", null], "(-176, 149)": ["white", null, "white"], "(-177, 149)": ["white", null, null], "(-177, 148)": [null, "white", "white"], "(-177, 147)": [null, "white", "white"]}
--------------------------------------------------------------------------------
/PyRoute/Outputs/Cursor.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Sep 12, 2023
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from typing import Union
7 | from typing_extensions import Self
8 |
9 |
10 | class Cursor(object):
11 |
12 | def __init__(self, x, y):
13 | self._x = x
14 | self._y = y
15 | self.dx = 0
16 | self.dy = 0
17 |
18 | def __eq__(self, other):
19 | return self._x == other._x and self._y == other._y
20 |
21 | def __str__(self):
22 | return f"{self._x}, {self._y}"
23 |
24 | def __hash__(self):
25 | return hash((self.x, self.y))
26 |
27 | def set_deltas(self, dx=2, dy=2) -> None:
28 | self.dx = dx
29 | self.dy = dy
30 |
31 | @property
32 | def x(self) -> int:
33 | return self._x
34 |
35 | @x.setter
36 | def x(self, value=0) -> None:
37 | # If in left margin, sets to minimum value.
38 | self._x = value
39 |
40 | @property
41 | def y(self) -> int:
42 | return self._y
43 |
44 | @y.setter
45 | def y(self, value=0) -> None:
46 | self._y = value
47 |
48 | # Changes this cursor
49 | def x_plus(self, dx=None) -> None:
50 | """
51 | Mutable x addition. Defaults to set delta value.
52 | """
53 | self._x += dx if dx is not None else self.dx
54 |
55 | def y_plus(self, dy=None) -> None:
56 | """
57 | Mutable y addition. Defaults to set delta value.
58 | """
59 | self._y += dy if dy is not None else self.dy
60 |
61 | def copy(self) -> Self:
62 | new_cursor = self.__class__(self.x, self.y)
63 | new_cursor.set_deltas(self.dx, self.dy)
64 | return new_cursor
65 |
66 | def as_tuple(self) -> tuple[int, int]:
67 | """
68 | Express x,y co-ordinates as a 2-element tuple
69 | """
70 | return self.x, self.y
71 |
72 | def scaled_tuple(self, scale: float, rounding: bool) -> tuple[Union[int, float], Union[int, float]]:
73 | if rounding:
74 | return int(self.x * scale), int(self.y * scale)
75 | return float(self.x * scale), float(self.y * scale)
76 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/xroute_routes_pass_1_2/Diaspora.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2024-09-28T17:12:48-07:00
3 |
4 | # Diaspora
5 | # 0,-2
6 |
7 | # Name: Diaspora
8 | # Name: Nakulakak (vi)
9 |
10 | # Abbreviation: Dias
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: Diaspora sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by Mark Gelinas Sr. and appears in Astrogators' Guide to Diaspora Sector (GDW, 1992).
15 |
16 | # Source: Traveller 5 Second Survey
17 |
18 | # Subsector A: Narquel
19 | # Subsector B: Libert
20 | # Subsector C: Sufren
21 | # Subsector D: Khavle
22 | # Subsector E: Shadigi
23 | # Subsector F: Kushga
24 | # Subsector G: Alurza
25 | # Subsector H: Pasdaruu
26 | # Subsector I: Ebasha
27 | # Subsector J: Iusea
28 | # Subsector K: The Blight
29 | # Subsector L: Promise
30 | # Subsector M: Hijiri
31 | # Subsector N: Shumisdi
32 | # Subsector O: Madoc
33 | # Subsector P: Khulam
34 |
35 | # Alleg: ImDs: "Third Imperium, Domain of Sol"
36 |
37 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar
38 | ---- -------------------- --------- ------------------------------ ------ ------- ------ ----- -- - --- -- ---- --------------
39 | 1109 Libert A3109BC-F Hi In Na Cs Pz (Liberts) Ht { 4 } (D8G+5) [CD8J] BEF - A 102 6 ImDs K8 III
40 | 1510 Cayene D425338-9 Lo { -2 } (720-2) [3159] B - - 520 10 ImDs K1 V
41 | 1807 Sturgis A544203-E Lo Ht { 1 } (611-2) [132B] B N - 102 9 ImDs K0 V
42 | 2209 Backman B55497B-C Hi Ht { 3 } (E8E+5) [BC7E] BE N - 103 13 ImDs K0 V M0 V
43 | 2609 Shareduu E431574-9 Ni Po Da { -2 } (A42-4) [3337] B - A 121 7 ImDs M2 V
44 | 2807 Blanket C424578-B Ni { 0 } (844+1) [555B] B S - 601 11 ImDs M1 V M3 V
45 | 2906 Dordogne A554578-D Ag Ni Ht { 2 } (B46+2) [575D] BC - - 404 11 ImDs M3 V
46 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/Passes/Dagudashaag-subsector-full-reduce.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2021-11-28T10:21:52-08:00
3 |
4 | # Dagudashaag
5 | # -1,0
6 |
7 | # Name: Dagudashaag
8 | # Name: Dagudashag
9 |
10 | # Abbreviation: Dagu
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: Dagudashaag sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by Duncan Law-Green, Leighton Piper and Jae Campbell, and appears in Signal GK Magazine.
15 |
16 | # Author: Duncan Law-Green, Leighton Piper and Jae Campbell; Martin J. Dougherty
17 | # Source: Traveller 5 Second Survey
18 |
19 | # Subsector A: Mimu
20 | # Subsector B: Old Suns
21 | # Subsector C: Arnakhish
22 | # Subsector D: Iiradu
23 | # Subsector E: Shallows
24 | # Subsector F: Ushra
25 | # Subsector G: Khandi
26 | # Subsector H: Kuriishe
27 | # Subsector I: Zeda
28 | # Subsector J: Remnants
29 | # Subsector K: Pact
30 | # Subsector L: Gadde
31 | # Subsector M: Bolivar
32 | # Subsector N: Argi
33 | # Subsector O: Sapphyre
34 | # Subsector P: Laraa
35 |
36 | # Alleg: ImAp: "Third Imperium, Amec Protectorate"
37 | # Alleg: ImDv: "Third Imperium, Domain of Vland"
38 | # Alleg: ImLc: "Third Imperium, Lancian Cultural Region"
39 |
40 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes
41 | ---- -------------------- --------- ------------------------------------- ------ ------- ------ ---- -- - --- -- ---- --------------- -----------------------------------------
42 | 1722 Campbell B99A200-E Lo Wa { 2 } (812-2) [1419] B W - 204 7 ImDv M1 V Xb:1420 Xb:1823 Xb:2020
43 | 2123 Kediiga B778411-8 Ni Pa { -1 } (832-5) [1314] Bc - - 920 9 ImDv G6 V
44 | 2124 Medurma A9D7954-C Hi An Cs Di(Miyavine) Asla1 S'mr0 { 3 } (G8E+1) [7C3A] BEF - - 823 12 ImDv G0 V Xb:1823 Xb:1926 Xb:2223 Xb:2225 Xb:2322
45 | 2123 Medurma A9D7954-C Hi An Cs Di(Miyavine) Asla1 S'mr0 { 3 } (G8E+1) [7C3A] BEF - - 823 12 ImDv G0 V Xb:1823 Xb:1926 Xb:2223 Xb:2225 Xb:2322
46 |
--------------------------------------------------------------------------------
/PyRoute/unique_sectors.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | """
3 | Created on Jan 12, 2025
4 |
5 | @author: CyberiaResurrection
6 | """
7 |
8 | import argparse
9 | import logging
10 | import codecs
11 |
12 | logger = logging.getLogger('PyRoute')
13 |
14 |
15 | def process() -> None:
16 | parser = argparse.ArgumentParser(
17 | description='Traveller trade route sector list deduplicator.'
18 | )
19 | parser.add_argument(
20 | '--infile',
21 | help='file with list of sector names to process',
22 | default="sectorlist.txt"
23 | )
24 | parser.add_argument(
25 | '--outfile',
26 | help='file to write deduplicated list of sector names to',
27 | default="sectorlist_uniq.txt"
28 | )
29 | parser.add_argument(
30 | '--log-level',
31 | default='INFO'
32 | )
33 |
34 | args = parser.parse_args()
35 | set_logging(args.log_level)
36 |
37 | srcfile = args.infile
38 | outfile = args.outfile
39 | if outfile is None:
40 | outfile = srcfile
41 |
42 | # read srcfile in, line by line:
43 | try:
44 | with codecs.open(srcfile, 'r', encoding="utf-8") as infile:
45 | lines = [line for line in infile]
46 | except FileNotFoundError as e:
47 | logger.error("Sector file " + srcfile + " not found. Cannot continue.")
48 | raise e
49 |
50 | line_set = set(lines)
51 |
52 | lines = list(line_set)
53 | lines.sort()
54 |
55 | try:
56 | with codecs.open(outfile, "w", encoding="utf-8") as targfile:
57 | targfile.writelines(lines)
58 | except Exception as e:
59 | logger.error("Deduplicated sector file " + outfile + " could not be created. Cannot continue")
60 | raise e
61 |
62 | logger.info("process complete")
63 |
64 |
65 | def set_logging(level) -> None:
66 | logger.setLevel(level)
67 | formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
68 | # create console handler and set level to debug
69 | ch = logging.StreamHandler()
70 | ch.setLevel(level)
71 | ch.setFormatter(formatter)
72 | logger.addHandler(ch)
73 |
74 |
75 | if __name__ == '__main__':
76 | process()
77 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/xroute_routes_pass_3_2/Zarushagar.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2021-11-28T10:37:26-08:00
3 |
4 | # Zarushagar
5 | # -1,-1
6 |
7 | # Name: Zarushagar
8 |
9 | # Abbreviation: Zaru
10 |
11 | # Milieu: M1105
12 |
13 | # Credits: Zarushagar sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by Greg Videll. Portions appear in The Travellers Digest and the MegaTraveller Referee's Gaming Kit (Digest Group Publications, 1989).
14 |
15 | # Author: Greg Videll, Martin J. Dougherty
16 | # Source: Traveller 5 Second Survey
17 |
18 | # Subsector A: Ibaru
19 | # Subsector B: Triton
20 | # Subsector C: Lagaar
21 | # Subsector D: Lode
22 | # Subsector E: Iimii
23 | # Subsector F: Khipge
24 | # Subsector G: Epshu
25 | # Subsector H: Gaussi
26 | # Subsector I: Mizar
27 | # Subsector J: Liasdi
28 | # Subsector K: Wolf
29 | # Subsector L: Oasis
30 | # Subsector M: Void
31 | # Subsector N: Allegro
32 | # Subsector O: Rhamsteppe
33 | # Subsector P: Mongrabi
34 |
35 | # Alleg: CsIm: "Client state, Third Imperium"
36 | # Alleg: ImDi: "Third Imperium, Domain of Ilelish"
37 | # Alleg: NaHu: "Non-Aligned, Human-dominated"
38 |
39 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes
40 | ---- -------------------- --------- -------------------------- ------ ------- ------ ----- -- - --- -- ---- -------------- ----------------------------------------------
41 | 0621 Lenox C855423-9 Ni Ga Pa { 0 } (C33-3) [1426] Bc W - 924 9 ImDi M3 V M9 V Xb:0220 Xb:0724
42 | 0928 Liasdi A887944-C Hi Ga Cs Pr Darm7 { 3 } (F8E+1) [7C3A] BcEF - - 404 12 ImDi F8 V Xb:0628 Xb:1226
43 | 1326 Driuden B6676AA-A Ag Ni Ga Ri Da Darm7 { 3 } (A57+5) [897C] BC N A 911 11 ImDi G7 V M3 V
44 | 1727 Lianma A7329A7-F Hi Na Po Cp Darm8 { 3 } (F8F+3) [9C5F] BEF - - 204 13 ImDi F0 V Xb:1528
45 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/xroute_routes_pass_1_4/Delphi.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2024-09-28T17:12:18-07:00
3 |
4 | # Delphi
5 | # 1,-1
6 |
7 | # Name: Delphi
8 | # Name: Manadish Khurem (vi)
9 |
10 | # Abbreviation: Delp
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: Delphi sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984).
15 |
16 | # Source: Traveller 5 Second Survey
17 |
18 | # Subsector A: Kirkankhim
19 | # Subsector B: Tsent
20 | # Subsector C: Dighirpi
21 | # Subsector D: Forlorn
22 | # Subsector E: Zukhumi
23 | # Subsector F: Rifts Reach
24 | # Subsector G: Rayoci'Ailr
25 | # Subsector H: Tolanada
26 | # Subsector I: Aklan
27 | # Subsector J: Void
28 | # Subsector K: Anaxias
29 | # Subsector L: Eduum
30 | # Subsector M: Eta-Gu
31 | # Subsector N: Riramla
32 | # Subsector O: Zuma
33 | # Subsector P: Breda
34 |
35 | # Alleg: ImDc: "Third Imperium, Domain of Sylea"
36 |
37 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar
38 | ---- -------------------- --------- -------------------- ------ ------- ------ ---- -- - --- -- ---- --------------
39 | 0129 Walden E687769-4 Ag Ga Ri O:0329 Lt { 0 } (966+1) [8765] BC - - 533 12 ImDc G1 V
40 | 0421 Withern C100963-B Hi In Na Va O:0623 { 3 } (E8E+1) [6C28] BE - - 603 7 ImDc M2 V M9 V
41 | 0528 Valli B562876-6 Ri Ph Pz { 1 } (A78+1) [7945] BCe - A 104 15 ImDc M1 V
42 | 0823 Herodotus A100435-F Ni Va Ht { 1 } (A34-1) [253D] B - - 604 10 ImDc M2 V
43 | 0929 Gabon C10088A-B Na Va Ph Pi { 1 } (E7B+3) [A97D] BDe - - 513 8 ImDc K3 V M4 V
44 | 1024 Toirdealbach B525677-7 Ni { -1 } (853-1) [6557] B - - 403 13 ImDc M0 V M4 V
45 | 1028 Edmonton C431721-A Na Po { 1 } (E6A-3) [3816] B - - 823 8 ImDc K3 V M0 V M0 V
46 | 1422 Barnet C897554-6 Ag Ni { -1 } (743-3) [3434] BC - - 204 10 ImDc K4 V M7 V
47 | 1724 Anaxias A553A85-D Hi Po Cs Ht { 3 } (C9F+1) [8D3B] BEF - - 900 11 ImDc K0 V M3 V
48 |
--------------------------------------------------------------------------------
/PyRoute/Inputs/StarlineParser.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Dec 27, 2023
3 |
4 | @author: CyberiaResurrection
5 | """
6 |
7 | from lark import Lark, Tree, Token
8 | import re
9 |
10 |
11 | def dashrepl(m) -> str:
12 | group = m.group().replace(' ', ' ')
13 | return group
14 |
15 |
16 | class StarlineParser:
17 |
18 | starline_grammar = r"""
19 | starline: position starname trade extensions nbz world_alg residual?
20 |
21 | position: /^((?:0[1-9]|[1-2]\d|3[0-2])(?:0[1-9]|40|[1-3]\d))/
22 |
23 | starname: /(.{15,}) ([A-HXYa-hxy\?][0-9A-Fa-f\?][\w\?]{2,2}[0-9A-Fa-f\?][0-9A-Xa-x\?][0-9A-Ka-k\?]-[\w\?]) /
24 |
25 | trade: BLANK | TRADECODE*
26 | TRADECODE: MINOR_DIEBACK | BINARY | POPCODE | MINOR_SOPHONT | OWNED_COLONY | MAJOR_SOPHONT | RESIDUAL | SINGLETON
27 | BLANK: / /
28 | BINARY.3: /[A-Z][a-z]/
29 | POPCODE.3: /[A-Z][a-z!]{1,3}[W\d]{0,1}/
30 | MINOR_SOPHONT.3: /\([^\)\{\(]{1,}\)[WX\d\?]{0,1}/
31 | MINOR_DIEBACK.3: /Di\([^\)]{1,}\)[\d]{0,1}/
32 | MAJOR_SOPHONT.3: /\[[^\]\{]{1,}\][WX\d\?]{0,1}/
33 | OWNED_COLONY.3: /[OC]:[X\d\?]{0,4}/ | /[OC]:[A-Z][A-Za-z]{3,3}[-\:]{0,1}\d{4,4}/
34 | RESIDUAL.2: /[0-9A-Za-z?\-+*()\'\{\}\[\]]{2,}/
35 | SINGLETON: /[0-9AC-Za-z\+\*()?\']/
36 |
37 | extensions: ix ex cx | /( ) ( ) ( )/
38 |
39 | ix: /\{ *[ +-]?[0-6] ?\}|-/
40 | ex: /\([0-9A-Za-z]{3}[+-]\d\)|-/
41 | cx: /(\[[0-9A-Za-z]{4}[\]\}]|-)/
42 |
43 | nbz: nobles base zone
44 |
45 | nobles: /([BcCDeEfFGH]{1,5}|-| )/
46 |
47 | base: /([A-Z]{1,3}|-|\*) /
48 |
49 | zone: /([ARUFGBarufgb]|-| )[ ]{0,}/
50 |
51 | pbg: /[0-9X?][0-9A-FX?][0-9A-FX?] /
52 |
53 | residual: /(.{1,})/
54 |
55 | world_alg: pbg worlds allegiance
56 | worlds: /(\d{1,} | |- )/
57 |
58 | allegiance: /[A-Z0-9?-][A-Za-z0-9?-]{1,3}/
59 |
60 | %import common.ESCAPED_STRING
61 | %import common.SIGNED_NUMBER
62 | %import common.WS
63 | %ignore WS
64 |
65 | """
66 |
67 | def __init__(self):
68 | self.parser = Lark(self.starline_grammar, start='starline')
69 |
70 | def parse(self, text: str) -> tuple[Tree[Token], str]:
71 | text = re.sub(r'[\w\-] [\w\-]', dashrepl, text)
72 |
73 | return self.parser.parse(text), text
74 |
--------------------------------------------------------------------------------
/PyRoute/Inputs/StarlineStationParser.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on May 20, 2024
3 |
4 | @author: CyberiaResurrection
5 | """
6 |
7 | from lark import Lark, Tree, Token
8 | import re
9 |
10 |
11 | def dashrepl(m) -> str:
12 | group = m.group().replace(' ', ' ')
13 | return group
14 |
15 |
16 | class StarlineStationParser:
17 |
18 | starline_grammar = r"""
19 | starline: position starname trade extensions nbz world_alg? residual?
20 |
21 | position: /^((?:0[1-9]|[1-2]\d|3[0-2])(?:0[1-9]|40|[1-3]\d))/
22 |
23 | starname: /(.{15,}) ([A-HXYa-hxy\?][0-9A-Fa-f\?][\w\?]{2,2}[0-9A-Fa-f\?][0-9A-Xa-x\?][0-9A-Ka-k\?]-[\w\?]) /
24 |
25 | trade: BLANK | TRADECODE*
26 | TRADECODE: MINOR_DIEBACK | BINARY | POPCODE | MINOR_SOPHONT | OWNED_COLONY | MAJOR_SOPHONT | RESIDUAL | SINGLETON
27 | BLANK: / /
28 | BINARY.3: /[A-Z][a-z]/
29 | POPCODE.3: /[A-Z][a-z!]{1,3}[W\d]{0,1}/
30 | MINOR_SOPHONT.3: /\([^\)\{\(]{1,}\)[WX\d\?]{0,1}/
31 | MINOR_DIEBACK.3: /Di\([^\)]{1,}\)[\d]{0,1}/
32 | MAJOR_SOPHONT.3: /\[[^\]\{]{1,}\][WX\d\?]{0,1}/
33 | OWNED_COLONY.3: /[OC]:[X\d\?]{0,4}/ | /[OC]:[A-Z][A-Za-z]{3,3}[-\:]{0,1}\d{4,4}/
34 | RESIDUAL.2: /[0-9A-Za-z?\-+*()\'\{\}\[\]]{2,}/
35 | SINGLETON: /[0-9AC-Za-z\+\*()?\']/
36 |
37 | extensions: ix ex cx | /( ) ( ) ( )/
38 |
39 | ix: /\{ *[ +-]?[0-6] ?\}/
40 | ex: /\([0-9A-Za-z]{3}[+-]\d\)|-/
41 | cx: /(\[[0-9A-Za-z]{4}[\]\}]|-)/
42 |
43 | nbz: nobles base zone
44 |
45 | nobles: /([BcCDeEfFGH]{1,5}|-| )/
46 |
47 | base: /([A-Z]{1,3}|-|\*) /
48 |
49 | zone: /([ARUFGBarufgb]|-| )[ ]{0,}/
50 |
51 | pbg: /[0-9X?][0-9A-FX?][0-9A-FX?] /
52 |
53 | residual: /(.{1,})/
54 |
55 | world_alg: pbg worlds allegiance
56 | worlds: /(\d{1,} | |- )/
57 |
58 | allegiance: / [A-Z0-9?-][A-Za-z0-9?-]{1,3}/
59 |
60 | %import common.ESCAPED_STRING
61 | %import common.SIGNED_NUMBER
62 | %import common.WS
63 | %ignore WS
64 |
65 | """
66 |
67 | def __init__(self):
68 | self.parser = Lark(self.starline_grammar, start='starline')
69 |
70 | def parse(self, text: str) -> tuple[Tree[Token], str]:
71 | text = re.sub(r'[\w\-] [\w\-]', dashrepl, text)
72 |
73 | return self.parser.parse(text), text
74 |
--------------------------------------------------------------------------------
/Tests/testAllyGen.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from PyRoute.Allies.AllyGen import AllyGen
4 |
5 |
6 | class testAllyGen(unittest.TestCase):
7 | def test_is_nonaligned_no_one(self) -> None:
8 | self.assertTrue(AllyGen.is_nonaligned('??'))
9 |
10 | def test_is_nonaligned_nadr(self) -> None:
11 | self.assertTrue(AllyGen.is_nonaligned('NaDr'))
12 |
13 | def test_is_nonaligned_nahu(self) -> None:
14 | self.assertTrue(AllyGen.is_nonaligned('NaHu'))
15 |
16 | def test_are_allies_one_none(self) -> None:
17 | self.assertFalse(AllyGen.are_allies(None, 'ImDd'))
18 |
19 | def test_are_allies_one_no_one(self) -> None:
20 | self.assertFalse(AllyGen.are_allies('--', 'ImDd'))
21 |
22 | def test_are_owned_allies_one_none(self) -> None:
23 | self.assertFalse(AllyGen.are_owned_allies(None, 'ImDd'))
24 |
25 | def test_are_owned_allies_one_no_one(self) -> None:
26 | self.assertFalse(AllyGen.are_owned_allies('--', 'ImDd'))
27 |
28 | def test_are_owned_allies_same_parent(self) -> None:
29 | self.assertTrue(AllyGen.are_owned_allies('ZhAx', 'ZhCa'))
30 |
31 | def test_are_owned_allies_diff_parent(self) -> None:
32 | self.assertFalse(AllyGen.are_owned_allies('ZhAx', 'HvFd'))
33 |
34 | def test_zhodani_in_name_of_population_alignment(self) -> None:
35 | expected = 'Zhod'
36 | actual = AllyGen.population_align('Na', 'Zhodani')
37 | self.assertEqual(expected, actual)
38 |
39 | def test_is_wilds_none(self) -> None:
40 | expected = False
41 | actual = AllyGen.is_wilds(None)
42 | self.assertEqual(expected, actual)
43 |
44 | def test_is_wilds_single_char(self) -> None:
45 | expected = False
46 | actual = AllyGen.is_wilds('a')
47 | self.assertEqual(expected, actual)
48 |
49 | def test_is_client_state_none(self) -> None:
50 | expected = False
51 | actual = AllyGen.is_client_state(None)
52 | self.assertEqual(expected, actual)
53 |
54 | def test_is_client_state_single_char(self) -> None:
55 | expected = False
56 | actual = AllyGen.is_client_state('a')
57 | self.assertEqual(expected, actual)
58 |
59 |
60 | if __name__ == '__main__':
61 | unittest.main()
62 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/xroute_routes_pass_1_2/Fornast.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2024-09-28T17:14:44-07:00
3 |
4 | # Fornast
5 | # 1,0
6 |
7 | # Name: Fornast
8 | # Name: Rishurir (vi)
9 |
10 | # Abbreviation: Forn
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: Fornast sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984).
15 |
16 | # Source: Traveller 5 Second Survey
17 |
18 | # Subsector A: Cirqa
19 | # Subsector B: Spas
20 | # Subsector C: Madu
21 | # Subsector D: Toza
22 | # Subsector E: AArna
23 | # Subsector F: Akashganar
24 | # Subsector G: Derri
25 | # Subsector H: Sanny
26 | # Subsector I: Imaka
27 | # Subsector J: Irinkuka
28 | # Subsector K: Dikhari
29 | # Subsector L: Nareshakir
30 | # Subsector M: Setten
31 | # Subsector N: Tharrynra
32 | # Subsector O: Shumduur
33 | # Subsector P: Zukhin
34 |
35 | # Alleg: ImDc: "Third Imperium, Domain of Sylea"
36 |
37 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar
38 | ---- -------------------- --------- ------------------------- ------ ------- ------ ----- -- - --- -- ---- --------------
39 | 0124 Hankel C654320-9 Lo { -1 } (720-5) [1214] B - - 502 10 ImDc G2 V M5 V
40 | 0526 Algenib C55466A-5 Ag Ni O:0527 Lt { -1 } (853+1) [8577] BC S - 323 8 ImDc G4 V
41 | 0928 Kikha Dur C100541-C Ni Va Ht { 0 } (A44-4) [1518] B - - 203 11 ImDc M0 V M6 V
42 | 1131 Mathieu A679346-E Lo Ht { 1 } (521+1) [244D] B - - 700 9 ImDc K6 V
43 | 1533 Gemam AA8A558-F Ni Oc Pr Ht { 1 } (745+1) [565F] Bc - - 500 10 ImDc K4 V M5 V
44 | 1933 Ketial A68578A-C Ag Ga Ri (Bhunj) Ht { 4 } (D6E+5) [9B7E] BCf - - 713 14 ImDc G5 V
45 | 2235 Irim A9979AB-E Hi In Pz Ht { 4 } (C8G+5) [BD7G] BEf - A 510 5 ImDc G4 V M1 V M0 V
46 | 2239 Blanchard C100566-B Ni Va O:2438 { 0 } (B44-1) [454A] B - - 122 12 ImDc G2 V
47 | 2334 Shumduur A5459AA-E Hi In Cs Pz Bhun0 Ht { 5 } (C8H+5) [BE7G] BEF W A 610 9 ImDc K3 V M6 V
48 |
--------------------------------------------------------------------------------
/Tests/Hypothesis/testTradeBalance.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Oct 19, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from hypothesis import given, assume, example, HealthCheck, settings
7 | from hypothesis.strategies import text, composite, sampled_from, lists, dictionaries, integers, tuples, SearchStrategy
8 |
9 | from PyRoute.AreaItems.Galaxy import Galaxy
10 | from PyRoute.AreaItems.Sector import Sector
11 | from PyRoute.TradeBalance import TradeBalance
12 | from Tests.baseTest import baseTest
13 |
14 |
15 | @composite
16 | def multi_dict(draw) -> SearchStrategy:
17 | sec_names = text(min_size=3, max_size=36, alphabet='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWYXZ -{}()[]?\'+*')
18 | sec_strat = lists(sec_names, min_size=2, unique=True)
19 | choices = draw(sec_strat)
20 | key_strat = tuples(sampled_from(choices), sampled_from(choices))
21 |
22 | multi_strat = dictionaries(keys=key_strat, values=integers(min_value=0, max_value=2000), min_size=2,
23 | max_size=len(choices) * (len(choices) - 1))
24 | multidict = draw(multi_strat)
25 |
26 | return multidict
27 |
28 |
29 | class testTradeBalance(baseTest):
30 |
31 | @given(multi_dict())
32 | @example({('000', '00'): 0, ('000', '0000'): 0})
33 | @example({('000', '001'): 0, ('000', '002'): 2})
34 | @example({('8cVH', 'idhC2-TNs'): 1312, ('hnjMin', 'idhC2-TNs'): 1397, ('idhC2-TNs', 'hnjMin'): 376})
35 | @settings(suppress_health_check=[HealthCheck(10)], deadline=1000)
36 | def test_multilateral_balance(self, s: dict) -> None:
37 | galaxy = Galaxy(8)
38 | for item in s:
39 | first = item[0]
40 | second = item[1]
41 | assume(first != second)
42 | if first not in galaxy.sectors:
43 | galaxy.sectors[first] = Sector('# ' + first, '# 0, 0')
44 | if second not in galaxy.sectors:
45 | galaxy.sectors[second] = Sector('# ' + second, '# 0, 0')
46 |
47 | foo = TradeBalance(stat_field='tradeExt', region=galaxy)
48 | foo.update(s)
49 | foo.multilateral_balance()
50 |
51 | act_dict = dict(foo)
52 | self.assertTrue(0 <= min(act_dict.values()), "Unexpected minimum value")
53 | self.assertTrue(1 >= max(act_dict.values()), "Unexpected maximum value")
54 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/Canonicalisation.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Jun 13, 2023
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaStar import DeltaStar
7 |
8 |
9 | class Canonicalisation(object):
10 |
11 | def __init__(self, reducer):
12 | self.reducer = reducer
13 |
14 | def preflight(self) -> bool:
15 | return self.reducer is not None and self.reducer.sectors is not None and 0 < len(self.reducer.sectors.lines)
16 |
17 | def run(self) -> None:
18 | # build substitution list - canonicalise _everything_
19 | subs_list = []
20 | for line in self.reducer.sectors.lines:
21 | canon = DeltaStar.reduce(line, canonicalise=True)
22 | assert isinstance(canon, str), "Candidate line " + line + " was not reduced to a string. Got " + canon + " instead."
23 | canon = DeltaStar.reduce(canon, canonicalise=True)
24 | subs_list.append((line, canon))
25 |
26 | if 0 == len(subs_list):
27 | # nothing to do, bail out early
28 | return
29 |
30 | temp_sectors = self.reducer.sectors.switch_lines(subs_list)
31 |
32 | interesting, msg, _ = self.reducer._check_interesting(self.reducer.args, temp_sectors)
33 |
34 | if interesting:
35 | new_hex = 'Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes \n'
36 | new_dash = '---- -------------------- --------- ------------------------------------- ------ ------- ------ ---- -- - --- -- ---- --------------- -----------------------------------------\n'
37 | for sec_name in temp_sectors:
38 | num_headers = len(temp_sectors[sec_name].headers)
39 | for i in range(0, num_headers):
40 | raw_line = temp_sectors[sec_name].headers[i]
41 | if raw_line.startswith('Hex '):
42 | temp_sectors[sec_name].headers[i] = new_hex
43 | elif raw_line.startswith('---- --'):
44 | temp_sectors[sec_name].headers[i] = new_dash
45 |
46 | self.reducer.sectors = temp_sectors
47 | msg = "Reduction found with full canonicalisation"
48 | self.reducer.logger.error(msg)
49 | return
50 |
--------------------------------------------------------------------------------
/PyRoute/Outputs/FontLayer.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Dec 04, 2021
3 |
4 | @author: CyberiaResurrection
5 |
6 | A dirt-simple adapter layer that used to abstract the differences in where Linux distros (currently, Ubuntu and Fedora)
7 | store their font files away from the rest of the project. Now, it still serves as a SPOT for font locations, even
8 | though they're now bundled with the project.
9 | """
10 |
11 | import os
12 | import functools
13 |
14 | from PyRoute.Utilities.UnpackFilename import UnpackFilename
15 |
16 |
17 | class FontLayer(object):
18 | fontdict: dict[str, list[str]] = {}
19 |
20 | def __init__(self):
21 | self.fontdict['DejaVuSerifCondensed.ttf'] = [
22 | UnpackFilename.unpack_filename('../PyRoute/Fonts/DejaVuSerifCondensed.ttf')
23 | ]
24 | self.fontdict['DejaVuSerifCondensed-Bold.ttf'] = [
25 | UnpackFilename.unpack_filename('../PyRoute/Fonts/DejaVuSerifCondensed-Bold.ttf')
26 | ]
27 | self.fontdict['LiberationMono-Bold.ttf'] = [
28 | UnpackFilename.unpack_filename('../PyRoute/Fonts/LiberationMono-Bold.ttf')
29 | ]
30 | self.fontdict['FreeMono.ttf'] = [
31 | UnpackFilename.unpack_filename('../PyRoute/Fonts/FreeMono.ttf')
32 | ]
33 | self.fontdict['Symbola-hint.ttf'] = [
34 | UnpackFilename.unpack_filename('../PyRoute/Fonts/Symbola-hint.ttf')
35 | ]
36 |
37 | self.fontdict['ZapfDingbats_Regular.ttf'] = [
38 | UnpackFilename.unpack_filename('../PyRoute/Fonts/ZapfDingbats-Regular.ttf')
39 | ]
40 |
41 | self.fontdict['ZapfDingbats-Regular.ttf'] = [
42 | UnpackFilename.unpack_filename('../PyRoute/Fonts/ZapfDingbats-Regular.ttf')
43 | ]
44 |
45 | @functools.cache
46 | def getpath(self, filename) -> str:
47 | # Deliberately lean on dictionaries blowing up on accessing absent keys to make it obvious
48 | # that we don't know how to handle what's being asked for
49 | filelist = self.fontdict[filename]
50 |
51 | for item in filelist:
52 | # for moment, don't care whether is a hard or softlinked file
53 | if os.path.exists(item) and not os.path.isdir(item):
54 | return item
55 |
56 | assert False, "Font mapping for " + filename + " not found. Tried " + " ".join(filelist)
57 |
--------------------------------------------------------------------------------
/Tests/Outputs/testCursor.py:
--------------------------------------------------------------------------------
1 |
2 | import unittest
3 |
4 | from PyRoute.Outputs.Cursor import Cursor
5 |
6 |
7 | class TestCursor(unittest.TestCase):
8 |
9 | def testSimple(self) -> None:
10 | cursor = Cursor(0, 0)
11 | self.assertEqual(cursor.x, 0)
12 | self.assertEqual(cursor.y, 0)
13 |
14 | def testdx(self) -> None:
15 | cursor = Cursor(1, 1)
16 | cursor.x_plus()
17 | self.assertEqual(cursor.x, 1)
18 | cursor.x_plus(1)
19 | self.assertEqual(cursor.x, 2)
20 |
21 | def testdy(self) -> None:
22 | cursor = Cursor(1, 1)
23 | cursor.y_plus()
24 | self.assertEqual(cursor.y, 1)
25 | cursor.y_plus(1)
26 | self.assertEqual(cursor.y, 2)
27 |
28 | def testSetDeltas(self) -> None:
29 | cursor = Cursor(1, 1)
30 | cursor.set_deltas()
31 | cursor.x_plus()
32 | self.assertEqual(cursor.x, 3)
33 | self.assertEqual(cursor.y, 1)
34 | cursor.y_plus()
35 | self.assertEqual(cursor.x, 3)
36 | self.assertEqual(cursor.y, 3)
37 |
38 | def testSetDeltaOverride(self) -> None:
39 | cursor = Cursor(1, 1)
40 | cursor.set_deltas(4, 4)
41 | cursor.x_plus()
42 | self.assertEqual(cursor.x, 5)
43 | self.assertEqual(cursor.y, 1)
44 | cursor.y_plus()
45 | self.assertEqual(cursor.x, 5)
46 | self.assertEqual(cursor.y, 5)
47 |
48 | def testString(self) -> None:
49 | cursor = Cursor(1, 1)
50 | self.assertEqual(str(cursor), "1, 1")
51 |
52 | def testEquals(self) -> None:
53 | cursor1 = Cursor(1, 1)
54 | cursor2 = Cursor(3, 3)
55 | self.assertFalse(cursor2 == cursor1)
56 | cursor1.x_plus(2)
57 | cursor1.y_plus(2)
58 | self.assertTrue(cursor2 == cursor1)
59 |
60 | def testAssignValues(self) -> None:
61 | cursor = Cursor(1, 1)
62 | cursor.x = -1
63 | self.assertEqual(cursor.x, -1)
64 | self.assertEqual(cursor.y, 1)
65 | cursor.y = -3
66 | self.assertEqual(cursor.x, -1)
67 | self.assertEqual(cursor.y, -3)
68 |
69 | def testCopy(self) -> None:
70 | cursor = Cursor(1, 1)
71 | cursor_new = cursor.copy()
72 |
73 | self.assertTrue(cursor == cursor_new)
74 |
75 |
76 | if __name__ == "__main__":
77 | unittest.main()
78 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/dijkstra_restart_blowup/Lishun.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2021-11-28T10:30:02-08:00
3 |
4 | # Lishun
5 | # 0,1
6 |
7 | # Name: Lishun
8 |
9 | # Abbreviation: Lish
10 |
11 | # Milieu: M1105
12 |
13 | # Credits: Lishun sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by Joe D. Fugate Sr., and portions appear in The Travellers Digest, The Flaming Eye (DGP, 1991) and Challenge Magazine.
14 |
15 | # Source: Traveller 5 Second Survey
16 |
17 | # Subsector A: Vakkuun
18 | # Subsector B: Adawi
19 | # Subsector C: Sotri
20 | # Subsector D: Criideu
21 | # Subsector E: Pryden
22 | # Subsector F: Masionia
23 | # Subsector G: Gama
24 | # Subsector H: Tephany
25 | # Subsector I: Shuna
26 | # Subsector J: Taccis
27 | # Subsector K: Simen
28 | # Subsector L: Ot Zell
29 | # Subsector M: Shuun
30 | # Subsector N: Welling
31 | # Subsector O: Strashna
32 | # Subsector P: Mirmida
33 |
34 | # Alleg: CsIm: "Client state, Third Imperium"
35 | # Alleg: ImDa: "Third Imperium, Domain of Antares"
36 | # Alleg: NaHu: "Non-Aligned, Human-dominated"
37 | # Alleg: NaVa: "Non-Aligned, Vargr-dominated"
38 | # Alleg: NaXX: "Non-Aligned, unclaimed"
39 |
40 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes
41 | ---- -------------------- --------- ------------------------ ------ ------- ------ ---- -- - --- -- ---- -------------- ------------------------------------
42 | 1134 Ushipaale A422264-C He Lo Po O:1033 { 1 } (611-1) [133A] B N - 920 10 ImDa M1 V Xb:1033 Xb:1037 Xb:1332
43 | 1136 Khusila B95A436-9 Ni Wa Da { 0 } (A33-1) [3448] B N A 304 11 ImDa M1 V
44 | 1232 Siidugam B654632-A Ag Ni { 2 } (856-2) [2816] BC - - 800 12 ImDa M3 V
45 | 1233 Ushmuur C100766-A Na Va Pi O:1033 { 1 } (96A+1) [6849] BD - - 200 7 ImDa K4 IV M0 V
46 | 1332 Welling C420973-C De He Hi In Na Po { 3 } (B8E+1) [6C29] BE S - 200 11 ImDa M1 V M3 V Xb:1134 Xb:1631
47 | 1333 Iigduur C574546-9 Ag Ni { 0 } (A44-1) [4548] BC S - 321 14 ImDa F6 V
48 | 1433 Chayn C637653-A Ni { 0 } (D54-3) [3627] B S - 505 15 ImDa M3 V
49 |
50 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/xroute_calculation_blowups/Antares-Sakhag-Celebes.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2021-11-28T10:20:28-08:00
3 |
4 | # Antares
5 | # 1,1
6 |
7 | # Name: Antares
8 | # Name: Mikasirka (vi)
9 |
10 | # Abbreviation: Anta
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: Antares sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984).
15 |
16 | # Source: Traveller 5 Second Survey
17 |
18 | # Subsector A: Pelusium
19 | # Subsector B: Ninik
20 | # Subsector C: Dartho
21 | # Subsector D: Oulduktak
22 | # Subsector E: Shurlarlem
23 | # Subsector F: Sarar
24 | # Subsector G: Gimgir
25 | # Subsector H: Kiirkandin
26 | # Subsector I: Urunishu
27 | # Subsector J: Uradnim
28 | # Subsector K: Antares
29 | # Subsector L: Mukusi
30 | # Subsector M: Urgusap
31 | # Subsector N: Gaakish
32 | # Subsector O: Sakhag
33 | # Subsector P: Celebes
34 |
35 | # Alleg: CsIm: "Client state, Third Imperium"
36 | # Alleg: ImDa: "Third Imperium, Domain of Antares"
37 | # Alleg: ImLa: "Third Imperium, League of Antares"
38 | # Alleg: JuRu: "Julian Protectorate, Rukadukaz Republic"
39 | # Alleg: NaHu: "Non-Aligned, Human-dominated"
40 | # Alleg: NaVa: "Non-Aligned, Vargr-dominated"
41 |
42 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes
43 | ---- -------------------- --------- ------------------------- ------ ------- ------ ---- -- - --- -- ---- -------------- ------------------------------------
44 | 2330 Pijasysley E572577-7 He Ni { -3 } (741-3) [5257] B - - 303 12 ImDa F4 V
45 | 2421 Antares A000ADA-D As Hi In Na Va Cs Pz { 5 } (C9H+5) [CF7F] BEFG NS A 800 3 ImDa M1 Ib B3 V Xb:2122 Xb:2220 Xb:2324
46 | 2425 Ansenz A547ABB-E Hi In Pz Varg1 Di(Kaski) { 4 } (F9G+5) [CE7G] BEf - A 403 11 ImLa G2 V M9 V
47 | 2427 Vat C550533-9 De Ni Po { -1 } (B43-4) [2426] B - - 522 12 ImDa G2 V Xb:2324 Xb:2730
48 | 2631 Laupla C564410-8 Ni Pa { -2 } (D31-5) [1213] Bc S - 725 15 ImDa G8 V
49 | 2634 Rinotawha A99A8BE-E Wa Ph Pi Pz { 2 } (C7D+5) [CA9J] BDe - A 420 9 ImDa K1 V M8 V Xb:2235 Xb:2638 Xb:2935
50 | 2638 Likarudig A89A7BD-E Wa Cp Pi Pz { 3 } (E6E+5) [BA9J] BDF W A 205 14 ImDa K0 V Xb:2634 Xb:2940
51 |
--------------------------------------------------------------------------------
/.github/workflows/python-package.yml:
--------------------------------------------------------------------------------
1 | name: Python package
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | python-version: ["3.9", "3.13"]
12 |
13 | steps:
14 | - uses: actions/checkout@v4
15 | - name: Set up Python ${{ matrix.python-version }}
16 | uses: actions/setup-python@v5
17 | with:
18 | python-version: ${{ matrix.python-version }}
19 | - name: Install freefonts & symbola
20 | run: |
21 | sudo apt-get -y install fonts-freefont-ttf fonts-symbola
22 | - name: Install dependencies
23 | run: |
24 | python -m pip install --upgrade pip
25 | pip install ruff pytest
26 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
27 | - name: Lint with ruff - syntax errors & undefined names
28 | run: |
29 | # stop the build if there are Python syntax errors or undefined names
30 | ruff check --select=E9,F63,F7,F82 --preview PyRoute Tests
31 | - name: Lint with ruff - configured options
32 | run: |
33 | # now run configured options
34 | ruff check --preview PyRoute Tests
35 | - name: Check types with mypy
36 | run: |
37 | mypy PyRoute/Allies/*.py PyRoute/AreaItems/*.py PyRoute/Calculation/*.py PyRoute/DeltaDebug/*.py PyRoute/DeltaPasses/*.py PyRoute/Inputs/*.py PyRoute/Position/*.py PyRoute/StatCalculation/*.py PyRoute/SystemData/*.py PyRoute/*.py
38 | - name: Run non-cython tests
39 | run: |
40 | pytest "Tests/Pathfinding/testAstarNumpy.py::testAStarNumpy::testAStarOverSubsector" "Tests/Pathfinding/testTradeCalculation.py::testTradeCalculation::test_route_update_simple"
41 | - name: Set up cython build
42 | run: |
43 | cd $LD_LIBRARY_PATH/python${{ matrix.python-version }}/site-packages/numpy/
44 | ls -al
45 | cp __init__.cython-30.pxd numpy.pxd
46 | - name: Build cython modules
47 | run: |
48 | cd PyRoute/Pathfinding
49 | python3 setup.py build_ext --inplace
50 | - name: Test with pytest
51 | run: |
52 | pytest
53 | - name: TradeMP smoke test
54 | run: |
55 | echo "$PWD"
56 | mkdir ./Tests/maps
57 | PYTHONPATH="." python ./PyRoute/route.py @Tests/TradeMPFiles/run_trademp_parameters
58 |
--------------------------------------------------------------------------------
/PyRoute/DeltaDebug/DeltaGalaxy.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on May 21, 2023
3 |
4 | @author: CyberiaResurrection
5 | """
6 | import copy
7 |
8 | from PyRoute.AreaItems.Allegiance import Allegiance
9 | from PyRoute.AreaItems.Galaxy import Galaxy
10 | from PyRoute.AreaItems.Subsector import Subsector
11 | from PyRoute.AreaItems.Sector import Sector
12 | from PyRoute.DeltaDebug.DeltaDictionary import SectorDictionary, SubsectorDictionary
13 | from PyRoute.Star import Star
14 |
15 |
16 | class DeltaGalaxy(Galaxy):
17 |
18 | def read_sectors(self, sectors, pop_code, ru_calc, # type: ignore
19 | route_reuse, trade_choice, route_btn, mp_threads, debug_flag) -> None:
20 | self._set_trade_object(route_reuse, trade_choice, route_btn, mp_threads, debug_flag)
21 | star_counter = 0
22 | sector: SectorDictionary
23 |
24 | sectors.skip_void_subsectors()
25 | for sector_name in sectors:
26 | sector = sectors[sector_name]
27 | sec = Sector("# " + sector.name, sector.position)
28 | sec.filename = sector.filename
29 |
30 | # load up subsectors
31 | for subsector_name in sector:
32 | subsector: SubsectorDictionary = sector[subsector_name]
33 | subsec = Subsector(subsector.name, subsector.position, sec)
34 | sec.subsectors[subsector.position] = subsec
35 |
36 | # load up allegiances
37 | for allegiance_name in sector.allegiances:
38 | allegiance: Allegiance = copy.deepcopy(sector.allegiances[allegiance_name])
39 |
40 | if allegiance_name not in self.alg:
41 | self.alg[allegiance_name] = allegiance
42 |
43 | # once all that's done, load up the stars
44 | for line in sector.lines:
45 | if line.startswith('#') or len(line) < 20:
46 | continue
47 | star = Star.parse_line_into_star(line, sec, pop_code, ru_calc)
48 | if star:
49 | star_counter = self.add_star_to_galaxy(star, star_counter, sec)
50 |
51 | self.sectors[sec.name] = sec
52 | self.logger.info("Sector {} loaded {} worlds".format(sec, len(sec.worlds)))
53 |
54 | self.set_bounding_sectors()
55 | self.set_bounding_subsectors()
56 | self.set_positions()
57 | self.logger.debug("Allegiances: {}".format(self.alg))
58 |
--------------------------------------------------------------------------------
/Tests/Pathfinding/testTradeMPCalculation.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Aug 08, 2024
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.AreaItems.Galaxy import Galaxy
7 | from PyRoute.DataClasses.ReadSectorOptions import ReadSectorOptions
8 | from Tests.baseTest import baseTest
9 |
10 |
11 | class testTradeMPCalculation(baseTest):
12 | def test_direct_route_doesnt_blow_up(self) -> None:
13 | sourcefile = self.unpack_filename('DeltaFiles/direct_routes_on_trade_mp/Lishun.sec')
14 |
15 | args = self._make_args()
16 | args.route_reuse = 30
17 | args.route_btn = 8
18 | args.mp_threads = 2
19 | args.routes = "trade-mp"
20 |
21 | readparms = ReadSectorOptions(sectors=[sourcefile], pop_code=args.pop_code, ru_calc=args.ru_calc,
22 | route_reuse=args.route_reuse, trade_choice=args.routes, route_btn=args.route_btn,
23 | mp_threads=args.mp_threads, debug_flag=args.debug_flag, fix_pop=False,
24 | deep_space={}, map_type=args.map_type)
25 |
26 | galaxy = Galaxy(min_btn=15, max_jump=4)
27 | galaxy.read_sectors(readparms)
28 | galaxy.output_path = args.output
29 |
30 | galaxy.generate_routes()
31 | galaxy.set_borders(args.borders, args.ally_match)
32 | galaxy.trade.calculate_routes()
33 |
34 | def test_direct_route_doesnt_blow_up_2(self) -> None:
35 | sourcefile = self.unpack_filename('DeltaFiles/direct_routes_on_trade_mp/Lishun.sec')
36 |
37 | args = self._make_args()
38 | args.route_reuse = 30
39 | args.min_btn = 15
40 | args.mp_threads = 2
41 | args.routes = "trade-mp"
42 |
43 | readparms = ReadSectorOptions(sectors=[sourcefile], pop_code=args.pop_code, ru_calc=args.ru_calc,
44 | route_reuse=args.route_reuse, trade_choice=args.routes, route_btn=args.route_btn,
45 | mp_threads=args.mp_threads, debug_flag=args.debug_flag, fix_pop=False,
46 | deep_space={}, map_type=args.map_type)
47 |
48 | galaxy = Galaxy(min_btn=15, max_jump=4)
49 | galaxy.read_sectors(readparms)
50 | galaxy.output_path = args.output
51 |
52 | galaxy.generate_routes()
53 | galaxy.set_borders(args.borders, args.ally_match)
54 | galaxy.trade.calculate_routes()
55 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/WidenHoleReducer.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Oct 04, 2023
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from typing import Optional
7 |
8 | from PyRoute.DeltaDebug.DeltaDictionary import DeltaDictionary
9 |
10 |
11 | class WidenHoleReducer(object):
12 |
13 | def __init__(self, reducer):
14 | from PyRoute.DeltaDebug.DeltaReduce import DeltaReduce
15 | self.reducer: DeltaReduce = reducer
16 |
17 | def preflight(self) -> bool:
18 | return self.reducer is not None and self.reducer.sectors is not None and 0 < len(self.reducer.sectors.lines)
19 |
20 | def run(self, start_pos, reverse=False, best_sectors=None) -> Optional[DeltaDictionary]:
21 | found_reduction = True
22 | chunk_size = 1
23 |
24 | supplied = best_sectors is not None
25 |
26 | best_sectors = self.reducer.sectors if not supplied else best_sectors
27 |
28 | msg = "# of lines: " + str(len(best_sectors.lines))
29 | self.reducer.logger.error(msg)
30 |
31 | while found_reduction:
32 | segment = best_sectors.lines
33 | segment_len = len(segment)
34 | if reverse:
35 | if 0 < start_pos:
36 | start_pos = min(start_pos, len(best_sectors.lines))
37 |
38 | raw_start_pos = start_pos + 1 if 0 < start_pos else segment_len + start_pos
39 | raw_fin_pos = raw_start_pos - chunk_size
40 | lines_to_drop = segment[min(raw_start_pos, raw_fin_pos):max(raw_start_pos, raw_fin_pos)]
41 | else:
42 | lines_to_drop = segment[start_pos:start_pos + chunk_size]
43 |
44 | if 1 < chunk_size and 0 == len(lines_to_drop):
45 | found_reduction = False
46 | continue
47 |
48 | temp_sectors = best_sectors.drop_lines(lines_to_drop)
49 |
50 | interesting, msg, _ = self.reducer._check_interesting(self.reducer.args, temp_sectors)
51 |
52 | if not interesting:
53 | found_reduction = False
54 | continue
55 | best_sectors = temp_sectors
56 | chunk_size *= 2
57 |
58 | msg = "Reduction found: new input has " + str(len(best_sectors.lines)) + " lines"
59 | self.reducer.logger.error(msg)
60 |
61 | if supplied:
62 | return best_sectors
63 |
64 | self.reducer.sectors = best_sectors
65 | return None
66 |
--------------------------------------------------------------------------------
/Tests/baseTest.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import logging
3 | import os
4 | import tempfile
5 | import unittest
6 |
7 | from PyRoute.Utilities.UnpackFilename import UnpackFilename
8 |
9 |
10 | class baseTest(unittest.TestCase):
11 |
12 | # Set up logging information
13 | logger = logging.getLogger(__name__)
14 | logging.basicConfig(level=logging.DEBUG)
15 |
16 | def unpack_filename(self, filename: str) -> str:
17 | return UnpackFilename.unpack_filename(filename)
18 |
19 | def unpack_workdir(self, dirname) -> str:
20 | # try unpacked directory directly
21 | workdir = os.path.abspath(dirname)
22 |
23 | cwd = os.getcwd()
24 | if not os.path.isdir(workdir):
25 | workdir = os.path.abspath(cwd + dirname)
26 |
27 | return workdir
28 |
29 | def reset_logging(self) -> None:
30 | loggers = [logging.getLogger(name) for name in logging.root.manager.loggerDict]
31 | loggers.append(logging.getLogger())
32 | for logger in loggers:
33 | handlers = logger.handlers[:]
34 | for handler in handlers:
35 | logger.removeHandler(handler)
36 | handler.close()
37 | logger.setLevel(logging.NOTSET)
38 | logger.propagate = True
39 |
40 | def _make_args(self) -> argparse.ArgumentParser:
41 | args = argparse.ArgumentParser(description='PyRoute input minimiser.')
42 | args.btn = 8
43 | args.max_jump = 2
44 | args.route_btn = 13
45 | args.pop_code = 'scaled'
46 | args.ru_calc = 'scaled'
47 | args.routes = 'trade'
48 | args.route_reuse = 10
49 | args.interestingline = "Weight of edge"
50 | args.interestingtype = None
51 | args.maps = None
52 | args.borders = 'range'
53 | args.ally_match = 'collapse'
54 | args.owned = False
55 | args.trade = True
56 | args.speculative_version = 'CT'
57 | args.ally_count = 10
58 | args.json_data = False
59 | args.output = tempfile.gettempdir()
60 | args.mp_threads = 1
61 | args.debug_flag = False
62 | args.map_type = "classic"
63 | return args
64 |
65 | def _make_args_no_line(self) -> argparse.ArgumentParser:
66 | args = self._make_args()
67 | args.interestingline = None
68 |
69 | return args
70 |
71 |
72 | if __name__ == '__main__':
73 | unittest.main()
74 |
--------------------------------------------------------------------------------
/PyRoute/Pathfinding/LandmarkSchemes/LandmarkAvoidHelper.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Mar 06, 2024
3 |
4 | @author: CyberiaResurrection
5 | """
6 | import numpy as np
7 |
8 |
9 | class LandmarkAvoidHelper:
10 |
11 | TREE_ROOT = -1
12 | TREE_NONE = -100
13 |
14 | @staticmethod
15 | def calc_weights(distances, lobound):
16 | return distances - lobound
17 |
18 | @staticmethod
19 | def calc_sizes(weights, tree, landmarks):
20 | active_nodes = np.array(range(len(weights)))
21 | sizes = np.zeros(len(weights))
22 | # filter out nodes who aren't part of a tree
23 | keep = tree[active_nodes] != LandmarkAvoidHelper.TREE_NONE
24 | active_nodes = active_nodes[keep]
25 |
26 | # spin through nodes in bulk, propagating weights upwards
27 | active_weights = weights[active_nodes]
28 | while 0 < len(active_nodes):
29 | # Directly iterating in python worked out faster than using nditer
30 | for index in range(len(active_nodes)):
31 | sizes[active_nodes[index]] += active_weights[index]
32 | keep = tree[active_nodes] != LandmarkAvoidHelper.TREE_ROOT
33 | active_nodes = active_nodes[keep]
34 | active_weights = active_weights[keep]
35 | active_nodes = tree[active_nodes] # Move up to immediate parents
36 |
37 | active_nodes = np.array(list(landmarks))
38 | while 0 < len(active_nodes):
39 | sizes[active_nodes] = 0
40 | keep = tree[active_nodes] != LandmarkAvoidHelper.TREE_ROOT
41 | active_nodes = active_nodes[keep]
42 | active_nodes = tree[active_nodes]
43 |
44 | return sizes
45 |
46 | @staticmethod
47 | def traverse_sizes(sizes, rootnode, tree):
48 | active_nodes = np.array(range(len(sizes)))
49 | if LandmarkAvoidHelper.TREE_ROOT != tree[rootnode]:
50 | raise AssertionError("Selected root node " + str(rootnode) + " not marked as a tree root")
51 | choice = active_nodes[np.argmax(sizes)]
52 | kidvec = np.where(choice == tree)
53 | kidsizes = sizes[kidvec]
54 | maxkid = np.argmax(kidsizes)
55 | while 0 < len(kidsizes):
56 | choice = active_nodes[kidvec][maxkid]
57 | kidvec = np.where(choice == tree)
58 | kidsizes = sizes[kidvec]
59 | if 0 < len(kidsizes):
60 | maxkid = np.argmax(kidsizes)
61 |
62 | return choice
63 |
--------------------------------------------------------------------------------
/PyRoute/Nobles.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Nov 29, 2023
3 |
4 | @author: CyberiaResurrection
5 | """
6 |
7 |
8 | class Nobles(object):
9 | __slots__ = 'nobles'
10 |
11 | codes = {'B': 'Knights',
12 | 'c': 'Baronets',
13 | 'C': 'Barons',
14 | 'D': 'Marquis',
15 | 'e': 'Viscounts',
16 | 'E': 'Counts',
17 | 'f': 'Dukes',
18 | 'F': 'Sector Dukes',
19 | 'G': 'Archdukes',
20 | 'H': 'Emperor'}
21 |
22 | key_list = list(codes.keys())
23 | value_list = list(codes.values())
24 |
25 | def __init__(self):
26 | self.nobles = {'Knights': 0,
27 | 'Baronets': 0,
28 | 'Barons': 0,
29 | 'Marquis': 0,
30 | 'Viscounts': 0,
31 | 'Counts': 0,
32 | 'Dukes': 0,
33 | 'Sector Dukes': 0,
34 | 'Archdukes': 0,
35 | 'Emperor': 0}
36 |
37 | def __str__(self):
38 | # If there's absolutely no nobles, return '-':
39 | if 0 == self.max_value:
40 | return '-'
41 |
42 | nobility = ""
43 | for rank, count in self.nobles.items():
44 | if count > 0:
45 | nobility += Nobles.key_list[Nobles.value_list.index(rank)]
46 | return ''.join(sorted(nobility, key=lambda v: (v.lower(), v[0].isupper())))
47 |
48 | def __getstate__(self):
49 | state = self.__dict__.copy()
50 | del state['codes']
51 | return state
52 |
53 | def count(self, nobility) -> None:
54 | for code, rank in Nobles.codes.items():
55 | if code in nobility:
56 | self.nobles[rank] += 1
57 |
58 | def accumulate(self, nobles) -> None:
59 | for rank, count in nobles.nobles.items():
60 | self.nobles[rank] += count
61 |
62 | @property
63 | def max_value(self) -> int:
64 | return max(self.nobles.values())
65 |
66 | @property
67 | def min_value(self) -> int:
68 | return min(self.nobles.values())
69 |
70 | @property
71 | def sum_value(self) -> int:
72 | return sum(self.nobles.values())
73 |
74 | def is_well_formed(self) -> tuple[bool, str]:
75 | msg = ''
76 | if 0 > self.max_value:
77 | msg = 'Noble count values cannot be negative'
78 | return False, msg
79 |
80 | return True, msg
81 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/Dagudashaag-subsector-reduced.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2021-11-28T10:21:52-08:00
3 |
4 | # Dagudashaag
5 | # -1,0
6 |
7 | # Name: Dagudashaag
8 | # Name: Dagudashag
9 |
10 | # Abbreviation: Dagu
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: Dagudashaag sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by Duncan Law-Green, Leighton Piper and Jae Campbell, and appears in Signal GK Magazine.
15 |
16 | # Author: Duncan Law-Green, Leighton Piper and Jae Campbell; Martin J. Dougherty
17 | # Source: Traveller 5 Second Survey
18 |
19 | # Subsector A: Mimu
20 | # Subsector B: Old Suns
21 | # Subsector C: Arnakhish
22 | # Subsector D: Iiradu
23 | # Subsector E: Shallows
24 | # Subsector F: Ushra
25 | # Subsector G: Khandi
26 | # Subsector H: Kuriishe
27 | # Subsector I: Zeda
28 | # Subsector J: Remnants
29 | # Subsector K: Pact
30 | # Subsector L: Gadde
31 | # Subsector M: Bolivar
32 | # Subsector N: Argi
33 | # Subsector O: Sapphyre
34 | # Subsector P: Laraa
35 |
36 | # Alleg: ImAp: "Third Imperium, Amec Protectorate"
37 | # Alleg: ImDv: "Third Imperium, Domain of Vland"
38 | # Alleg: ImLc: "Third Imperium, Lancian Cultural Region"
39 |
40 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes
41 | ---- -------------------- --------- ------------------------------------- ------ ------- ------ ---- -- - --- -- ---- --------------- -----------------------------------------
42 | 1728 Ekhugush A63698A-D Hi { 3 } (E8F+5) [BC7F] BE N - 312 8 ImDv M2 V M9 V
43 | 1729 Ushmegili D86A204-A Lo Wa { -1 } (810-3) [1138] B S - 722 14 ImDv G1 V
44 | 2123 Medurma A9D7954-C Hi An Cs Di(Miyavine) Asla1 S'mr0 { 3 } (G8E+1) [7C3A] BEF - - 823 12 ImDv G0 V Xb:1823 Xb:1926 Xb:2223 Xb:2225 Xb:2322
45 | 2122 Chandra's World C9966AA-7 Ag Ni Da { -1 } (853+1) [8579] BC - A 803 8 ImDv K4 V
46 | 2123 Kediiga B778411-8 Ni Pa { -1 } (832-5) [1314] Bc - - 920 9 ImDv G6 V
47 | 2127 Thalassa D56A557-9 Ni Wa Pr { -2 } (C42-2) [5359] Bc - - 423 11 ImDv K8 V
48 | 2130 Maiden B43069B-C De Na Ni Po Da { 1 } (D55+3) [877E] B N A 814 13 ImDv M0 V M7 V Xb:2028 Xb:2531
49 |
--------------------------------------------------------------------------------
/sectorlist.txt:
--------------------------------------------------------------------------------
1 | Afawahisa
2 | Aktifao
3 | Aldebaran
4 | Alpha Crucis
5 | Amderstun
6 | Amdukan
7 | Angfutsag
8 | Antares
9 | Astron
10 | Ataurre
11 | Avereguar
12 | Banners
13 | Bar'kakr
14 | Blaskon
15 | Canopus
16 | Centrax
17 | Chit Botshti
18 | Core
19 | Corridor
20 | Crucis Margin
21 | Dagudashaag
22 | Daibei
23 | Dark Nebula
24 | Darret
25 | Datsatl
26 | Delphi
27 | Deneb
28 | Dfotseth
29 | Dhuerorrg
30 | Diaspora
31 | Drakken
32 | Ealiyasiyw
33 | Eiaplial
34 | Empty Quarter
35 | Esai'yo
36 | Extolian
37 | Fa Dzaets
38 | Faoheiroi'iyhao
39 | Far Frontiers
40 | Finggvakhou
41 | Folgore
42 | Fornast
43 | Ftaoiyekyu
44 | Fulani
45 | Gashikan
46 | Gateway
47 | Genfert
48 | Gh!hken
49 | Ghoekhnael
50 | Glimmerdrift Reaches
51 | Gn'hk'r
52 | Grikr!ng
53 | Gur
54 | Gushemege
55 | Gvurrdon
56 | Gzaekfueg
57 | Gzirr!k'l
58 | Hadji
59 | Hanstone
60 | Harea
61 | Hinterworlds
62 | Hkakhaeaw
63 | Hlakhoi
64 | Ianshaplzdier
65 | Ilelish
66 | Ingukrax
67 | Iphigenaia
68 | Irugangog
69 | Itvikiastaf
70 | Iwahfuah
71 | K'trekreer
72 | Kaa G!'kul
73 | Karleaya
74 | Katoonah
75 | Kfazz Ghik
76 | Khaeaw
77 | Kharrthon
78 | Kidunal
79 | Kilong
80 | Knaeleng
81 | Knoellighz
82 | Kolire
83 | Kring Noor
84 | Kruse
85 | Ksinanirz
86 | Ktiin'gzat
87 | Langere
88 | Leonidae
89 | Ley Sector
90 | Lishun
91 | Listanaya
92 | Lorspane
93 | Lubbock
94 | Luretiir!girr
95 | Magyar
96 | Malorn
97 | Massilia
98 | Mbil!!gh
99 | Mendan
100 | Meshan
101 | Mikhail
102 | Mugheen't
103 | Neworld
104 | Ngathksirz
105 | Nooq
106 | Numbis
107 | Nuughe
108 | Oeghz Vaerrghr
109 | Old Expanses
110 | Phlange
111 | Phlask
112 | Porlock
113 | Provence
114 | Raakaan
115 | Reaver's Deep
116 | Reft Sector
117 | Rfigh
118 | Ricenden
119 | Riftspan Reaches
120 | RimReach
121 | Ruthless Veil
122 | Ruupiin
123 | Rzakki
124 | Sidiadl
125 | Solomani Rim
126 | Spangele
127 | Spica
128 | Spinward Marches
129 | Staihaia'yo
130 | Star's End
131 | Stiatlchepr
132 | Storr
133 | Tar'G'kell'p
134 | The Beyond
135 | Theron
136 | Theta Borealis
137 | Tienspevnekr
138 | Touchstone
139 | Treece
140 | Trenchans
141 | Trojan Reach
142 | Tsadra
143 | Tuglikki
144 | Uistilrao
145 | Ukaarriit!!b
146 | Un'k!!k'ng
147 | Ustral Quadrant
148 | Uuk
149 | Vanguard Reaches
150 | Veg Fergakh
151 | Verge
152 | Vland
153 | Waroatahe
154 | Windhorn
155 | Wrenton
156 | X'kug
157 | Xaagr
158 | Yiklerzdanzh
159 | Zao Kfeng Ig Grilokh
160 | Zarushagar
161 | Zdiedeiant
162 | Zhdant
163 | Zhiaqrqiats
164 | Ziafrplians
165 | Zortakh
166 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/Zarushagar-imbalanced-routes.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2021-11-28T10:37:26-08:00
3 |
4 | # Zarushagar
5 | # -1,-1
6 |
7 | # Name: Zarushagar
8 |
9 | # Abbreviation: Zaru
10 |
11 | # Milieu: M1105
12 |
13 | # Credits: Zarushagar sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by Greg Videll. Portions appear in The Travellers Digest and the MegaTraveller Referee's Gaming Kit (Digest Group Publications, 1989).
14 |
15 | # Author: Greg Videll, Martin J. Dougherty
16 | # Source: Traveller 5 Second Survey
17 |
18 | # Subsector A: Ibaru
19 | # Subsector B: Triton
20 | # Subsector C: Lagaar
21 | # Subsector D: Lode
22 | # Subsector E: Iimii
23 | # Subsector F: Khipge
24 | # Subsector G: Epshu
25 | # Subsector H: Gaussi
26 | # Subsector I: Mizar
27 | # Subsector J: Liasdi
28 | # Subsector K: Wolf
29 | # Subsector L: Oasis
30 | # Subsector M: Void
31 | # Subsector N: Allegro
32 | # Subsector O: Rhamsteppe
33 | # Subsector P: Mongrabi
34 |
35 | # Alleg: CsIm: "Client state, Third Imperium"
36 | # Alleg: ImDi: "Third Imperium, Domain of Ilelish"
37 | # Alleg: NaHu: "Non-Aligned, Human-dominated"
38 |
39 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes
40 | ---- -------------------- --------- -------------------------- ------ ------- ------ ----- -- - --- -- ---- -------------- ----------------------------------------------
41 | 3037 Ralik C765220-9 Lo Ga { -1 } (910-5) [1114] B - - 205 12 ImDi G7 V M7 V
42 | 3038 Noire D000556-7 As Ni Va { -3 } (741-4) [4246] B - - 715 13 ImDi K3 V
43 | 3040 Borderline B664756-B Ag Ri { 4 } (B6E+3) [6B4A] BCf N - 711 10 ImDi K3 V M7 V
44 | 3134 Pylon D556652-7 Ag Ni { -2 } (852-5) [2413] BC - - 403 9 ImDi M0 V
45 | 3135 Aquivda C565730-8 Ag Ri { 1 } (B69-3) [2813] BC S - 902 8 ImDi M2 V
46 | 3136 Gaidraa B6859A9-C Hi Ga Pr Darm6 { 4 } (B8F+5) [AD6D] BcEf W - 400 13 ImDi K3 V M5 V Xb:2937 Xb:3133
47 |
--------------------------------------------------------------------------------
/Tests/Pyfakefs/baseTest.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import logging
3 | import os
4 | import tempfile
5 | from pyfakefs.fake_filesystem_unittest import TestCase
6 |
7 | from PyRoute.Inputs.ParseStarInput import ParseStarInput
8 | from PyRoute.Utilities.UnpackFilename import UnpackFilename
9 |
10 |
11 | class baseTest(TestCase):
12 |
13 | # Set up logging information
14 | logger = logging.getLogger(__name__)
15 | logging.basicConfig(level=logging.DEBUG)
16 |
17 | def setUp(self) -> None:
18 | ParseStarInput.deep_space = {}
19 |
20 | def unpack_filename(self, filename: str) -> str:
21 | return UnpackFilename.unpack_filename(filename)
22 |
23 | def unpack_workdir(self, dirname: str) -> str:
24 | # try unpacked directory directly
25 | workdir = os.path.abspath(dirname)
26 |
27 | cwd = os.getcwd()
28 | if not os.path.isdir(workdir):
29 | workdir = os.path.abspath(cwd + dirname)
30 |
31 | return workdir
32 |
33 | def reset_logging(self) -> None:
34 | loggers = [logging.getLogger(name) for name in logging.root.manager.loggerDict]
35 | loggers.append(logging.getLogger())
36 | for logger in loggers:
37 | handlers = logger.handlers[:]
38 | for handler in handlers:
39 | logger.removeHandler(handler)
40 | handler.close()
41 | logger.setLevel(logging.NOTSET)
42 | logger.propagate = True
43 |
44 | def _make_args(self) -> argparse.ArgumentParser:
45 | args = argparse.ArgumentParser(description='PyRoute input minimiser.')
46 | args.btn = 8
47 | args.max_jump = 2
48 | args.route_btn = 13
49 | args.pop_code = 'scaled'
50 | args.ru_calc = 'scaled'
51 | args.routes = 'trade'
52 | args.route_reuse = 10
53 | args.interestingline = "Weight of edge"
54 | args.interestingtype = None
55 | args.maps = None
56 | args.borders = 'range'
57 | args.ally_match = 'collapse'
58 | args.owned = False
59 | args.trade = True
60 | args.speculative_version = 'CT'
61 | args.ally_count = 10
62 | args.json_data = False
63 | args.output = tempfile.gettempdir()
64 | args.mp_threads = 1
65 | args.debug_flag = False
66 | args.map_type = "classic"
67 | return args
68 |
69 | def _make_args_no_line(self) -> argparse.ArgumentParser:
70 | args = self._make_args()
71 | args.interestingline = None
72 |
73 | return args
74 |
--------------------------------------------------------------------------------
/PyRoute/Pathfinding/DistanceGraph.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Sep 23, 2023
3 |
4 | @author: CyberiaResurrection
5 |
6 | Thanks to @GamesByDavidE for original prototype and design discussions
7 | """
8 | import copy
9 |
10 | import numpy as np
11 | from networkx.classes import Graph
12 |
13 | from PyRoute.Pathfinding.DistanceBase import DistanceBase
14 |
15 |
16 | class DistanceGraph(DistanceBase):
17 |
18 | __slots__ = '_arcs', '_positions', '_nodes', '_indexes', '_min_cost', '_min_indirect'
19 |
20 | def __init__(self, graph: Graph):
21 | super().__init__(graph)
22 | self._arcs = [
23 | (
24 | np.array(graph.adj[u], dtype=int),
25 | np.array([data['weight'] for data in list(graph.adj[u].values())], dtype=float),
26 | {v: k for (k, v) in enumerate(list(graph.adj[u].keys()))}
27 | )
28 | for u in self._nodes
29 | ]
30 | self._min_cost = np.zeros(len(self._nodes))
31 | self._min_indirect = np.zeros(len(self._nodes))
32 | for i in range(0, len(self._nodes)):
33 | node_edges = self._arcs[i][1]
34 | if 0 < len(node_edges):
35 | self._min_cost[i] = min(node_edges)
36 | for i in range(0, len(self._nodes)):
37 | node_neighbours = self._arcs[i][0]
38 | if 0 < len(node_neighbours):
39 | self._min_indirect[i] = min(self._min_cost[node_neighbours])
40 |
41 | def min_cost(self, target: int, indirect: bool = False) -> np.ndarray:
42 | min_cost = copy.deepcopy(self._min_cost)
43 | min_cost[target] = 0
44 |
45 | if indirect is not True:
46 | return min_cost
47 |
48 | min_indirect = copy.deepcopy(self._min_indirect)
49 | neighbours = self._arcs[target][0] # Skip neighbours of target and target itself in the indirect leg
50 | min_indirect[target] = 0
51 | min_indirect[neighbours] = 0
52 |
53 | min_cost += min_indirect
54 |
55 | return min_cost
56 |
57 | def lighten_edge(self, u: int, v: int, weight: float) -> None:
58 | if v not in self._arcs[u][2]:
59 | assert False
60 | self._lighten_arc(u, v, weight)
61 | self._lighten_arc(v, u, weight)
62 | if weight < self._min_cost[u]:
63 | self._min_cost[u] = weight
64 | if weight < self._min_cost[v]:
65 | self._min_cost[v] = weight
66 |
67 | self._min_indirect[self._arcs[u][0]] = np.fmin(self._min_indirect[self._arcs[u][0]], weight)
68 | self._min_indirect[self._arcs[v][0]] = np.fmin(self._min_indirect[self._arcs[v][0]], weight)
69 |
--------------------------------------------------------------------------------
/Tests/BorderGeneration/Far Frontiers-partial-allymap-allymap.json:
--------------------------------------------------------------------------------
1 | {"(-176, 144)": "Na", "(-175, 141)": "Na", "(-175, 139)": "4Wor", "(-175, 137)": "Na", "(-174, 144)": "Prot", "(-174, 139)": "4Wor", "(-174, 138)": "4Wor", "(-174, 137)": "Na", "(-173, 145)": "Prot", "(-173, 142)": "Prot", "(-173, 139)": "4Wor", "(-173, 138)": "UnGa", "(-172, 144)": "Prot", "(-177, 144)": "Na", "(-176, 143)": "Na", "(-175, 143)": "Prot", "(-175, 144)": "Prot", "(-176, 145)": "Prot", "(-177, 145)": "Na", "(-176, 141)": "4Wor", "(-175, 140)": "4Wor", "(-174, 140)": "4Wor", "(-174, 141)": "4Wor", "(-175, 142)": "Na", "(-176, 142)": "Na", "(-176, 139)": "4Wor", "(-175, 138)": "Na", "(-176, 140)": "4Wor", "(-177, 139)": "4Wor", "(-176, 138)": "Na", "(-173, 137)": "4Wor", "(-177, 141)": "4Wor", "(-177, 140)": "4Wor", "(-178, 139)": null, "(-177, 138)": null, "(-176, 137)": "Na", "(-175, 136)": null, "(-174, 136)": null, "(-173, 136)": null, "(-172, 136)": null, "(-172, 137)": "4Wor", "(-172, 138)": "4Wor", "(-172, 139)": "4Wor", "(-173, 140)": "4Wor", "(-177, 142)": "4Wor", "(-178, 142)": "4Wor", "(-178, 141)": "4Wor", "(-178, 140)": "4Wor", "(-174, 143)": "Prot", "(-173, 143)": "Prot", "(-173, 144)": "Prot", "(-174, 145)": "Prot", "(-175, 145)": "Prot", "(-174, 142)": "Prot", "(-172, 142)": "Prot", "(-172, 143)": "Prot", "(-174, 146)": "Prot", "(-175, 146)": "Prot", "(-176, 146)": "Prot", "(-173, 141)": "4Wor", "(-172, 141)": "Prot", "(-171, 141)": "Prot", "(-171, 142)": "Prot", "(-171, 143)": "Prot", "(-171, 144)": "Prot", "(-172, 145)": "Prot", "(-173, 146)": "Prot", "(-174, 147)": "Prot", "(-175, 147)": "Prot", "(-176, 147)": "Prot", "(-177, 147)": "Prot", "(-177, 146)": "Prot", "(-171, 136)": null, "(-171, 137)": null, "(-171, 138)": "4Wor", "(-171, 139)": "4Wor", "(-172, 140)": "4Wor", "(-174, 135)": null, "(-173, 135)": null, "(-172, 135)": null, "(-171, 135)": null, "(-170, 135)": null, "(-170, 136)": null, "(-170, 137)": null, "(-170, 138)": null, "(-170, 139)": null, "(-171, 140)": "Prot", "(-177, 143)": "Na", "(-178, 143)": null, "(-171, 145)": "Prot", "(-172, 146)": "Prot", "(-173, 147)": "Prot", "(-170, 142)": "Prot", "(-170, 143)": "Prot", "(-170, 144)": "Prot", "(-170, 145)": "Prot", "(-171, 146)": "Prot", "(-172, 147)": "Prot", "(-173, 148)": "Prot", "(-174, 148)": "Prot", "(-175, 148)": "Prot", "(-176, 148)": "Prot", "(-170, 141)": "Prot", "(-169, 141)": null, "(-169, 142)": null, "(-169, 143)": null, "(-169, 144)": null, "(-169, 145)": null, "(-170, 146)": null, "(-171, 147)": null, "(-172, 148)": null, "(-173, 149)": null, "(-174, 149)": "Prot", "(-175, 149)": "Prot", "(-176, 149)": "Prot", "(-177, 149)": "Prot", "(-177, 148)": "Prot", "(-170, 140)": null, "(-169, 138)": null, "(-169, 139)": null, "(-169, 140)": null}
--------------------------------------------------------------------------------
/Tests/DeltaFiles/xroute_routes_pass_2_4/Dagudashaag.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2021-11-28T10:21:52-08:00
3 |
4 | # Dagudashaag
5 | # -1,0
6 |
7 | # Name: Dagudashaag
8 | # Name: Dagudashag
9 |
10 | # Abbreviation: Dagu
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: Dagudashaag sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by Duncan Law-Green, Leighton Piper and Jae Campbell, and appears in Signal GK Magazine.
15 |
16 | # Author: Duncan Law-Green, Leighton Piper and Jae Campbell; Martin J. Dougherty
17 | # Source: Traveller 5 Second Survey
18 |
19 | # Subsector A: Mimu
20 | # Subsector B: Old Suns
21 | # Subsector C: Arnakhish
22 | # Subsector D: Iiradu
23 | # Subsector E: Shallows
24 | # Subsector F: Ushra
25 | # Subsector G: Khandi
26 | # Subsector H: Kuriishe
27 | # Subsector I: Zeda
28 | # Subsector J: Remnants
29 | # Subsector K: Pact
30 | # Subsector L: Gadde
31 | # Subsector M: Bolivar
32 | # Subsector N: Argi
33 | # Subsector O: Sapphyre
34 | # Subsector P: Laraa
35 |
36 | # Alleg: ImDv: "Third Imperium, Domain of Vland"
37 |
38 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar Routes
39 | ---- -------------------- --------- ------------------------------------- ------ ------- ------ ---- -- - --- -- ---- --------------- -----------------------------------------
40 | 2124 Medurma A9D7954-C Hi An Cs Di(Miyavine) Asla1 S'mr0 { 3 } (G8E+1) [7C3A] BEF - - 823 12 ImDv G0 V Xb:1823 Xb:1926 Xb:2223 Xb:2225 Xb:2322
41 | 2526 Rraeghzoez B775452-C Ni Pa VargW { 1 } (934-3) [1518] Bc N - 212 7 ImDv K3 V M3 V
42 | 2927 Shea E651976-8 Hi Po { -1 } (E88-2) [8847] BE - - 303 6 ImDv M0 V M5 V
43 | 3228 Ishmurish D000200-B As Lo Va { -1 } (410-5) [1116] B S - 700 7 ImDv F6 V M1 V
44 | 3231 Mershemu D580411-7 De Ni { -3 } (631-5) [1113] B - - 903 12 ImDv G2 V
45 | 3235 Edraconis C594550-7 Ag Ni { -1 } (743-5) [1412] BC - - 903 12 ImDv M1 V M5 V
46 | 3239 Tutrii DA7A266-8 Lo Oc Mr { -3 } (610-4) [1147] B S - 102 7 ImDv G9 V M4 V
47 |
--------------------------------------------------------------------------------
/PyRoute/Pathfinding/single_source_dijkstra.py:
--------------------------------------------------------------------------------
1 | """
2 | Restarted single-source all-target Dijkstra shortest paths.
3 |
4 | Lifted from networkx repo (https://github.com/networkx/networkx/blob/main/networkx/algorithms/shortest_paths/weighted.py)
5 | as at Add note about using latex formatting in docstring in the contributor… (commit a63c8bd).
6 |
7 | Major modifications here are ripping out gubbins unrelated to the single-source all-targets case, handling cases
8 | where less than the entire shortest path tree needs to be regenerated due to a link weight change, and specialising to
9 | the specific input format used in PyRoute.
10 |
11 | """
12 | import numpy as np
13 |
14 | try:
15 | from PyRoute.Pathfinding.single_source_dijkstra_core import dijkstra_core
16 | except ModuleNotFoundError:
17 | from PyRoute.Pathfinding.single_source_dijkstra_core_fallback import dijkstra_core
18 | except ImportError:
19 | from PyRoute.Pathfinding.single_source_dijkstra_core_fallback import dijkstra_core
20 | except AttributeError:
21 | from PyRoute.Pathfinding.single_source_dijkstra_core_fallback import dijkstra_core
22 |
23 |
24 | def implicit_shortest_path_dijkstra_distance_graph(graph, source, distance_labels, seeds=None, divisor=1, min_cost=None, max_labels=None) -> tuple:
25 | # return only distance_labels from the explicit version
26 | distance_labels, _, max_neighbour_labels = explicit_shortest_path_dijkstra_distance_graph(graph, source,
27 | distance_labels, seeds,
28 | divisor,
29 | min_cost=min_cost,
30 | max_labels=max_labels)
31 | return distance_labels, max_neighbour_labels
32 |
33 |
34 | def explicit_shortest_path_dijkstra_distance_graph(graph, source, distance_labels, seeds=None, divisor=1, min_cost=None, max_labels=None) -> tuple:
35 | if not isinstance(distance_labels, np.ndarray):
36 | raise ValueError("distance labels must be ndarray")
37 |
38 | # assumes that distance_labels is already setup
39 | if seeds is None:
40 | seeds = {source}
41 |
42 | seeds = list(seeds)
43 | min_cost = np.zeros(len(graph), dtype=float) if min_cost is None else min_cost
44 | max_neighbour_labels = max_labels if max_labels is not None else np.ones(len(graph), dtype=float) * float('+inf')
45 |
46 | arcs = graph._arcs
47 |
48 | return dijkstra_core(arcs, distance_labels, divisor, seeds, max_neighbour_labels, min_cost)
49 |
--------------------------------------------------------------------------------
/RunSuite.txt:
--------------------------------------------------------------------------------
1 | # download the sector files
2 | python PyRoute/downloadsec.py --routes sectorlist.txt ../sectors
3 |
4 | # generate the maps and data
5 | nohup pypy PyRoute/route.py --borders erode --min-btn 15 --pop-code fixed --routes trade --max-jump=5 --speculative-version None --output /root/maps --input /root/sectors --sectors sectorlist.txt > route.log 2>&1 &
6 |
7 | # Upload the sector summaries.
8 | python PyRoute/WikiUploadPDF.py --input Runs/runs_map14
9 |
10 | # Update the xml files for TravellerMap
11 | python PyRoute/map_remap.py ../maps/stars.txt ../sectors/ ../maps
12 |
13 | #pypy PyRoute/route.py --borders erode --min-btn 15 --pop-code fixed --routes comm --max-jump 4 --route-reuse 3 --output sectors/review --input sectors/review --sectors sectors/review/reviewlist.txt
14 |
15 | #python PyRoute/route.py --borders erode --min-btn 13 --pop-code fixed --routes comm --no-subsector-maps --max-jump 6 --route-reuse 9 --output Runs/imperial_xroute --input sectors/Imperial --sectors sectorimplist.txt
16 | #python PyRoute/map_remap.py Runs/imperial_xroute/stars.txt sectors/Imperial Runs/imperial_xroute
17 | #python PyRoute/route.py --borders erode --min-btn 13 --pop-code fixed --routes trade --max-jump 10 --output maps_m1900
18 |
19 | #
20 |
21 | #python PyRoute/WikiUploadPDF.py --no-maps --no-secs --input test_data/full
22 |
23 |
24 | # Zarushagar update
25 | python PyRoute/WikiCreateWorlds.py @test_data/zarushagar/parameters > Zarushagar.data
26 |
27 | python PyRoute/WikiCreateWorlds.py sectors/full/Zarushagar.sec --search-disambig "(Za " --skip-list skip_processing.txt \
28 | --category test_data/zarushagar/Darmine_Corporate_worlds.txt --category test_data/zarushagar/Independent_Duchy_of_Oasis_worlds.txt \
29 | --category test_data/zarushagar/Zaimen,_Clammath_and_Hollings_worlds.txt \
30 | --category test_data/zarushagar/Gushemege_Main_worlds.txt --category test_data/zarushagar/Massilia_Spur_worlds.txt \
31 | --category test_data/zarushagar/Rimward_Link_worlds.txt --category test_data/zarushagar/Sylean_Main_worlds.txt \
32 | --category test_data/zarushagar/Vilani_Run_worlds.txt \
33 | --source test_data/zarushagar/Atlas-source.txt \
34 | > Zarushagar.data
35 |
36 |
37 | # python PyRoute/route.py --borders erode --min-btn 13 --pop-code fixed --routes trade --max-jump=5 --output test_data
38 |
39 | python PyRoute/route.py --borders erode --min-btn 13 --pop-code fixed --routes trade --max-jump 10 --min-ally-count 0 --ally-match separate --output maps_m1900 sectors/1900/spinward\ marches.sec sectors/1900/gvurrdon.sec
40 |
41 | python3 PyRoute/route.py --borders erode --min-btn 13 --pop-code fixed --routes trade --max-jump 5 --min-ally-count 0 --ally-match separate --no-subsector-maps --owned-worlds \
42 | --output maps_federation --input sectors/Federation --sectors sectors/Federation/Federation.txt
--------------------------------------------------------------------------------
/Tests/DeltaFiles/xroute_routes_pass_1_2/Massilia.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2024-09-28T17:21:53-07:00
3 |
4 | # Massilia
5 | # 0,-1
6 |
7 | # Name: Massilia
8 | # Name: Massila
9 | # Name: Masilaa (vi)
10 |
11 | # Abbreviation: Mass
12 |
13 | # Milieu: M1105
14 |
15 | # Credits: Massilia sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by Joe D. Fugate Sr. and appears The Travellers Digest and in Knightfall (GDW, 1990).
16 |
17 | # Source: Traveller 5 Second Survey
18 |
19 | # Subsector A: Kerr
20 | # Subsector B: Arar
21 | # Subsector C: Khisham
22 | # Subsector D: Zalucha
23 | # Subsector E: Keum
24 | # Subsector F: Vaait
25 | # Subsector G: Forquee
26 | # Subsector H: Palasha
27 | # Subsector I: Ten Suns
28 | # Subsector J: Shiwonee
29 | # Subsector K: Nes'Vra
30 | # Subsector L: Tooka
31 | # Subsector M: Annari
32 | # Subsector N: Shokee
33 | # Subsector O: Shimmer
34 | # Subsector P: Oreo
35 |
36 | # Alleg: ImDc: "Third Imperium, Domain of Sylea"
37 |
38 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar
39 | ---- -------------------- --------- ----------------------------- ------ ------- ------ ---- -- - --- -- ---- --------------
40 | 0904 Transvaal B557584-B Ag Ni { 2 } (B46+1) [3739] BC - - 404 16 ImDc M3 V M5 V
41 | 0908 Sargashad A994878-C Pa Ph Pi Pz Px Ht { 2 } (A7C+2) [8A5C] BcDe N A 400 13 ImDc G2 V M5 V
42 | 1203 Datrumna A562686-C Ni Ri Ht { 2 } (B56+1) [584B] BC - - 403 16 ImDc M1 V
43 | 1601 Suttas Belts A000200-F As Lo Va Ht { 1 } (411-3) [131A] B - - 100 6 ImDc M2 V
44 | 2001 Marfa B20079D-C Na Va Pi Pz Ht { 2 } (C6C+5) [B99G] BD - A 503 13 ImDc G3 V M4 V
45 | 2302 Elan B66889D-A Ri Pa Ph Pz { 3 } (F7C+5) [CB9E] BcCe N A 805 9 ImDc F7 V M6 V M7 V
46 | 2503 Euteneier B4227B8-B He Na Po Pi { 2 } (E6C+2) [795B] BD - - 414 9 ImDc M2 V M8 V
47 | 2902 Watia B545776-A Ag Pi { 3 } (F6C+2) [6A49] BCD - - 824 12 ImDc K1 V
48 | 0312 Rebin A88A436-F Ni Wa Ht { 1 } (834+1) [354E] B - - 202 8 ImDc F9 V M5 V
49 | 0414 Laisarlo B100758-E Na Va Pi Ht { 2 } (A6D+2) [795E] BD N - 101 14 ImDc M2 V M7 V
50 | 0711 Newport A427303-F Lo Ht { 1 } (B21-2) [142C] B - - 624 10 ImDc K4 V M9 V
51 | 0815 Keum A67198A-E He Hi In Cs Ht { 4 } (F8G+5) [BD7G] BEF - - 704 16 ImDc K0 V M4 V
52 |
--------------------------------------------------------------------------------
/Tests/Outputs/testClipping.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from PyRoute.Outputs.Map import Map
3 | from PyRoute.Outputs.Cursor import Cursor
4 |
5 |
6 | class testClipping(unittest.TestCase):
7 | def setUp(self) -> None:
8 | self.min_pos = Cursor(25, 25)
9 | self.max_pos = Cursor(100, 100)
10 |
11 | def test_internal(self) -> None:
12 | start = Cursor(30, 30)
13 | end = Cursor(75, 75)
14 | clip_start, clip_end = Map.clipping(self.min_pos, self.max_pos, start, end)
15 |
16 | self.assertEqual(start, clip_start)
17 | self.assertEqual(end, clip_end)
18 |
19 | def test_external(self) -> None:
20 | start = Cursor(0, 0)
21 | end = Cursor(10, 10)
22 | clip_start, clip_end = Map.clipping(self.min_pos, self.max_pos, start, end)
23 |
24 | self.assertIsNone(clip_start)
25 | self.assertIsNone(clip_end)
26 |
27 | def test_top_line(self) -> None:
28 | start = Cursor(30, 25)
29 | end = Cursor(50, 25)
30 | clip_start, clip_end = Map.clipping(self.min_pos, self.max_pos, start, end)
31 | self.assertEqual(start, clip_start)
32 | self.assertEqual(end, clip_end)
33 |
34 | def test_top_clip(self) -> None:
35 | start = Cursor(30, 20)
36 | end = Cursor(50, 50)
37 | clip_start, clip_end = Map.clipping(self.min_pos, self.max_pos, start, end)
38 | self.assertEqual(int(clip_start.x), 33)
39 | self.assertEqual(clip_start.y, 25)
40 | self.assertEqual(clip_end.x, 50)
41 | self.assertEqual(clip_end.y, 50)
42 |
43 | def test_right_clip(self) -> None:
44 | start = Cursor(150, 40)
45 | end = Cursor(50, 50)
46 | clip_start, clip_end = Map.clipping(self.min_pos, self.max_pos, start, end)
47 | self.assertEqual(int(clip_start.x), 100)
48 | self.assertEqual(clip_start.y, 45)
49 | self.assertEqual(clip_end.x, 50)
50 | self.assertEqual(clip_end.y, 50)
51 |
52 | def test_left_clip(self) -> None:
53 | start = Cursor(15, 40)
54 | end = Cursor(50, 50)
55 | clip_start, clip_end = Map.clipping(self.min_pos, self.max_pos, start, end)
56 | self.assertEqual(int(clip_start.x), 25)
57 | self.assertEqual(int(clip_start.y), 42)
58 | self.assertEqual(clip_end.x, 50)
59 | self.assertEqual(clip_end.y, 50)
60 |
61 | def test_bottom_clip(self) -> None:
62 | start = Cursor(45, 150)
63 | end = Cursor(50, 50)
64 | clip_start, clip_end = Map.clipping(self.min_pos, self.max_pos, start, end)
65 | self.assertEqual(int(clip_start.x), 47)
66 | self.assertEqual(int(clip_start.y), 100)
67 | self.assertEqual(clip_end.x, 50)
68 | self.assertEqual(clip_end.y, 50)
69 |
70 |
71 | if __name__ == '__main__':
72 | unittest.main()
73 |
--------------------------------------------------------------------------------
/PyRoute/DeltaPasses/TwoLineReducer.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Oct 03, 2023
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.DeltaPasses.BeyondLineReducer import BeyondLineReducer
7 |
8 |
9 | class TwoLineReducer(BeyondLineReducer):
10 |
11 | def run(self, singleton_only=False, first_segment=True) -> None:
12 | segment = self.reducer.sectors.lines
13 |
14 | # An interesting less-than-4-element list is 2-minimal by definition
15 | if 4 > len(segment):
16 | return
17 |
18 | best_sectors = self.reducer.sectors
19 | old_length = len(segment)
20 | sqrt = max(2, round(old_length ** 0.5))
21 | gap = 1 if first_segment else sqrt
22 | maxgap = sqrt + 1 if first_segment else len(segment)
23 |
24 | while gap < min(maxgap, len(segment)):
25 | msg = "# of lines: " + str(len(best_sectors.lines)) + f", gap {gap}"
26 | self.reducer.logger.error(msg)
27 | interim_write = max(10, int((len(segment) - gap) ** 0.5))
28 | interim_counter = 0
29 |
30 | i = 0
31 | j = i + gap
32 | while j < len(segment):
33 | lines_to_remove = [segment[i], segment[j]]
34 | temp_sectors = best_sectors.drop_lines(lines_to_remove)
35 |
36 | interesting, msg, _ = self.reducer._check_interesting(self.reducer.args, temp_sectors)
37 |
38 | # We've found a chunk of input and have _demonstrated_ its irrelevance,
39 | # empty that chunk, update best so far, and continue
40 | if interesting:
41 | best_sectors = temp_sectors
42 | segment = best_sectors.lines
43 | msg = "Reduction found: new input has " + str(len(best_sectors.lines)) + " lines"
44 | self.reducer.logger.error(msg)
45 | interim_counter += 1
46 | if interim_write == interim_counter:
47 | interim_counter = 0
48 | best_sectors.trim_empty_allegiances()
49 | self.write_files(best_sectors)
50 | else:
51 | i += 1
52 | j = i + gap
53 |
54 | if old_length > len(segment):
55 | best_sectors.trim_empty_allegiances()
56 | self.write_files(best_sectors)
57 | old_length = len(segment)
58 |
59 | gap += 1
60 |
61 | # now that the pass is done, update self.sectors with best reduction found
62 | self.reducer.sectors = best_sectors
63 |
64 | # At least one sector was shown to be irrelevant, write out the intermediate result
65 | if old_length > len(segment):
66 | self.reducer.sectors.trim_empty_allegiances()
67 | self.write_files()
68 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/xroute_routes_pass_1_4/Core.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2024-09-28T17:11:16-07:00
3 |
4 | # Core
5 | # 0,0
6 |
7 | # Name: Core
8 | # Name: Ukan (vi)
9 |
10 | # Abbreviation: Core
11 |
12 | # Milieu: M1105
13 |
14 | # Credits: Core sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984).
15 |
16 | # Source: Traveller 5 Second Survey
17 |
18 | # Subsector A: Apge
19 | # Subsector B: Perite
20 | # Subsector C: Ameros
21 | # Subsector D: Shinkan
22 | # Subsector E: Sanches
23 | # Subsector F: Mekee
24 | # Subsector G: Core
25 | # Subsector H: Kaskii
26 | # Subsector I: Bunkeria
27 | # Subsector J: Cemplas
28 | # Subsector K: Chant
29 | # Subsector L: Dingtra
30 | # Subsector M: Cadion
31 | # Subsector N: Ch'naar
32 | # Subsector O: Dunea
33 | # Subsector P: Saregon
34 |
35 | # Alleg: ImDc: "Third Imperium, Domain of Sylea"
36 | # Alleg: ImSy: "Third Imperium, Sylean Worlds"
37 |
38 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar
39 | ---- -------------------- --------- ------------------------------------- ------ ------- ------ ----- -- - --- -- ---- ---------------
40 | 2118 Capital A586A98-F Hi Cx (Syleans)5 Ht { 4 } (H9G+4) [AE5F] BEFG NW - 605 15 ImSy G2 V
41 | 2519 Riid Irman D000330-A As Lo Va { -1 } (920-5) [1215] B S - 604 7 ImDc G3 V
42 | 2821 Kuggar B552997-9 Hi Po { 2 } (F8C+2) [9B59] BE - - 304 7 ImDc M2 V
43 | 3123 Kankuup Ir B4247A8-B Pi { 3 } (E6D+3) [7A5B] BD NS - 814 10 ImDc M3 V
44 | 3226 Duuka B686576-B Ag Ni Ga Pr { 2 } (746+1) [474A] BcC N - 600 13 ImDc M2 V
45 | 3227 Ashash As B758677-A Ag Ni { 2 } (B56+2) [685A] BC - - 903 12 ImDc M2 V M6 V
46 | 2532 Mie Duur B310320-D Lo Da Ht { 2 } (822-2) [1518] B NS A 803 13 ImDc K6 V
47 | 2536 Duunpigamuur C511637-9 Ic Na Ni { -1 } (E53-1) [6559] B S - 624 9 ImDc K9 V M2 V
48 | 2639 Suurashuur B100403-E Ni Va Ht { 1 } (934-2) [152B] B S - 203 10 ImDc G9 V
49 | 2731 Milpa B6315AF-B Ni Po Da { 1 } (B45+5) [A6AG] B N A 404 14 ImDc G0 V
50 | 3040 Unlakhar B629997-E Hi In Ht { 4 } (E8G+4) [9D5E] BEf N - 603 10 ImDc K3 V
51 | 3131 Ashga A7C3874-D Fl Ph Ht { 2 } (E7D+1) [6A3B] Be - - 513 12 ImDc K2 V M6 V
52 |
--------------------------------------------------------------------------------
/PyRoute/downloadsec.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Jun 3, 2014
3 |
4 | @author: tjoneslo
5 | """
6 | import argparse
7 | import codecs
8 | import os
9 | import time
10 | import urllib.error
11 | import urllib.parse
12 | import urllib.request
13 |
14 |
15 | def get_url(url, sector, suffix, output_dir) -> bool:
16 | try:
17 | f = urllib.request.urlopen(url)
18 | except urllib.error.HTTPError as ex:
19 | print("get URL failed: {} -> {}".format(url, ex))
20 | return False
21 | except urllib.error.URLError as ex:
22 | print("get URL failed: {} -> {}".format(url, ex))
23 | return False
24 |
25 | encoding = f.headers['content-type'].split('charset=')[-1]
26 | content = f.read()
27 | if encoding == 'text/xml' or encoding == 'text/html':
28 | ucontent = str(content, 'utf-8').replace('\r\n', '\n')
29 | else:
30 | ucontent = str(content, encoding).replace('\r\n', '\n')
31 |
32 | path = os.path.join(output_dir, '%s.%s' % (sector, suffix))
33 |
34 | with codecs.open(path, 'wb', 'utf-8') as out:
35 | out.write(ucontent)
36 | f.close()
37 | return True
38 |
39 |
40 | if __name__ == '__main__':
41 |
42 | parser = argparse.ArgumentParser(description='Download sector/metadata from TravellerMap')
43 | parser.add_argument('--routes', dest='routes', default=False, action='store_true',
44 | help='Include route information in the sector downloads')
45 | parser.add_argument('sector_list', help='List of sectors to download')
46 | parser.add_argument('output_dir', help='output directory for sector data and xml metadata')
47 | parser.add_argument('--milieu', default="M1105", help="Milieu of data to download")
48 | args = parser.parse_args()
49 |
50 | with codecs.open(args.sector_list, 'r', encoding="utf-8") as f:
51 | sectorsList = [line for line in f]
52 |
53 | for raw_sector in sectorsList:
54 | sector = raw_sector.rstrip()
55 | print('Downloading %s' % sector)
56 | params = {'sector': sector, 'type': 'SecondSurvey', "milieu": args.milieu}
57 | if args.routes:
58 | params['routes'] = '1'
59 | url_params = urllib.parse.urlencode(params)
60 | url = 'http://www.travellermap.com/api/sec?%s' % url_params
61 |
62 | success = get_url(url, sector, 'sec', args.output_dir)
63 | if not success:
64 | print("Retrying " + sector)
65 | get_url(url, sector, 'sec', args.output_dir)
66 |
67 | url_params = urllib.parse.urlencode({'sector': sector, 'accept': 'text/xml'})
68 | url = 'http://travellermap.com/api/metadata?%s' % url_params
69 | success = get_url(url, sector, 'xml', args.output_dir)
70 | if not success:
71 | print("Retrying XML for " + sector)
72 | get_url(url, sector, 'sec', args.output_dir)
73 |
74 | time.sleep(5)
75 |
--------------------------------------------------------------------------------
/Tests/Pyfakefs/AreaItems/testGalaxy.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Nov 09, 2025
3 |
4 | @author: CyberiaResurrection
5 | """
6 | import os
7 |
8 | from PyRoute.AreaItems.Galaxy import Galaxy
9 | from PyRoute.DataClasses.ReadSectorOptions import ReadSectorOptions
10 | from Tests.Pyfakefs.baseTest import baseTest
11 |
12 |
13 | class testGalaxy(baseTest):
14 |
15 | def test_process_owned_worlds_1(self) -> None:
16 | sourcefile = self.unpack_filename('DeltaFiles/Zarushagar-Ibara.sec')
17 |
18 | args = self._make_args()
19 | args.routes = 'trade'
20 | args.route_btn = 15
21 | readparms = ReadSectorOptions(sectors=[sourcefile], pop_code=args.pop_code, ru_calc=args.ru_calc,
22 | route_reuse=args.route_reuse, trade_choice=args.routes, route_btn=args.route_btn,
23 | mp_threads=args.mp_threads, debug_flag=args.debug_flag, fix_pop=False,
24 | deep_space={}, map_type=args.map_type)
25 |
26 | galaxy = Galaxy(min_btn=15, max_jump=4)
27 | galaxy.read_sectors(readparms)
28 | galaxy.generate_routes()
29 | galaxy.output_path = args.output
30 | galaxy.set_borders(args.borders, args.ally_match)
31 |
32 | foostar = galaxy.star_mapping[24]
33 | foostar.ownedBy = 'Dagu-0'
34 |
35 | self.setUpPyfakefs(allow_root_user=False)
36 | namepath = args.output + '/owned-worlds-names.csv'
37 | listpath = args.output + '/owned-worlds-list.csv'
38 |
39 | galaxy.process_owned_worlds()
40 |
41 | self.assertTrue(os.path.exists(namepath))
42 | self.assertTrue(os.path.exists(listpath))
43 |
44 | expected_name = [
45 | '"Woden (Zarushagar 0306)", "Mr", "Strela (Zarushagar 0407)", "Imled '
46 | '(Zarushagar 0406)", "Miller\'s World (Zarushagar 0607)", "Makkus (Zarushagar 0304)"\n',
47 | '"Aslungi (Zarushagar 0605)", "None", "Gishin (Zarushagar 0804)", "Miller\'s '
48 | 'World (Zarushagar 0607)", "Strela (Zarushagar 0407)", "Imled (Zarushagar 0406)"\n',
49 | '"Airvae (Zarushagar 0801)", "Mr", "Gishin (Zarushagar 0804)", "Nedadzia (Zarushagar 0701)"\n',
50 | '"Ginshe (Zarushagar 0805)", "Gishin (Zarushagar 0804)", "Gishin (Zarushagar '
51 | '0804)", "Miller\'s World (Zarushagar 0607)", "Strela (Zarushagar 0407)", "Imled (Zarushagar 0406)"\n'
52 | ]
53 | expected_list = [
54 | '"Zaru", "0306", "Mr", "O:0407", "O:0406", "O:0607", "O:0304"\n',
55 | '"Zaru", "0605", "None", "O:0804", "O:0607", "O:0407", "O:0406"\n',
56 | '"Zaru", "0801", "Mr", "O:0804", "O:0701"\n',
57 | '"Zaru", "0805", "Gishin (Zarushagar 0804)", "O:0804", "O:0607", "O:0407", "O:0406"\n']
58 | with open(namepath, 'r', encoding='utf-8') as f:
59 | name_contents = f.readlines()
60 | self.assertEqual(expected_name, name_contents)
61 | with open(listpath, 'r', encoding='utf-8') as f:
62 | list_contents = f.readlines()
63 | self.assertEqual(expected_list, list_contents)
64 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/duplicate_node_blowup/Verge.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2023-12-23T00:59:47-08:00
3 |
4 | # Verge
5 | # -3,-1
6 |
7 | # Name: Verge
8 | # Name: Khtiyhkokaeiw (as)
9 | # Name: Shaakasi (vi)
10 |
11 | # Abbreviation: Verg
12 |
13 | # Milieu: M1105
14 |
15 | # Credits: Verge sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984).
16 |
17 | # Source: Traveller 5 Second Survey
18 |
19 | # Subsector A: Piimigeka
20 | # Subsector B: Draco Abyss
21 | # Subsector C: Mimiuudlika
22 | # Subsector D: Nuzuu
23 | # Subsector E: Shukhuarmiushruu
24 | # Subsector F: Dark Chasm
25 | # Subsector G: Biika
26 | # Subsector H: Miruu
27 | # Subsector I: Arkhukamier
28 | # Subsector J: Khukaruu
29 | # Subsector K: Likagemika
30 | # Subsector L: Khulikhu
31 | # Subsector M: Khuug
32 | # Subsector N: Inugzuu
33 | # Subsector O: Imkhuruuka
34 | # Subsector P: Naakhulige
35 |
36 | # Alleg: AsMw: "Aslan Hierate, single multiple-world clan dominates"
37 | # Alleg: AsTv: "Aslan Hierate, Tlaukhu vassal clan dominates"
38 | # Alleg: AsWc: "Aslan Hierate, single one-world clan dominates"
39 | # Alleg: CsIm: "Client state, Third Imperium"
40 | # Alleg: ImDi: "Third Imperium, Domain of Ilelish"
41 | # Alleg: NaHu: "Non-Aligned, Human-dominated"
42 |
43 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar
44 | ---- -------------------- --------- -------------------- ------ ------- ------ ---- -- - --- -- ---- --------------
45 | 2805 Lagi B436999-E Hi { 3 } (F8F+4) [AC6F] - - - 100 1 ImDi K9 V
46 | 2902 Joe's World E681105-8 Lo { -3 } (600-5) [1136] - - - 100 1 ImDi M1 V
47 | 2903 Serafim C538230-A Lo { 0 } (810-4) [1215] - - - 100 1 ImDi K5 V
48 | 2908 Tlaq B54A9DD-C Hi In Pz Wa { 4 } (C8F+5) [DD9G] - - - 100 1 ImDi K2 V
49 | 3210 Inkha C5769AA-A Hi In Pz { 3 } (H8D+5) [BC7C] - - - 100 1 ImDi K3 V
50 | 2519 Beraugaus B4338BC-C Na Ph Po Pz { 2 } (F7C+5) [BA8F] - - - 100 1 ImDi G2 V
51 | 2612 Tripolis B885A98-E Ga Hi { 4 } (H9G+4) [AE5E] - NW - 100 1 ImDi K9 V
52 | 2811 Sarlee A310746-C Na Pi { 2 } (C6C+1) [694B] - N - 100 1 ImDi G1 IV
53 | 2820 Ukuna C77A342-A Lo Wa { 0 } (920-4) [1316] - - - 100 1 ImDi M1 V
54 | 2911 Ksandi B432856-D Na Ph Po { 2 } (E7D+1) [7A4C] - - - 100 1 ImDi M1 V
55 | 2916 Uranion D4319BD-A Hi Na Po Pz { 1 } (D8B+5) [DA9E] - S - 100 1 ImDi M1 V
56 | 2919 Pippin A6377BB-D Pz { 2 } (D6D+4) [997F] - - - 100 1 ImDi M0 V
57 | 3113 Ellas B876355-C Lo { 1 } (521-1) [143A] - - - 100 1 ImDi K4 V
58 |
--------------------------------------------------------------------------------
/Tests/DeltaFiles/xroute_routes_pass_1_4/Massilia.sec:
--------------------------------------------------------------------------------
1 | # Generated by https://travellermap.com
2 | # 2024-09-28T17:21:53-07:00
3 |
4 | # Massilia
5 | # 0,-1
6 |
7 | # Name: Massilia
8 | # Name: Massila
9 | # Name: Masilaa (vi)
10 |
11 | # Abbreviation: Mass
12 |
13 | # Milieu: M1105
14 |
15 | # Credits: Massilia sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by Joe D. Fugate Sr. and appears The Travellers Digest and in Knightfall (GDW, 1990).
16 |
17 | # Source: Traveller 5 Second Survey
18 |
19 | # Subsector A: Kerr
20 | # Subsector B: Arar
21 | # Subsector C: Khisham
22 | # Subsector D: Zalucha
23 | # Subsector E: Keum
24 | # Subsector F: Vaait
25 | # Subsector G: Forquee
26 | # Subsector H: Palasha
27 | # Subsector I: Ten Suns
28 | # Subsector J: Shiwonee
29 | # Subsector K: Nes'Vra
30 | # Subsector L: Tooka
31 | # Subsector M: Annari
32 | # Subsector N: Shokee
33 | # Subsector O: Shimmer
34 | # Subsector P: Oreo
35 |
36 | # Alleg: ImDc: "Third Imperium, Domain of Sylea"
37 |
38 | Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar
39 | ---- -------------------- --------- ----------------------------- ------ ------- ------ ---- -- - --- -- ---- --------------
40 | 3203 Freelund E792743-6 He Pi { -2 } (965-5) [4523] BD - - 302 10 ImDc K3 V
41 | 3207 Hamagami B300245-D Lo Va Ht { 1 } (711-1) [133B] B - - 503 8 ImDc M3 V
42 | 0720 Amaya A100321-F Lo Va Ht { 1 } (B21-3) [141B] B - - 924 9 ImDc K9 III D
43 | 0815 Keum A67198A-E He Hi In Cs Ht { 4 } (F8G+5) [BD7G] BEF - - 704 16 ImDc K0 V M4 V
44 | 0816 Capson B551459-C Ni Po Ht { 1 } (C34+2) [556D] B - - 233 13 ImDc F7 V
45 | 3211 Porto B556AEC-E Hi Pz Ht { 3 } (H9F+5) [DD8H] BE - A 114 12 ImDc M1 V
46 | 3215 Remundo B889468-C Ni Mr Ht { 1 } (D34+1) [455C] B N - 325 10 ImDc G1 V
47 | 3219 Letycia B684113-B Lo Da { 1 } (701-2) [1228] B - A 904 10 ImDc F5 V
48 | 1122 Elemza E10046B-9 Ni Va O:1019 { -2 } (931+1) [627B] B - - 303 7 ImDc M1 III
49 | 1424 Mag' Lphr B100435-E Ni Va Ht { 2 } (935+1) [263C] B W - 703 8 ImDc M0 V
50 | 1428 Nindaro B9896BB-A Ni Ri Da { 2 } (856+4) [887C] BC - A 300 6 ImDc G0 V
51 | 1731 Myimpia B784202-B Lo { 2 } (812-2) [1417] B NS - 222 13 ImDc G2 V
52 | 2132 Honatty C545851-9 Pa Ph Pi { 0 } (E79-4) [4815] BcDe - - 104 10 ImDc F8 V
53 | 2531 Luong A500596-D Ni Va Da Ht { 1 } (C45+1) [464C] B - A 214 16 ImDc M3 V
54 | 2931 Amanita B100587-D Ni Va Ht { 1 } (A45+1) [565D] B - - 603 13 ImDc G1 V
55 |
--------------------------------------------------------------------------------
/PyRoute/AreaItems/Subsector.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on 21 Jul, 2024
3 |
4 | @author: CyberiaResurrection
5 | """
6 | from PyRoute.AreaItems.AreaItem import AreaItem
7 |
8 |
9 | class Subsector(AreaItem):
10 | def __init__(self, name, position, sector):
11 | super(Subsector, self).__init__(name)
12 | self.positions = ["ABCD", "EFGH", "IJKL", "MNOP"]
13 | self.sector = sector
14 | self.position = position
15 | self.spinward = None
16 | self.trailing = None
17 | self.coreward = None
18 | self.rimward = None
19 | self.dx = sector.dx
20 | self.dy = sector.dy
21 | self._wiki_name = Subsector.set_wiki_name(name, sector.name, position)
22 |
23 | # For the JSONPickel work
24 | def __getstate__(self):
25 | state = self.__dict__.copy()
26 | del state['sector']
27 | del state['spinward']
28 | del state['trailing']
29 | del state['coreward']
30 | del state['rimward']
31 | del state['alg_sorted']
32 | del state['positions']
33 | return state
34 |
35 | @staticmethod
36 | def set_wiki_name(name, sector_name, position) -> str:
37 | if len(name) == 0:
38 | return "{0} location {1}".format(sector_name, position)
39 | else:
40 | if "(" in name:
41 | return '[[{0} Subsector|{1}]]'.format(name, name[:-7])
42 | else:
43 | return '[[{0} Subsector|{0}]]'.format(name)
44 |
45 | def wiki_title(self) -> str:
46 | return '{0} - {1}'.format(self.wiki_name(), self.sector.wiki_name())
47 |
48 | def subsector_name(self) -> str:
49 | if len(self.name) == 0:
50 | return "Location {}".format(self.position)
51 | else:
52 | return self.name[:-9].strip() if self.name.endswith('Subsector') else self.name
53 |
54 | def set_bounding_subsectors(self) -> None:
55 | posrow = 0
56 | for row in self.positions:
57 | if self.position in row:
58 | pos = self.positions[posrow].index(self.position) # pragma: no mutate
59 | break
60 | posrow += 1
61 |
62 | if posrow == 0:
63 | self.coreward = self.sector.coreward.subsectors[self.positions[3][pos]] if self.sector.coreward else None
64 | else:
65 | self.coreward = self.sector.subsectors[self.positions[posrow - 1][pos]]
66 |
67 | if pos == 0:
68 | self.spinward = self.sector.spinward.subsectors[self.positions[posrow][3]] if self.sector.spinward else None
69 | else:
70 | self.spinward = self.sector.subsectors[self.positions[posrow][pos - 1]]
71 |
72 | if posrow == 3:
73 | self.rimward = self.sector.rimward.subsectors[self.positions[0][pos]] if self.sector.rimward else None
74 | else:
75 | self.rimward = self.sector.subsectors[self.positions[posrow + 1][pos]]
76 |
77 | if pos == 3:
78 | self.trailing = self.sector.trailing.subsectors[self.positions[posrow][0]] if self.sector.trailing else None
79 | else:
80 | self.trailing = self.sector.subsectors[self.positions[posrow][pos + 1]]
81 |
--------------------------------------------------------------------------------