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
35 changes: 28 additions & 7 deletions src/location/bank.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
from location.enum.locationType import LocationType
from player.player import Player
from prompt.prompt import Prompt
from world.timeService import TimeService
from world.timeService import TimeService, INTEREST_RATE, MAX_INTEREST_PER_DAY
from stats.stats import Stats
from ui.userInterface import UserInterface
from npc.npc import NPC
from business import business
from investments import investments
from progression import progression

# The break-even point past which the daily interest cap binds - used so the
# teller's dialogue can state the real rule instead of drifting from it (see
# src/world/timeService.py for the underlying constants).
_INTEREST_CAP_BALANCE = int(MAX_INTEREST_PER_DAY / INTEREST_RATE)


# @author Daniel McCoy Stephenson
class Bank:
Expand Down Expand Up @@ -42,16 +47,20 @@ def __init__(
"response": "The bank is simple and safe! You can deposit money when you have some on hand, "
"and withdraw it whenever you need. We keep your money secure - "
"no risk of losing it to gambling or spending it accidentally! "
"Plus, your savings earn interest over time. The more you save, the more you earn. "
"It's the smart way to grow your wealth!",
"Plus, your savings earn interest over time - up to $%d a day. "
"It's the smart way to grow your wealth!" % MAX_INTEREST_PER_DAY,
},
{
"question": "Tell me about interest rates.",
"response": "Ah yes, interest! Every day that passes, your savings grow by a small percentage. "
"It might not seem like much at first, but over time it really adds up! "
"response": "Ah yes, interest! Every day that passes, your savings grow by %d%%, "
"up to $%d a day - past about $%d banked you've maxed out what I can pay you. "
"The interest is automatically added to your bank account. "
"Think of it as the bank paying you for keeping your money with us. "
"The more you save, the more interest you earn!",
"Think of it as the bank paying you for keeping your money with us."
% (
int(INTEREST_RATE * 100),
MAX_INTEREST_PER_DAY,
_INTEREST_CAP_BALANCE,
),
},
{
"question": "Should I save or spend my money?",
Expand Down Expand Up @@ -222,6 +231,12 @@ def deposit(self):
if amount is None:
self.currentPrompt.text = "Try again. Money: $%.2f" % self.player.money
continue
if amount <= 0:
self.currentPrompt.text = (
"Enter an amount greater than zero. Money: $%.2f"
% self.player.money
)
continue

if self.player.canAfford(amount):
self.player.moneyInBank += amount
Expand All @@ -240,6 +255,12 @@ def withdraw(self):
"Try again. Money In Bank: $%.2f" % self.player.moneyInBank
)
continue
if amount <= 0:
self.currentPrompt.text = (
"Enter an amount greater than zero. Money In Bank: $%.2f"
% self.player.moneyInBank
)
continue

if amount <= self.player.moneyInBank:
self.player.money += amount
Expand Down
10 changes: 4 additions & 6 deletions src/npc/villagers.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,10 @@ def wageDialogue():
)

def crowdedDialogue():
info = business.tierInfo(business.currentTier(player))
return (
"All %d berths full on the %s. Elbow to elbow out there! If you "
"want more hands you'll need more boat."
% (info["maxWorkers"], info["name"])
"All %d berths full across the fleet. Elbow to elbow out there! "
"If you want more hands you'll need another boat."
% boats.totalCrewBerths(player)
)

