-
Notifications
You must be signed in to change notification settings - Fork 0
Rebind TimeService to the loaded player and stats after save load #123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -110,6 +110,91 @@ def createGameForPersistence(data_directory): | |
| return game | ||
|
|
||
|
|
||
| def createGameThroughInit(data_directory, saveFiles): | ||
| # Run the real FishE.__init__ against a temp save slot holding exactly | ||
| # saveFiles ({filename: json-serializable}), with only the save-slot menu | ||
| # and the front-end stubbed out - everything else is the real wiring, so | ||
| # the load block and the state it hands to TimeService are exercised. | ||
| fishE.Player = Player | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This helper reassigns |
||
| fishE.Stats = Stats | ||
| fishE.TimeService = TimeService | ||
| fishE.Prompt = Prompt | ||
| fishE.PlayerJsonReaderWriter = PlayerJsonReaderWriter | ||
| fishE.StatsJsonReaderWriter = StatsJsonReaderWriter | ||
| fishE.TimeServiceJsonReaderWriter = TimeServiceJsonReaderWriter | ||
| fishE.SaveFileManager = SaveFileManager | ||
|
|
||
| slot = os.path.join(data_directory, "slot_1") | ||
| os.makedirs(slot, exist_ok=True) | ||
| for filename, contents in saveFiles.items(): | ||
| with open(os.path.join(slot, filename), "w") as f: | ||
| json.dump(contents, f) | ||
|
|
||
| config = Config() | ||
| config.dataDirectory = data_directory | ||
|
|
||
| def selectSlotOne(self): | ||
| self.saveFileManager.select_save_slot(1) | ||
|
|
||
| with patch.object(fishE, "Config", return_value=config), patch.object( | ||
| fishE, "UserInterfaceFactory", MagicMock() | ||
| ), patch.object(fishE.FishE, "_selectSaveFile", selectSlotOne): | ||
| return fishE.FishE() | ||
|
|
||
|
|
||
| def test_init_rebinds_timeService_to_loaded_player_without_timeService_file(): | ||
| with tempfile.TemporaryDirectory() as data_directory: | ||
| # prepare/call - a slot holding player.json and stats.json but no | ||
| # timeService.json, which is the shape migrate_old_save_files() produces | ||
| # from an old save that never had one | ||
| game = createGameThroughInit( | ||
| data_directory, | ||
| { | ||
| "player.json": PlayerJsonReaderWriter().createJsonFromPlayer(Player()), | ||
| "stats.json": StatsJsonReaderWriter().createJsonFromStats(Stats()), | ||
| }, | ||
| ) | ||
|
|
||
| # check - the TimeService drives the same objects the rest of the game | ||
| # uses, so daily interest/income/rent land on the loaded player | ||
| assert game.timeService.player is game.player | ||
| assert game.timeService.stats is game.stats | ||
|
|
||
|
|
||
| def test_init_rebinds_timeService_when_only_player_file_present(): | ||
| with tempfile.TemporaryDirectory() as data_directory: | ||
| # prepare/call - the shape left behind by a save interrupted after | ||
| # player.json was written but before stats.json/timeService.json | ||
| game = createGameThroughInit( | ||
| data_directory, | ||
| {"player.json": PlayerJsonReaderWriter().createJsonFromPlayer(Player())}, | ||
| ) | ||
|
|
||
| # check | ||
| assert game.timeService.player is game.player | ||
| assert game.timeService.stats is game.stats | ||
|
|
||
|
|
||
| def test_init_daily_tick_credits_the_loaded_player(): | ||
| with tempfile.TemporaryDirectory() as data_directory: | ||
| # prepare - a saved player with money in the bank, in a slot with no | ||
| # timeService.json | ||
| savedPlayer = Player() | ||
| savedPlayer.moneyInBank = 100 | ||
| game = createGameThroughInit( | ||
| data_directory, | ||
| {"player.json": PlayerJsonReaderWriter().createJsonFromPlayer(savedPlayer)}, | ||
| ) | ||
| moneyInBankBefore = game.player.moneyInBank | ||
|
|
||
| # call | ||
| game.timeService.increaseDay() | ||
|
|
||
| # check - bank interest reaches the player the game actually reads | ||
| assert game.player.moneyInBank > moneyInBankBefore | ||
| assert game.stats.moneyMadeFromInterest > 0 | ||
|
|
||
|
|
||
| def test_save_then_load_roundtrip(): | ||
| # restore real classes in case an earlier test mocked the module globals | ||
| fishE.Player = Player | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rebinding attributes here keeps the fix small, but it does leave two places that must agree about what the current player is (this block and the
TimeService.__init__that captured them). A more structural fix would be to defer constructingTimeServiceuntil after the load block entirely — it is only needed earlier becauseUserInterfaceFactory.create_user_interfacetakes it at line 44 so the save-file menu can render. Not worth the churn in this PR, and the comment above documents the coupling, but worth knowing if the load sequence is reworked later.