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.
This commit is contained in:
Jeremy Saklad 2021-08-03 14:48:42 -05:00
parent b0075ab519
commit 84c62aa3c6
Signed by: Jeremy Saklad
GPG Key ID: 9CA2149583EDBF84
17 changed files with 162 additions and 164 deletions

View File

@ -7,7 +7,9 @@ name = "pypi"
ortools = "~=9.0" ortools = "~=9.0"
windows-curses = {platform_system = "== 'Windows'"} windows-curses = {platform_system = "== 'Windows'"}
[dev-packages] [scripts]
bone_market_solver = "python -m bonemarketsolver"
[requires] [requires]
python_version = "3.9" python_version = "3.9"

View File

@ -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: Having navigated to the directory that contains the script, you can see the options of the script using the following command:
```sh ```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. 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: Here's an example, broken into multiple lines for ease of reading:
```sh ```sh
pipenv run python Bone\ Market\ Solver.py \ pipenv run bone_market_solver \
--bone-market-fluctuations menace \ --bone-market-fluctuations menace \
--occasional-buyer an_enterprising_boot_salesman \ --occasional-buyer an_enterprising_boot_salesman \
--zoological_mania insect \ --zoological_mania insect \

View File

@ -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))

View File

@ -3,8 +3,8 @@ __author__ = "Jeremy Saklad"
from enum import Enum from enum import Enum
from data.costs import Cost from .costs import Cost
from objects.action import Action from ..objects.action import Action
class Adjustment(Enum): class Adjustment(Enum):
"""An action that is taken after all parts have been added to a skeleton.""" """An action that is taken after all parts have been added to a skeleton."""

View File

@ -3,8 +3,8 @@ __author__ = "Jeremy Saklad"
from enum import Enum from enum import Enum
from data.costs import Cost from .costs import Cost
from objects.action import Action from ..objects.action import Action
class Appendage(Enum): class Appendage(Enum):
"""An action that is taken once all skulls are added to a skeleton.""" """An action that is taken once all skulls are added to a skeleton."""

View File

@ -3,8 +3,8 @@ __author__ = "Jeremy Saklad"
from enum import Enum from enum import Enum
from data.costs import Cost from .costs import Cost
from objects.action import Action from ..objects.action import Action
class Buyer(Enum): class Buyer(Enum):
"""An action that converts a skeleton into revenue.""" """An action that converts a skeleton into revenue."""

View File

@ -3,8 +3,8 @@ __author__ = "Jeremy Saklad"
from enum import Enum from enum import Enum
from data.costs import Cost from .costs import Cost
from objects.action import Action from ..objects.action import Action
class Declaration(Enum): class Declaration(Enum):
"""An action that is taken after all adjustments have been made to a skeleton.""" """An action that is taken after all adjustments have been made to a skeleton."""

View File

@ -3,7 +3,7 @@ __author__ = "Jeremy Saklad"
from enum import Enum from enum import Enum
from data.buyers import Buyer from .buyers import Buyer
DiplomatFascination = Enum( DiplomatFascination = Enum(
'DiplomatFascination', 'DiplomatFascination',

View File

@ -3,8 +3,8 @@ __author__ = "Jeremy Saklad"
from enum import Enum from enum import Enum
from data.costs import Cost from .costs import Cost
from objects.action import Action from ..objects.action import Action
class Embellishment(Enum): class Embellishment(Enum):
"""An action is taken after a declaration has been made for a skeleton.""" """An action is taken after a declaration has been made for a skeleton."""

View File

@ -3,7 +3,7 @@ __author__ = "Jeremy Saklad"
from enum import Enum from enum import Enum
from data.buyers import Buyer from .buyers import Buyer
class OccasionalBuyer(Enum): class OccasionalBuyer(Enum):
"""Which of several unusual buyers are available.""" """Which of several unusual buyers are available."""

View File

@ -3,8 +3,8 @@ __author__ = "Jeremy Saklad"
from enum import Enum from enum import Enum
from data.costs import Cost from .costs import Cost
from objects.action import Action from ..objects.action import Action
class Skull(Enum): class Skull(Enum):
"""An action that is taken immediately after starting a skeleton.""" """An action that is taken immediately after starting a skeleton."""

View File

@ -3,8 +3,8 @@ __author__ = "Jeremy Saklad"
from enum import Enum from enum import Enum
from data.costs import Cost from .costs import Cost
from objects.action import Action from ..objects.action import Action
class Torso(Enum): class Torso(Enum):
"""An action that initiates a skeleton.""" """An action that initiates a skeleton."""

View File

@ -3,25 +3,22 @@
__all__ = ['Buyer', 'Declaration', 'DiplomatFascination', 'Fluctuation', 'OccasionalBuyer', 'Solve'] __all__ = ['Buyer', 'Declaration', 'DiplomatFascination', 'Fluctuation', 'OccasionalBuyer', 'Solve']
__author__ = "Jeremy Saklad" __author__ = "Jeremy Saklad"
import argparse
import curses
from functools import reduce from functools import reduce
from os import cpu_count from os import cpu_count
from ortools.sat.python import cp_model from ortools.sat.python import cp_model
from data.adjustments import Adjustment from .data.adjustments import Adjustment
from data.appendages import Appendage from .data.appendages import Appendage
from data.buyers import Buyer from .data.buyers import Buyer
from data.costs import Cost from .data.costs import Cost
from data.declarations import Declaration from .data.declarations import Declaration
from data.diplomat_fascinations import DiplomatFascination from .data.diplomat_fascinations import DiplomatFascination
from data.embellishments import Embellishment from .data.embellishments import Embellishment
from data.fluctuations import Fluctuation from .data.fluctuations import Fluctuation
from data.occasional_buyers import OccasionalBuyer from .data.occasional_buyers import OccasionalBuyer
from data.skulls import Skull from .data.skulls import Skull
from data.torsos import Torso from .data.torsos import Torso
from objects.enumaction import EnumAction
# This multiplier is applied to the profit margin to avoid losing precision due to rounding. # This multiplier is applied to the profit margin to avoid losing precision due to rounding.
PROFIT_MARGIN_MULTIPLIER = 10000000 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)) raise RuntimeError("Unknown status returned: {}.".format(status))
return printer.PrintableSolution(solver) 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()