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 \ | ||||
|  | ||||
							
								
								
									
										130
									
								
								bonemarketsolver/__main__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								bonemarketsolver/__main__.py
									
									
									
									
									
										Normal 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)) | ||||
| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user