Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ Build a fortune of **$10,000** in total wealth (cash on hand plus savings in the
### Fishing Business
Once you can afford it, buy a **boat** at the docks ("Manage Boat & Crew") and **hire workers**. Each day your crew brings in a passive catch in exchange for a daily wage — turning saved-up money into ongoing production. If you over-hire and can't make payroll, the workers you can't pay quit, so keep enough cash on hand to cover wages.

### Selling Fish
Sell your catch at the shop. The shop has a limited amount of money each day that refills overnight, so a very large haul may sell out the shop and need to be finished the next day — sell regularly, and park your earnings in the bank or reinvest them in gear and your crew.

### Multiple Save Files
FishE supports multiple save files, allowing you to maintain different game progressions simultaneously. When you start the game, you'll see a save file manager that displays:

Expand Down
72 changes: 59 additions & 13 deletions src/location/shop.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import random
from location.enum.locationType import LocationType
from player.player import Player
from prompt.prompt import Prompt
Expand All @@ -20,6 +19,12 @@
ROD_BASE_PRICE = 75
MAX_ROD_LEVEL = 10

# The shop has a limited pool of money for buying fish that refills each new day.
# It comfortably covers a normal day's catch but can be exhausted by a very large
# haul, so massive hoards must be sold over several days (and the bank/business
# give somewhere to put wealth in the meantime).
SHOP_DAILY_BUDGET = 750


def rodUpgradeCost(rodLevel):
return ROD_BASE_PRICE * rodLevel
Expand Down Expand Up @@ -84,6 +89,9 @@ def __init__(
}
]
)
# Daily budget for buying fish; refills when a new day begins.
self.money = SHOP_DAILY_BUDGET
self.lastRefillDay = self.timeService.day

def run(self):
li = [
Expand Down Expand Up @@ -114,21 +122,59 @@ def run(self):
self.currentPrompt.text = "What would you like to do?"
return LocationType.DOCKS

def _refillIfNewDay(self):
if self.timeService.day > self.lastRefillDay:
self.money = SHOP_DAILY_BUDGET
self.lastRefillDay = self.timeService.day

def sellFish(self):
if self.player.fishByType:
# Price each species individually (rarer fish are worth more).
moneyToAdd = 0
for fishTypeName, count in self.player.fishByType.items():
moneyToAdd += count * fish.fishValue(fishTypeName)
else:
# Legacy save with only an aggregate count: flat $3-5 per fish.
moneyToAdd = self.player.fishCount * random.randint(3, 5)
self._refillIfNewDay()

self.player.money += moneyToAdd
self.stats.totalMoneyMade += moneyToAdd
self.player.clearFish()
if self.player.fishCount == 0:
self.currentPrompt.text = "You have no fish to sell."
return

self.currentPrompt.text = "You sold all of your fish!"
# One entry per held fish. Sell the most valuable species first so the
# best fish are cashed in before the shop's daily budget runs out; any
# unaffordable leftovers stay in the inventory for another day.
if self.player.fishByType:
queue = []
for species, count in self.player.fishByType.items():
queue.extend([species] * count)
queue.sort(
key=lambda s: (fish.getFishType(s) or {}).get("maxValue", 0),
reverse=True,
)
else:
# Legacy save with only an aggregate count (no species breakdown).
queue = [None] * self.player.fishCount

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
self.player.money += value
self.stats.totalMoneyMade += value
earned += value
self._removeOneFish(species)

if self.player.fishCount > 0:
self.currentPrompt.text = (
"Sold fish for $%.2f, but the shop is out of money for today. "
"Come back tomorrow for the rest." % earned
)
else:
self.currentPrompt.text = "You sold your fish for $%.2f!" % earned

def _removeOneFish(self, species):
self.player.fishCount -= 1
if species is None:
return
self.player.fishByType[species] -= 1
if self.player.fishByType[species] == 0:
del self.player.fishByType[species]

def buyBetterBait(self):
if self.player.fishMultiplier >= MAX_FISH_MULTIPLIER:
Expand Down
51 changes: 51 additions & 0 deletions tests/location/test_shop.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,54 @@ def test_buyBetterRod_refused_at_cap():
assert shopInstance.player.rodLevel == MAX_ROD_LEVEL
assert shopInstance.player.money == 1000000
assert shopInstance.currentPrompt.text == "Your rod is already the finest in the village!"


def test_sellFish_limited_by_shop_budget():
# prepare - a haul worth far more than the shop's daily budget
from src.fish import fish
from src.location.shop import SHOP_DAILY_BUDGET

shopInstance = createShop()
shopInstance.player.money = 0
# 100 Marlins ($15-25 each) >> the budget, so the shop can't buy them all
shopInstance.player.addFish("Marlin", 100)

# call
shopInstance.sellFish()

# check - the shop spent (about) its whole budget and some fish remain unsold
assert shopInstance.player.money <= SHOP_DAILY_BUDGET
assert shopInstance.player.money > SHOP_DAILY_BUDGET - 25 # within one fish of the cap
assert shopInstance.money < 25 # budget nearly exhausted
assert shopInstance.player.fishCount > 0 # leftovers carried over
assert "out of money for today" in shopInstance.currentPrompt.text


def test_shop_budget_refills_next_day():
# prepare - exhaust the shop's budget
shopInstance = createShop()
shopInstance.player.addFish("Marlin", 100)
shopInstance.sellFish()
assert shopInstance.money < 25 # drained

# a new day begins
shopInstance.timeService.day += 1

# call - selling again first refills the budget for the new day
leftover_before = shopInstance.player.fishCount
shopInstance.sellFish()

# check - more fish sold (budget refilled), inventory shrank further
assert shopInstance.player.fishCount < leftover_before


def test_sellFish_no_fish_message():
# prepare
shopInstance = createShop()
shopInstance.player.clearFish()

# call
shopInstance.sellFish()

# check
assert shopInstance.currentPrompt.text == "You have no fish to sell."
Loading