From 67c3ab941476b13d2577e6bfa25f0a633b912472 Mon Sep 17 00:00:00 2001 From: Daniel McCoy Stephenson Date: Sat, 4 Jul 2026 12:34:15 -0600 Subject: [PATCH 1/2] Add operator mode for testing without money constraints Adds a Player.operatorMode flag (enabled via FISHE_OPERATOR_MODE env var) with canAfford()/spendMoney() helpers that bypass every money gate across the shop, docks, tavern, bank, and business systems, so testers can buy gear, hire crew, and gamble without needing cash. Not persisted to saves. Surfaces an [OPERATOR MODE] indicator in all three front-ends. Co-Authored-By: Claude Sonnet 5 --- src/business/business.py | 7 +++++-- src/location/bank.py | 6 +++--- src/location/docks.py | 4 ++-- src/location/shop.py | 8 ++++---- src/location/tavern.py | 10 +++++----- src/player/player.py | 20 ++++++++++++++++++++ src/ui/pygameUserInterface.py | 4 +++- src/ui/userInterface.py | 2 ++ src/ui/webUserInterface.py | 3 +++ 9 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/business/business.py b/src/business/business.py index bd22448..1900e1f 100644 --- a/src/business/business.py +++ b/src/business/business.py @@ -27,7 +27,10 @@ def runDailyProduction(player, stats=None): if not player.hasBoat or player.workers <= 0: return summary - affordable = min(player.workers, int(player.money // WORKER_DAILY_WAGE)) + if player.operatorMode: + affordable = player.workers + else: + affordable = min(player.workers, int(player.money // WORKER_DAILY_WAGE)) if affordable < player.workers: summary["quit"] = player.workers - affordable player.workers = affordable @@ -37,7 +40,7 @@ def runDailyProduction(player, stats=None): return summary wages = affordable * WORKER_DAILY_WAGE - player.money -= wages + player.spendMoney(wages) # Each worker fishes the same waters as the player, landing a rarity-rolled # species (not just the cheapest one), so the crew's income is competitive # with simply upgrading your own gear. diff --git a/src/location/bank.py b/src/location/bank.py index 688075f..f44935d 100644 --- a/src/location/bank.py +++ b/src/location/bank.py @@ -78,7 +78,7 @@ def run(self): ) if input == "1": - if self.player.money > 0: + if self.player.operatorMode or self.player.money > 0: self.currentPrompt.text = ( "How much would you like to deposit? Money: $%.2f" % self.player.money @@ -114,9 +114,9 @@ def deposit(self): self.currentPrompt.text = "Try again. Money: $%.2f" % self.player.money continue - if amount <= self.player.money: + if self.player.canAfford(amount): self.player.moneyInBank += amount - self.player.money -= amount + self.player.spendMoney(amount) self.currentPrompt.text = "$%.2f deposited successfully." % amount else: diff --git a/src/location/docks.py b/src/location/docks.py index 238ae5d..a664054 100644 --- a/src/location/docks.py +++ b/src/location/docks.py @@ -176,8 +176,8 @@ def manageBusiness(self): action = actions[choice - 1] if action == "buy_boat": - if self.player.money >= business.BOAT_PRICE: - self.player.money -= business.BOAT_PRICE + if self.player.canAfford(business.BOAT_PRICE): + self.player.spendMoney(business.BOAT_PRICE) self.player.hasBoat = True self.currentPrompt.text = "You bought a boat! Now hire a crew." else: diff --git a/src/location/shop.py b/src/location/shop.py index 059eb72..6fcffc5 100644 --- a/src/location/shop.py +++ b/src/location/shop.py @@ -179,11 +179,11 @@ def _removeOneFish(self, species): def buyBetterBait(self): if self.player.fishMultiplier >= MAX_FISH_MULTIPLIER: self.currentPrompt.text = "Your bait is already the best money can buy!" - elif self.player.money < self.player.priceForBait: + elif not self.player.canAfford(self.player.priceForBait): self.currentPrompt.text = "You don't have enough money!" else: self.player.fishMultiplier += 1 - self.player.money -= self.player.priceForBait + self.player.spendMoney(self.player.priceForBait) self.player.priceForBait = self.player.priceForBait * 1.25 self.currentPrompt.text = "You bought some better bait!" @@ -192,11 +192,11 @@ def buyBetterRod(self): cost = rodUpgradeCost(self.player.rodLevel) if self.player.rodLevel >= MAX_ROD_LEVEL: self.currentPrompt.text = "Your rod is already the finest in the village!" - elif self.player.money < cost: + elif not self.player.canAfford(cost): self.currentPrompt.text = "You don't have enough money!" else: self.player.rodLevel += 1 - self.player.money -= cost + self.player.spendMoney(cost) self.currentPrompt.text = "You bought a better fishing rod!" def talkToNPC(self): diff --git a/src/location/tavern.py b/src/location/tavern.py index 3f5097a..541fcfa 100644 --- a/src/location/tavern.py +++ b/src/location/tavern.py @@ -92,7 +92,7 @@ def run(self): ) if input == "1": - if self.player.money >= 10: + if self.player.canAfford(10): self.getDrunk() return LocationType.HOME else: @@ -118,7 +118,7 @@ def getDrunk(self): self.userInterface.lotsOfSpace() self.userInterface.divider() - self.player.money -= 10 + self.player.spendMoney(10) for i in range(3): print("... ") @@ -136,7 +136,7 @@ def getDrunk(self): loss_percentage = random.uniform(0.1, 0.5) money_lost = int(self.player.money * loss_percentage) if money_lost > 0: - self.player.money -= money_lost + self.player.spendMoney(money_lost) self.stats.moneyLostWhileDrunk += money_lost self.currentPrompt.text = f"You have a headache. In your drunken stupor, you lost ${money_lost}!" else: @@ -182,7 +182,7 @@ def gamble(self): ) continue else: - self.player.money -= self.currentBet + self.player.spendMoney(self.currentBet) self.stats.moneyLostFromGambling += self.currentBet self.currentBet = 0 self.currentPrompt.text = ( @@ -210,7 +210,7 @@ def changeBet(self, prompt): return self.amount = int(amount) - if self.amount <= self.player.money: + if self.player.canAfford(self.amount): self.currentBet = self.amount self.currentPrompt.text = ( diff --git a/src/player/player.py b/src/player/player.py index b9e0a71..742042d 100644 --- a/src/player/player.py +++ b/src/player/player.py @@ -1,3 +1,6 @@ +import os + + # @author Daniel McCoy Stephenson class Player: def __init__(self): @@ -15,6 +18,23 @@ def __init__(self): # daily catch for a daily wage (see src/business). self.hasBoat = False self.workers = 0 + # Testing/debug cheat: when on, every money check passes and spendMoney + # becomes a no-op, so cash never actually runs out. Not persisted to save + # files - it's a runtime toggle, not game progress. Defaults from the + # FISHE_OPERATOR_MODE env var so it can be turned on for a whole session + # (e.g. `FISHE_OPERATOR_MODE=1 python3 src/fishE.py`) without code changes. + self.operatorMode = os.environ.get("FISHE_OPERATOR_MODE", "").lower() in ( + "1", + "true", + "yes", + ) + + def canAfford(self, cost): + return self.operatorMode or self.money >= cost + + def spendMoney(self, cost): + if not self.operatorMode: + self.money -= cost def addFish(self, fishTypeName, amount): self.fishByType[fishTypeName] = self.fishByType.get(fishTypeName, 0) + amount diff --git a/src/ui/pygameUserInterface.py b/src/ui/pygameUserInterface.py index 9dc7de9..7cbf76c 100644 --- a/src/ui/pygameUserInterface.py +++ b/src/ui/pygameUserInterface.py @@ -154,7 +154,9 @@ def _draw_game_screen(self, descriptor, optionList): f"Money: ${self.player.money}", f"Fish: {self.player.fishCount}" ] - + if self.player.operatorMode: + status_lines.append("[OPERATOR MODE]") + status_x = margin_x + (self.width * 0.06) # Indent status lines line_height = self.height * 0.05 # 5% of screen height per line diff --git a/src/ui/userInterface.py b/src/ui/userInterface.py index a151169..7a11a04 100644 --- a/src/ui/userInterface.py +++ b/src/ui/userInterface.py @@ -42,6 +42,8 @@ def showOptions( print(" | Money: $%.2f" % self.player.money) print(" | Fish: %d" % self.player.fishCount) print(" | Energy: %d" % self.player.energy) + if self.player.operatorMode: + print(" | [OPERATOR MODE]") if self.goalProgress: print(" | Goal: " + self.goalProgress) print("\n " + self.currentPrompt.text) diff --git a/src/ui/webUserInterface.py b/src/ui/webUserInterface.py index 589bc36..89dc964 100644 --- a/src/ui/webUserInterface.py +++ b/src/ui/webUserInterface.py @@ -46,6 +46,7 @@ .controls { font-size: .8rem; color: #6a8aa0; border-top: 1px solid #2a4a5a; margin-top: 1.5rem; padding-top: .5rem; } .low { color: #ff8a8a; font-weight: bold; } + .operator { color: #ffcf8f; font-weight: bold; } .notice { color: #9fd0ff; margin-top: 1rem; } .notice.warning { color: #ffcf8f; border-left: 3px solid #c77b2a; padding-left: .6rem; } @@ -119,6 +120,7 @@ addPart(energy); if (h.location) addPart(h.location); if (h.goal) addPart(`Goal: ${h.goal}`); + if (h.operator) addPart(el("span", { textContent: "OPERATOR MODE", className: "operator" })); app.append(header); document.title = `FishE — Day ${h.day}, $${h.money.toFixed(2)}`; } @@ -282,6 +284,7 @@ def _header(self): "energy": self.player.energy, "location": self.currentLocationName, "goal": self.goalProgress, + "operator": self.player.operatorMode, } # --- BaseUserInterface primitives ------------------------------------ From 94876115ba4efeeaec79d801a55770d06b09df54 Mon Sep 17 00:00:00 2001 From: Daniel McCoy Stephenson Date: Sat, 4 Jul 2026 12:46:37 -0600 Subject: [PATCH 2/2] Extend operator mode to bypass energy and shop budget gates Adds Player.hasEnergy()/spendEnergy() (mirroring canAfford/spendMoney) so fishing works regardless of energy in operator mode, and skips the shop's daily buy-budget check when selling fish, so testers can fish and sell without running into either resource gate. --- src/location/docks.py | 6 +++--- src/location/shop.py | 7 ++++--- src/player/player.py | 7 +++++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/location/docks.py b/src/location/docks.py index a664054..3b41ce9 100644 --- a/src/location/docks.py +++ b/src/location/docks.py @@ -99,7 +99,7 @@ def run(self): ) if input == "1": - if self.player.energy >= 10: + if self.player.hasEnergy(10): self.fish() return LocationType.DOCKS else: @@ -222,7 +222,7 @@ def fish(self): # Check if player has enough energy for all hours energy_needed = hours * 10 - if self.player.energy < energy_needed: + if not self.player.hasEnergy(energy_needed): # Fish for as many hours as energy allows hours = self.player.energy // 10 if hours == 0: @@ -250,7 +250,7 @@ def fish(self): for i in range(hours): self.stats.hoursSpentFishing += 1 self.timeService.increaseTime() - self.player.energy -= 10 # Consume 10 energy per hour + self.player.spendEnergy(10) # Consume 10 energy per hour baseFish = random.randint(1, 10) fishToAdd = int(baseFish * quality * self.player.fishMultiplier * timeFactor) diff --git a/src/location/shop.py b/src/location/shop.py index 6fcffc5..0526072 100644 --- a/src/location/shop.py +++ b/src/location/shop.py @@ -152,9 +152,10 @@ def sellFish(self): earned = 0.0 for species in queue: value = fish.fishValue(species) - if self.money < value: - break # shop is out of money for today - self.money -= value + if not self.player.operatorMode: + if self.money < value: + break # shop is out of money for today + self.money -= value self.player.money += value self.stats.totalMoneyMade += value earned += value diff --git a/src/player/player.py b/src/player/player.py index 742042d..875e1b1 100644 --- a/src/player/player.py +++ b/src/player/player.py @@ -36,6 +36,13 @@ def spendMoney(self, cost): if not self.operatorMode: self.money -= cost + def hasEnergy(self, amount): + return self.operatorMode or self.energy >= amount + + def spendEnergy(self, amount): + if not self.operatorMode: + self.energy -= amount + def addFish(self, fishTypeName, amount): self.fishByType[fishTypeName] = self.fishByType.get(fishTypeName, 0) + amount self.fishCount += amount