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"
|
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"
|
||||||
|
|
|
@ -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 \
|
||||||
|
|
|
@ -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 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."""
|
|
@ -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."""
|
|
@ -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."""
|
|
@ -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."""
|
|
@ -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',
|
|
@ -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."""
|
|
@ -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."""
|
|
@ -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."""
|
|
@ -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."""
|
|
@ -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()
|
|
Loading…
Reference in New Issue