diff --git a/elite_engineering/materials.py b/elite_engineering/materials.py new file mode 100644 index 0000000..127b115 --- /dev/null +++ b/elite_engineering/materials.py @@ -0,0 +1,171 @@ +import dataclasses +import enum +import fractions +import typing + +from returns.maybe import Maybe, Some, Nothing + +from elite_engineering import trade + + +class MaterialType(enum.Enum): + raw = "Raw" + manufactured = "Manufactured" + encoded = "Encoded" + + +categorized = { + MaterialType.raw: { + "Raw Material Category 1": ["Carbon", "Vanadium", "Niobium", "Yttrium"], + "Raw Material Category 2": [ + "Phosphorus", + "Chromium", + "Molybdenum", + "Technetium", + ], + "Raw Material Category 3": ["Sulphur", "Manganese", "Cadmium", "Ruthenium"], + "Raw Material Category 4": ["Iron", "Zinc", "Tin", "Selenium"], + "Raw Material Category 5": ["Nickel", "Germanium", "Tungsten", "Tellurium"], + "Raw Material Category 6": ["Rhenium", "Arsenic", "Mercury", "Polonium"], + "Raw Material Category 7": ["Lead", "Zirconium", "Boron", "Antimony"], + }, + MaterialType.manufactured: { + "Chemical": [ + "Chemical Storage Units", + "Chemical Processors", + "Chemical Distillery", + "Chemical Manipulators", + "Pharmaceutical Isolators", + ], + "Thermic": [ + "Tempered Alloys", + "Heat Resistant Ceramics", + "Precipitated Alloys", + "Thermic Alloys", + "Military Grade Alloys", + ], + "Heat": [ + "Heat Conduction Wiring", + "Heat Dispersion Plate", + "Heat Exchangers", + "Heat Vanes", + "Proto Heat Radiators", + ], + "Conductive": [ + "Basic Conductors", + "Conductive Components", + "Conductive Ceramics", + "Conductive Polymers", + "Biotech Conductors", + ], + "Mechanical Components": [ + "Mechanical Scrap", + "Mechanical Equipment", + "Mechanical Components", + "Configurable Components", + "Improvised Components", + ], + "Capacitors": [ + "Grid Resistors", + "Hybrid Capacitors", + "Electrochemical Arrays", + "Polymer Capacitors", + "Military Supercapacitors", + ], + "Shielding": [ + "Worn Shield Emitters", + "Shield Emitters", + "Shielding Sensors", + "Compound Shielding", + "Imperial Shielding", + ], + "Composite": [ + "Compact Composites", + "Filament Composites", + "High Density Composites", + "Proprietary Composites", + "Core Dynamics Composites", + ], + "Crystals": [ + "Crystal Shards", + "Flawed Focus Crystals", + "Focus Crystals", + "Refined Focus Crystals", + "Exquisite Focus Crystals", + ], + "Alloys": [ + "Salvaged Alloys", + "Galvanising Alloys", + "Phase Alloys", + "Proto Light Alloys", + "Proto Radiolic Alloys", + ], + }, + MaterialType.encoded: { + "Emission Data": [ + "Exceptional Scrambled Emission Data", + "Irregular Emission Data", + "Unexpected Emission Data", + "Decoded Emission Data", + "Abnormal Compact Emissions Data", + ], + "Wake Scans": [ + "Atypical Disrupted Wake Echoes", + "Anomalous FSD Telemetry", + "Strange Wake Solutions", + "Eccentric Hyperspace Trajectories", + "Datamined Wake Exceptions", + ], + "Shield Data": [ + "Distorted Shield Cycle Recordings", + "Inconsistent Shield Soak Analysis", + "Untypical Shield Scans", + "Aberrant Shield Pattern Analysis", + "Peculiar Shield Frequency Data", + ], + "Encryption Files": [ + "Unusual Encrypted Files", + "Tagged Encryption Codes", + "Open Symmetric Keys", + "Atypical Encryption Archives", + "Adaptive Encryptors Capture", + ], + "Data Archives": [ + "Anomalous Bulk Scan Data", + "Unidentified Scan Archives", + "Classified Scan Databanks", + "Divergent Scan Data", + "Classified Scan Fragment", + ], + "Encoded Firmware": [ + "Specialised Legacy Firmware", + "Modified Consumer Firmware", + "Cracked Industrial Firmware", + "Security Firmware Patch", + "Modified Embedded Firmware", + ], + }, +} + + +@dataclasses.dataclass +class Material: + name: str + type_: MaterialType + category: str + grade: int + + MAX_CAPACITY: int = 300 + + def trade_ratio(self, other: "Material") -> Maybe[fractions.Fraction]: + if self.type_ != other.type_: + return Nothing + return trade.ratio(self.grade, other.grade, self.category != other.category) + + +materials: typing.Dict[str, Material] = { + name: Material(name, type_, category, grade) + for type_, categories in categorized.items() + for category, names in categories.items() + for grade, name in enumerate(names) +} diff --git a/elite_engineering/trade.py b/elite_engineering/trade.py index 3b85004..5550e29 100644 --- a/elite_engineering/trade.py +++ b/elite_engineering/trade.py @@ -1,182 +1,21 @@ -import dataclasses -import enum import fractions -import typing -from returns.maybe import Maybe, Some, Nothing +from returns.maybe import Maybe, Nothing, Some + +MAX_CAPACITY: int = 300 -class MaterialType(enum.Enum): - raw = "Raw" - manufactured = "Manufactured" - encoded = "Encoded" - - -categorized = { - MaterialType.raw: { - "Raw Material Category 1": ["Carbon", "Vanadium", "Niobium", "Yttrium"], - "Raw Material Category 2": [ - "Phosphorus", - "Chromium", - "Molybdenum", - "Technetium", - ], - "Raw Material Category 3": ["Sulphur", "Manganese", "Cadmium", "Ruthenium"], - "Raw Material Category 4": ["Iron", "Zinc", "Tin", "Selenium"], - "Raw Material Category 5": ["Nickel", "Germanium", "Tungsten", "Tellurium"], - "Raw Material Category 6": ["Rhenium", "Arsenic", "Mercury", "Polonium"], - "Raw Material Category 7": ["Lead", "Zirconium", "Boron", "Antimony"], - }, - MaterialType.manufactured: { - "Chemical": [ - "Chemical Storage Units", - "Chemical Processors", - "Chemical Distillery", - "Chemical Manipulators", - "Pharmaceutical Isolators", - ], - "Thermic": [ - "Tempered Alloys", - "Heat Resistant Ceramics", - "Precipitated Alloys", - "Thermic Alloys", - "Military Grade Alloys", - ], - "Heat": [ - "Heat Conduction Wiring", - "Heat Dispersion Plate", - "Heat Exchangers", - "Heat Vanes", - "Proto Heat Radiators", - ], - "Conductive": [ - "Basic Conductors", - "Conductive Components", - "Conductive Ceramics", - "Conductive Polymers", - "Biotech Conductors", - ], - "Mechanical Components": [ - "Mechanical Scrap", - "Mechanical Equipment", - "Mechanical Components", - "Configurable Components", - "Improvised Components", - ], - "Capacitors": [ - "Grid Resistors", - "Hybrid Capacitors", - "Electrochemical Arrays", - "Polymer Capacitors", - "Military Supercapacitors", - ], - "Shielding": [ - "Worn Shield Emitters", - "Shield Emitters", - "Shielding Sensors", - "Compound Shielding", - "Imperial Shielding", - ], - "Composite": [ - "Compact Composites", - "Filament Composites", - "High Density Composites", - "Proprietary Composites", - "Core Dynamics Composites", - ], - "Crystals": [ - "Crystal Shards", - "Flawed Focus Crystals", - "Focus Crystals", - "Refined Focus Crystals", - "Exquisite Focus Crystals", - ], - "Alloys": [ - "Salvaged Alloys", - "Galvanising Alloys", - "Phase Alloys", - "Proto Light Alloys", - "Proto Radiolic Alloys", - ], - }, - MaterialType.encoded: { - "Emission Data": [ - "Exceptional Scrambled Emission Data", - "Irregular Emission Data", - "Unexpected Emission Data", - "Decoded Emission Data", - "Abnormal Compact Emissions Data", - ], - "Wake Scans": [ - "Atypical Disrupted Wake Echoes", - "Anomalous FSD Telemetry", - "Strange Wake Solutions", - "Eccentric Hyperspace Trajectories", - "Datamined Wake Exceptions", - ], - "Shield Data": [ - "Distorted Shield Cycle Recordings", - "Inconsistent Shield Soak Analysis", - "Untypical Shield Scans", - "Aberrant Shield Pattern Analysis", - "Peculiar Shield Frequency Data", - ], - "Encryption Files": [ - "Unusual Encrypted Files", - "Tagged Encryption Codes", - "Open Symmetric Keys", - "Atypical Encryption Archives", - "Adaptive Encryptors Capture", - ], - "Data Archives": [ - "Anomalous Bulk Scan Data", - "Unidentified Scan Archives", - "Classified Scan Databanks", - "Divergent Scan Data", - "Classified Scan Fragment", - ], - "Encoded Firmware": [ - "Specialised Legacy Firmware", - "Modified Consumer Firmware", - "Cracked Industrial Firmware", - "Security Firmware Patch", - "Modified Embedded Firmware", - ], - }, -} - - -@dataclasses.dataclass -class Material: - name: str - type_: MaterialType - category: str - grade: int - - MAX_CAPACITY: int = 300 - - def trade_ratio(self, other: "Material") -> Maybe[fractions.Fraction]: - if self.type_ != other.type_: - return Nothing - grade_base = 3 if self.grade <= other.grade else 6 - grade_ratio = fractions.Fraction( - grade_base ** self.grade, grade_base ** other.grade - ) - category_ratio = ( - fractions.Fraction(1, 1) - if self.category == other.category - else fractions.Fraction(6, 1) - ) - ratio = grade_ratio * category_ratio - if ratio.numerator > self.MAX_CAPACITY or ratio.denominator > self.MAX_CAPACITY: - return Nothing - else: - return Some(ratio) - - -materials: typing.Dict[str, Material] = { - name: Material(name, type_, category, grade) - for type_, categories in categorized.items() - for category, names in categories.items() - for grade, name in enumerate(names) -} +def ratio( + grade_in: int, grade_out: int, across_categories: bool +) -> Maybe[fractions.Fraction]: + trading_up = grade_out > grade_in + grade_base = 6 if trading_up else 3 + grade_ratio = fractions.Fraction(grade_base ** grade_out, grade_base ** grade_in) + category_ratio = ( + fractions.Fraction(6, 1) if across_categories else fractions.Fraction(1, 1) + ) + result = grade_ratio * category_ratio + if max(result.numerator, result.denominator) > MAX_CAPACITY: + return Nothing + else: + return Some(result) diff --git a/tests/test_materials.py b/tests/test_materials.py new file mode 100644 index 0000000..858d540 --- /dev/null +++ b/tests/test_materials.py @@ -0,0 +1,93 @@ +import fractions +import unittest + +from elite_engineering import materials + + +class TradeCalculationTests(unittest.TestCase): + def format_ratio(self, ratio: fractions.Fraction) -> str: + return "{} → {}".format(ratio.numerator, ratio.denominator) + + def test_same_category_exchange(self) -> None: + expected = [ + ["1 → 1", "1 → 3", "1 → 9", "1 → 27", "1 → 81"], + ["6 → 1", "1 → 1", "1 → 3", "1 → 9", "1 → 27"], + ["36 → 1", "6 → 1", "1 → 1", "1 → 3", "1 → 9"], + ["216 → 1", "36 → 1", "6 → 1", "1 → 1", "1 → 3"], + ["-", "216 → 1", "36 → 1", "6 → 1", "1 → 1"], + ] + + actual = [ + [ + materials.Material( + "name", materials.MaterialType.raw, "category", grade_in + ) + .trade_ratio( + materials.Material( + "name", materials.MaterialType.raw, "category", grade_out + ) + ) + .map(self.format_ratio) + .value_or("-") + for grade_in in [1, 2, 3, 4, 5] + ] + for grade_out in [1, 2, 3, 4, 5] + ] + + self.assertEqual(expected, actual) + + def test_different_category_exchange(self) -> None: + expected = [ + ["6 → 1", "2 → 1", "2 → 3", "2 → 9", "2 → 27"], + ["36 → 1", "6 → 1", "2 → 1", "2 → 3", "2 → 9"], + ["216 → 1", "36 → 1", "6 → 1", "2 → 1", "2 → 3"], + ["-", "216 → 1", "36 → 1", "6 → 1", "2 → 1"], + ["-", "-", "216 → 1", "36 → 1", "6 → 1"], + ] + + actual = [ + [ + materials.Material( + "name", materials.MaterialType.raw, "category", grade_in + ) + .trade_ratio( + materials.Material( + "name", materials.MaterialType.raw, "other", grade_out + ) + ) + .map(self.format_ratio) + .value_or("-") + for grade_in in [1, 2, 3, 4, 5] + ] + for grade_out in [1, 2, 3, 4, 5] + ] + + self.assertEqual(expected, actual) + + def test_different_type_exchange(self) -> None: + expected = [ + ["-", "-", "-", "-", "-"], + ["-", "-", "-", "-", "-"], + ["-", "-", "-", "-", "-"], + ["-", "-", "-", "-", "-"], + ["-", "-", "-", "-", "-"], + ] + + actual = [ + [ + materials.Material( + "name", materials.MaterialType.raw, "category", grade_in + ) + .trade_ratio( + materials.Material( + "name", materials.MaterialType.encoded, "other", grade_out + ) + ) + .map(self.format_ratio) + .value_or("-") + for grade_in in [1, 2, 3, 4, 5] + ] + for grade_out in [1, 2, 3, 4, 5] + ] + + self.assertEqual(expected, actual) diff --git a/tests/test_trade.py b/tests/test_trade.py index 599beaa..6851789 100644 --- a/tests/test_trade.py +++ b/tests/test_trade.py @@ -1,9 +1,6 @@ import fractions import unittest -from returns.curry import partial -from returns.maybe import Maybe - from elite_engineering import trade @@ -20,21 +17,14 @@ class TradeCalculationTests(unittest.TestCase): ["-", "216 → 1", "36 → 1", "6 → 1", "1 → 1"], ] - material = partial( - trade.Material, - "name", - trade.MaterialType.raw, - "category", - ) actual = [ [ - material(grade_in) - .trade_ratio(material(grade_out)) + trade.ratio(grade_in, grade_out, across_categories=False) .map(self.format_ratio) .value_or("-") - for grade_out in [1, 2, 3, 4, 5] + for grade_in in [1, 2, 3, 4, 5] ] - for grade_in in [1, 2, 3, 4, 5] + for grade_out in [1, 2, 3, 4, 5] ] self.assertEqual(expected, actual) @@ -48,41 +38,14 @@ class TradeCalculationTests(unittest.TestCase): ["-", "-", "216 → 1", "36 → 1", "6 → 1"], ] - type_ = trade.MaterialType.raw - material = partial(trade.Material, "name", trade.MaterialType.raw) actual = [ [ - material("category", grade_in) - .trade_ratio(material("other", grade_out)) + trade.ratio(grade_in, grade_out, across_categories=True) .map(self.format_ratio) .value_or("-") - for grade_out in [1, 2, 3, 4, 5] + for grade_in in [1, 2, 3, 4, 5] ] - for grade_in in [1, 2, 3, 4, 5] - ] - - self.assertEqual(expected, actual) - - def test_different_type_exchange(self) -> None: - expected = [ - ["-", "-", "-", "-", "-"], - ["-", "-", "-", "-", "-"], - ["-", "-", "-", "-", "-"], - ["-", "-", "-", "-", "-"], - ["-", "-", "-", "-", "-"], - ] - - type_ = trade.MaterialType.raw - material = partial(trade.Material, "name") - actual = [ - [ - material(trade.MaterialType.raw, "category", grade_in) - .trade_ratio(material(trade.MaterialType.encoded, "other", grade_out)) - .map(self.format_ratio) - .value_or("-") - for grade_out in [1, 2, 3, 4, 5] - ] - for grade_in in [1, 2, 3, 4, 5] + for grade_out in [1, 2, 3, 4, 5] ] self.assertEqual(expected, actual)