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:
parent
b0075ab519
commit
84c62aa3c6
4
Pipfile
4
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"
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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))
|
|
@ -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."""
|
|
@ -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."""
|
|
@ -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."""
|
|
@ -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."""
|
|
@ -3,7 +3,7 @@ __author__ = "Jeremy Saklad"
|
|||
|
||||
from enum import Enum
|
||||
|
||||
from data.buyers import Buyer
|
||||
from .buyers import Buyer
|
||||
|
||||
DiplomatFascination = Enum(
|
||||
'DiplomatFascination',
|
|
@ -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."""
|
|
@ -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."""
|
|
@ -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."""
|
|
@ -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."""
|
|
@ -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()
|
Loading…
Reference in New Issue