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"
windows-curses = {platform_system = "== 'Windows'"}
[dev-packages]
[scripts]
bone_market_solver = "python -m bonemarketsolver"
[requires]
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:
```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 \

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 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."""

View File

@ -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."""

View File

@ -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."""

View File

@ -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."""

View File

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

View File

@ -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."""

View File

@ -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."""

View File

@ -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."""

View File

@ -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."""

View File

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