From 84c62aa3c6fcfbe94733f5d3ecc1cc0d7b55d419 Mon Sep 17 00:00:00 2001 From: Jeremy Saklad Date: Tue, 3 Aug 2021 14:48:42 -0500 Subject: [PATCH] Convert project to package All scripts are now contained in a package named "bonemarketsolver". The command-line interface has been moved to __main__.py. The solver script has been moved to solve.py. Relative module imports are now used where appropriate. The invocation method of the CLI has changed: instead of running Python itself, you can now use "pipenv run bone_market_solver". The README has been updated to reflect the new usage method. --- Pipfile | 4 +- README.md | 4 +- bonemarketsolver/__main__.py | 130 +++++++++++++++ .../data}/adjustments.py | 4 +- {data => bonemarketsolver/data}/appendages.py | 4 +- {data => bonemarketsolver/data}/buyers.py | 4 +- {data => bonemarketsolver/data}/costs.py | 0 .../data}/declarations.py | 4 +- .../data}/diplomat_fascinations.py | 2 +- .../data}/embellishments.py | 4 +- .../data}/fluctuations.py | 0 .../data}/occasional_buyers.py | 2 +- {data => bonemarketsolver/data}/skulls.py | 4 +- {data => bonemarketsolver/data}/torsos.py | 4 +- .../objects}/action.py | 0 .../objects}/enumaction.py | 0 .../solve.py | 156 ++---------------- 17 files changed, 162 insertions(+), 164 deletions(-) create mode 100644 bonemarketsolver/__main__.py rename {data => bonemarketsolver/data}/adjustments.py (92%) rename {data => bonemarketsolver/data}/appendages.py (99%) rename {data => bonemarketsolver/data}/buyers.py (98%) rename {data => bonemarketsolver/data}/costs.py (100%) rename {data => bonemarketsolver/data}/declarations.py (96%) rename {data => bonemarketsolver/data}/diplomat_fascinations.py (92%) rename {data => bonemarketsolver/data}/embellishments.py (91%) rename {data => bonemarketsolver/data}/fluctuations.py (100%) rename {data => bonemarketsolver/data}/occasional_buyers.py (95%) rename {data => bonemarketsolver/data}/skulls.py (98%) rename {data => bonemarketsolver/data}/torsos.py (98%) rename {objects => bonemarketsolver/objects}/action.py (100%) rename {objects => bonemarketsolver/objects}/enumaction.py (100%) rename Bone Market Solver.py => bonemarketsolver/solve.py (93%) diff --git a/Pipfile b/Pipfile index 4e7cb20..c8ad4d2 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,9 @@ name = "pypi" ortools = "~=9.0" windows-curses = {platform_system = "== 'Windows'"} -[dev-packages] +[scripts] + +bone_market_solver = "python -m bonemarketsolver" [requires] python_version = "3.9" diff --git a/README.md b/README.md index 5d5bb43..cf2ceff 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ pipenv is not strictly necessary for this script, though it is highly recommende Having navigated to the directory that contains the script, you can see the options of the script using the following command: ```sh -pipenv run python Bone\ Market\ Solver.py --help +pipenv run bone_market_solver --help ``` These options allow you to specify world qualities, skeleton parameters, and solver options. @@ -31,7 +31,7 @@ These options do not need to be provided in any particular order, and generally Here's an example, broken into multiple lines for ease of reading: ```sh -pipenv run python Bone\ Market\ Solver.py \ +pipenv run bone_market_solver \ --bone-market-fluctuations menace \ --occasional-buyer an_enterprising_boot_salesman \ --zoological_mania insect \ diff --git a/bonemarketsolver/__main__.py b/bonemarketsolver/__main__.py new file mode 100644 index 0000000..1d58de5 --- /dev/null +++ b/bonemarketsolver/__main__.py @@ -0,0 +1,130 @@ +import argparse +import curses + +from .objects.enumaction import EnumAction +from .solve import * + +parser = argparse.ArgumentParser( + prog='Bone Market Solver', + description="Devise the optimal skeleton at the Bone Market in Fallen London.", + argument_default=argparse.SUPPRESS, + ) + + +world_qualities = parser.add_argument_group( + "world qualities", + "Parameters shared across Fallen London, often changing on a routine basis" + ) + +world_qualities.add_argument( + "-f", "--bone-market-fluctuations", + action=EnumAction, + type=Fluctuation, + help="current value of Bone Market Fluctuations, which grants various bonuses to certain buyers", + dest='bone_market_fluctuations' + ) + +world_qualities.add_argument( + "-m", "--zoological-mania", + action=EnumAction, + type=Declaration, + help="current value of Zoological Mania, which grants a percentage bonus to value for a certain declaration", + dest='zoological_mania' + ) + +world_qualities.add_argument( + "-o", "--occasional-buyer", + action=EnumAction, + type=OccasionalBuyer, + help="current value of Occasional Buyer, which allows access to a buyer that is not otherwise available", + dest='occasional_buyer' + ) + +world_qualities.add_argument( + "-d", "--diplomat-fascination", + action=EnumAction, + type=DiplomatFascination, + help="current value of The Diplomat's Current Fascination, which determines what the Trifling Diplomat is interested in", + dest='diplomat_fascination' + ) + + +skeleton_parameters = parser.add_argument_group( + "skeleton parameters", + "Parameters that determine what you want the solver to produce" + ) + +skeleton_parameters.add_argument( + "-s", "--shadowy", + type=int, + required=True, + help="the effective level of Shadowy used for selling to buyers", + dest='shadowy_level' + ) + +skeleton_parameters.add_argument( + "-b", "--buyer", "--desired-buyer", + action=EnumAction, + nargs='+', + type=Buyer, + help="specific buyer that skeleton should be designed for (if declared repeatedly, will choose from among those provided)", + dest='desired_buyers' + ) + +skeleton_parameters.add_argument( + "-c", "--cost", "--maximum-cost", + type=int, + help="maximum number of pennies that should be invested in skeleton", + dest='maximum_cost' + ) + +skeleton_parameters.add_argument( + "-e", "--exhaustion", "--maximum_exhaustion", + type=int, + help="maximum exhaustion that skeleton should generate", + dest='maximum_exhaustion' + ) + + +solver_options = parser.add_argument_group( + "solver options", + "Options that affect how the solver behaves" + ) + +solver_options.add_argument( + "-v", "--verbose", + action='store_true', + default=False, + help="whether the solver should output search progress rather than showing intermediate solutions", + dest='verbose' + ) + +solver_options.add_argument( + "-t", "--time-limit", + type=float, + help="maximum number of seconds that the solver runs for", + dest='time_limit' + ) + +solver_options.add_argument( + "-w", "--workers", + type=int, + help="number of search worker threads to run in parallel (default: one worker per available CPU thread)", + dest='workers' + ) + + +args = parser.parse_args() + +arguments = vars(args) + +if not arguments.pop('verbose'): + def WrappedSolve(stdscr, arguments): + # Prevents crash if window is too small to fit text + stdscr.scrollok(True) + # Move stdscr to last position + arguments['stdscr'] = stdscr + return Solve(**arguments) + print(curses.wrapper(WrappedSolve, arguments)) +else: + print(Solve(**arguments)) diff --git a/data/adjustments.py b/bonemarketsolver/data/adjustments.py similarity index 92% rename from data/adjustments.py rename to bonemarketsolver/data/adjustments.py index a184a44..6d59d01 100644 --- a/data/adjustments.py +++ b/bonemarketsolver/data/adjustments.py @@ -3,8 +3,8 @@ __author__ = "Jeremy Saklad" from enum import Enum -from data.costs import Cost -from objects.action import Action +from .costs import Cost +from ..objects.action import Action class Adjustment(Enum): """An action that is taken after all parts have been added to a skeleton.""" diff --git a/data/appendages.py b/bonemarketsolver/data/appendages.py similarity index 99% rename from data/appendages.py rename to bonemarketsolver/data/appendages.py index 55d2be0..f235413 100644 --- a/data/appendages.py +++ b/bonemarketsolver/data/appendages.py @@ -3,8 +3,8 @@ __author__ = "Jeremy Saklad" from enum import Enum -from data.costs import Cost -from objects.action import Action +from .costs import Cost +from ..objects.action import Action class Appendage(Enum): """An action that is taken once all skulls are added to a skeleton.""" diff --git a/data/buyers.py b/bonemarketsolver/data/buyers.py similarity index 98% rename from data/buyers.py rename to bonemarketsolver/data/buyers.py index 1bef732..bef0550 100644 --- a/data/buyers.py +++ b/bonemarketsolver/data/buyers.py @@ -3,8 +3,8 @@ __author__ = "Jeremy Saklad" from enum import Enum -from data.costs import Cost -from objects.action import Action +from .costs import Cost +from ..objects.action import Action class Buyer(Enum): """An action that converts a skeleton into revenue.""" diff --git a/data/costs.py b/bonemarketsolver/data/costs.py similarity index 100% rename from data/costs.py rename to bonemarketsolver/data/costs.py diff --git a/data/declarations.py b/bonemarketsolver/data/declarations.py similarity index 96% rename from data/declarations.py rename to bonemarketsolver/data/declarations.py index 7b09a19..2fc286a 100644 --- a/data/declarations.py +++ b/bonemarketsolver/data/declarations.py @@ -3,8 +3,8 @@ __author__ = "Jeremy Saklad" from enum import Enum -from data.costs import Cost -from objects.action import Action +from .costs import Cost +from ..objects.action import Action class Declaration(Enum): """An action that is taken after all adjustments have been made to a skeleton.""" diff --git a/data/diplomat_fascinations.py b/bonemarketsolver/data/diplomat_fascinations.py similarity index 92% rename from data/diplomat_fascinations.py rename to bonemarketsolver/data/diplomat_fascinations.py index 9a2cb86..8738230 100644 --- a/data/diplomat_fascinations.py +++ b/bonemarketsolver/data/diplomat_fascinations.py @@ -3,7 +3,7 @@ __author__ = "Jeremy Saklad" from enum import Enum -from data.buyers import Buyer +from .buyers import Buyer DiplomatFascination = Enum( 'DiplomatFascination', diff --git a/data/embellishments.py b/bonemarketsolver/data/embellishments.py similarity index 91% rename from data/embellishments.py rename to bonemarketsolver/data/embellishments.py index b66d4f1..138e073 100644 --- a/data/embellishments.py +++ b/bonemarketsolver/data/embellishments.py @@ -3,8 +3,8 @@ __author__ = "Jeremy Saklad" from enum import Enum -from data.costs import Cost -from objects.action import Action +from .costs import Cost +from ..objects.action import Action class Embellishment(Enum): """An action is taken after a declaration has been made for a skeleton.""" diff --git a/data/fluctuations.py b/bonemarketsolver/data/fluctuations.py similarity index 100% rename from data/fluctuations.py rename to bonemarketsolver/data/fluctuations.py diff --git a/data/occasional_buyers.py b/bonemarketsolver/data/occasional_buyers.py similarity index 95% rename from data/occasional_buyers.py rename to bonemarketsolver/data/occasional_buyers.py index 18a0708..7bbb008 100644 --- a/data/occasional_buyers.py +++ b/bonemarketsolver/data/occasional_buyers.py @@ -3,7 +3,7 @@ __author__ = "Jeremy Saklad" from enum import Enum -from data.buyers import Buyer +from .buyers import Buyer class OccasionalBuyer(Enum): """Which of several unusual buyers are available.""" diff --git a/data/skulls.py b/bonemarketsolver/data/skulls.py similarity index 98% rename from data/skulls.py rename to bonemarketsolver/data/skulls.py index 86e5802..f17e35f 100644 --- a/data/skulls.py +++ b/bonemarketsolver/data/skulls.py @@ -3,8 +3,8 @@ __author__ = "Jeremy Saklad" from enum import Enum -from data.costs import Cost -from objects.action import Action +from .costs import Cost +from ..objects.action import Action class Skull(Enum): """An action that is taken immediately after starting a skeleton.""" diff --git a/data/torsos.py b/bonemarketsolver/data/torsos.py similarity index 98% rename from data/torsos.py rename to bonemarketsolver/data/torsos.py index 0004933..c63bb14 100644 --- a/data/torsos.py +++ b/bonemarketsolver/data/torsos.py @@ -3,8 +3,8 @@ __author__ = "Jeremy Saklad" from enum import Enum -from data.costs import Cost -from objects.action import Action +from .costs import Cost +from ..objects.action import Action class Torso(Enum): """An action that initiates a skeleton.""" diff --git a/objects/action.py b/bonemarketsolver/objects/action.py similarity index 100% rename from objects/action.py rename to bonemarketsolver/objects/action.py diff --git a/objects/enumaction.py b/bonemarketsolver/objects/enumaction.py similarity index 100% rename from objects/enumaction.py rename to bonemarketsolver/objects/enumaction.py diff --git a/Bone Market Solver.py b/bonemarketsolver/solve.py similarity index 93% rename from Bone Market Solver.py rename to bonemarketsolver/solve.py index 90a5293..b9dfb67 100644 --- a/Bone Market Solver.py +++ b/bonemarketsolver/solve.py @@ -3,25 +3,22 @@ __all__ = ['Buyer', 'Declaration', 'DiplomatFascination', 'Fluctuation', 'OccasionalBuyer', 'Solve'] __author__ = "Jeremy Saklad" -import argparse -import curses from functools import reduce from os import cpu_count from ortools.sat.python import cp_model -from data.adjustments import Adjustment -from data.appendages import Appendage -from data.buyers import Buyer -from data.costs import Cost -from data.declarations import Declaration -from data.diplomat_fascinations import DiplomatFascination -from data.embellishments import Embellishment -from data.fluctuations import Fluctuation -from data.occasional_buyers import OccasionalBuyer -from data.skulls import Skull -from data.torsos import Torso -from objects.enumaction import EnumAction +from .data.adjustments import Adjustment +from .data.appendages import Appendage +from .data.buyers import Buyer +from .data.costs import Cost +from .data.declarations import Declaration +from .data.diplomat_fascinations import DiplomatFascination +from .data.embellishments import Embellishment +from .data.fluctuations import Fluctuation +from .data.occasional_buyers import OccasionalBuyer +from .data.skulls import Skull +from .data.torsos import Torso # This multiplier is applied to the profit margin to avoid losing precision due to rounding. PROFIT_MARGIN_MULTIPLIER = 10000000 @@ -1250,134 +1247,3 @@ def Solve(shadowy_level, bone_market_fluctuations = None, zoological_mania = Non raise RuntimeError("Unknown status returned: {}.".format(status)) return printer.PrintableSolution(solver) - - -def main(): - parser = argparse.ArgumentParser( - prog='Bone Market Solver', - description="Devise the optimal skeleton at the Bone Market in Fallen London.", - argument_default=argparse.SUPPRESS, - ) - - - world_qualities = parser.add_argument_group( - "world qualities", - "Parameters shared across Fallen London, often changing on a routine basis" - ) - - world_qualities.add_argument( - "-f", "--bone-market-fluctuations", - action=EnumAction, - type=Fluctuation, - help="current value of Bone Market Fluctuations, which grants various bonuses to certain buyers", - dest='bone_market_fluctuations' - ) - - world_qualities.add_argument( - "-m", "--zoological-mania", - action=EnumAction, - type=Declaration, - help="current value of Zoological Mania, which grants a percentage bonus to value for a certain declaration", - dest='zoological_mania' - ) - - world_qualities.add_argument( - "-o", "--occasional-buyer", - action=EnumAction, - type=OccasionalBuyer, - help="current value of Occasional Buyer, which allows access to a buyer that is not otherwise available", - dest='occasional_buyer' - ) - - world_qualities.add_argument( - "-d", "--diplomat-fascination", - action=EnumAction, - type=DiplomatFascination, - help="current value of The Diplomat's Current Fascination, which determines what the Trifling Diplomat is interested in", - dest='diplomat_fascination' - ) - - - skeleton_parameters = parser.add_argument_group( - "skeleton parameters", - "Parameters that determine what you want the solver to produce" - ) - - skeleton_parameters.add_argument( - "-s", "--shadowy", - type=int, - required=True, - help="the effective level of Shadowy used for selling to buyers", - dest='shadowy_level' - ) - - skeleton_parameters.add_argument( - "-b", "--buyer", "--desired-buyer", - action=EnumAction, - nargs='+', - type=Buyer, - help="specific buyer that skeleton should be designed for (if declared repeatedly, will choose from among those provided)", - dest='desired_buyers' - ) - - skeleton_parameters.add_argument( - "-c", "--cost", "--maximum-cost", - type=int, - help="maximum number of pennies that should be invested in skeleton", - dest='maximum_cost' - ) - - skeleton_parameters.add_argument( - "-e", "--exhaustion", "--maximum_exhaustion", - type=int, - help="maximum exhaustion that skeleton should generate", - dest='maximum_exhaustion' - ) - - - solver_options = parser.add_argument_group( - "solver options", - "Options that affect how the solver behaves" - ) - - solver_options.add_argument( - "-v", "--verbose", - action='store_true', - default=False, - help="whether the solver should output search progress rather than showing intermediate solutions", - dest='verbose' - ) - - solver_options.add_argument( - "-t", "--time-limit", - type=float, - help="maximum number of seconds that the solver runs for", - dest='time_limit' - ) - - solver_options.add_argument( - "-w", "--workers", - type=int, - help="number of search worker threads to run in parallel (default: one worker per available CPU thread)", - dest='workers' - ) - - - args = parser.parse_args() - - arguments = vars(args) - - if not arguments.pop('verbose'): - def WrappedSolve(stdscr, arguments): - # Prevents crash if window is too small to fit text - stdscr.scrollok(True) - # Move stdscr to last position - arguments['stdscr'] = stdscr - return Solve(**arguments) - print(curses.wrapper(WrappedSolve, arguments)) - else: - print(Solve(**arguments)) - - -if __name__ == '__main__': - main()