def businessNameDialogue():
Expand Down Expand Up @@ -257,8 +256,7 @@ def ambitionDialogue():
# Only worth asking once there's no room left to hire.
"question": "Getting crowded out there, isn't it?",
"response": crowdedDialogue,
"condition": lambda: player.workers
>= business.tierInfo(business.currentTier(player))["maxWorkers"],
"condition": lambda: player.workers >= boats.totalCrewBerths(player),
},
{
# The outfit has to have a name before anyone can have an
Expand Down
64 changes: 64 additions & 0 deletions tests/location/test_bank.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,26 @@ def test_npc_business_dialogue_fleet_tier():
assert "retire a wealthy soul" in bankInstance._businessDialogue()


def test_npc_interest_dialogue_discloses_the_daily_cap():
# prepare - the teller's "interest rates" answer must state the actual
# rule (the daily cap timeService.py enforces), not just "the more you
# save, the more you earn"
from src.world.timeService import INTEREST_RATE, MAX_INTEREST_PER_DAY

bankInstance = createBank()
options = bankInstance.npc.get_dialogue_options()
questions = [option["question"] for option in options]
index = questions.index("Tell me about interest rates.")

# call
response = bankInstance.npc.get_dialogue_response(index)

# check
assert "%d%%" % int(INTEREST_RATE * 100) in response
assert "$%d" % MAX_INTEREST_PER_DAY in response
assert "maxed out" in response


def test_deposit_success():
# prepare
bankInstance = createBank()
Expand Down Expand Up @@ -333,6 +353,50 @@ def test_withdraw_invalid_input_retries_then_succeeds():
assert bankInstance.player.money == 10


def test_deposit_negative_amount_is_rejected():
# prepare - a negative "deposit" would otherwise pass canAfford (money >=
# negative is always true) and mint cash out of nothing
bankInstance = createBank()
bankInstance.userInterface.lotsOfSpace = MagicMock()
bankInstance.userInterface.divider = MagicMock()
bankInstance.player.money = 20
bankInstance.player.moneyInBank = 0
bankInstance.userInterface.promptForNumber = MagicMock(
side_effect=[-100.0, 10.0]
)

# call
bankInstance.deposit()

# check - the negative attempt is rejected and re-prompted, then the
# valid second attempt goes through
assert bankInstance.userInterface.promptForNumber.call_count == 2
assert bankInstance.player.money == 10
assert bankInstance.player.moneyInBank == 10


def test_withdraw_negative_amount_is_rejected():
# prepare - a negative "withdrawal" would otherwise pass the
# amount <= moneyInBank check and drive player.money negative
bankInstance = createBank()
bankInstance.userInterface.lotsOfSpace = MagicMock()
bankInstance.userInterface.divider = MagicMock()
bankInstance.player.moneyInBank = 400
bankInstance.player.money = 20
bankInstance.userInterface.promptForNumber = MagicMock(
side_effect=[-500.0, 10.0]
)

# call
bankInstance.withdraw()

# check - the negative attempt is rejected and re-prompted, then the
# valid second attempt goes through
assert bankInstance.userInterface.promptForNumber.call_count == 2
assert bankInstance.player.moneyInBank == 390
assert bankInstance.player.money == 30


def test_manageInvestments_buy_when_affordable():
# prepare
from src.investments import investments
Expand Down
19 changes: 19 additions & 0 deletions tests/npc/test_villagers.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,25 @@ def test_createCrewNPC_crowded_response_quotes_the_berth_count():
assert "All %d berths" % business.tierInfo(1)["maxWorkers"] in response


def test_createCrewNPC_crowded_question_stays_hidden_with_room_on_another_boat():
# prepare - two Rowboats, full crew on the first but the second still has
# room, so hiring is fleet-wide and the player isn't actually crowded out
player = createEmployedPlayer(tier=1)
boats.addBoat(player, 1)
for villager in villagers.availableVillagers(player)[
: business.tierInfo(1)["maxWorkers"] - 1
]:
boats.hireWorker(player, villager["name"])
npc = villagers.createCrewNPC(player, "Marta Kell")

# call
questions = [option["question"] for option in npc.get_dialogue_options()]

# check
assert "Getting crowded out there, isn't it?" not in questions
assert player.workers < boats.totalCrewBerths(player)


def test_createCrewNPC_ambition_response_mentions_the_business():
# prepare
player = createEmployedPlayer(tier=len(business.BOAT_TIERS))
Expand Down
Loading