Skip to content

Commit 667c703

Browse files
Merge pull request #47 from Stephenson-Software/copilot/make-fishing-interactive
Add interactive timing minigame to fishing activity
2 parents e4774d1 + b9d8228 commit 667c703

2 files changed

Lines changed: 170 additions & 49 deletions

File tree

src/location/docks.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,24 +90,61 @@ def fish(self):
9090
self.currentPrompt.text = "You're too tired to fish! Go home and sleep."
9191
return
9292

93+
successfulCatches = 0
94+
totalAttempts = 0
95+
9396
for i in range(hours):
9497
print("><> ")
9598
sys.stdout.flush()
9699
time.sleep(0.5)
100+
101+
# Interactive minigame: player must press Enter at the right moment
102+
print("A fish is biting! Press Enter quickly! ")
103+
sys.stdout.flush()
104+
105+
startTime = time.time()
106+
try:
107+
input()
108+
reactionTime = time.time() - startTime
109+
110+
# Success if pressed within 2 seconds
111+
if reactionTime <= 2.0:
112+
successfulCatches += 1
113+
print("Got it! ")
114+
else:
115+
print("Too slow... ")
116+
except (KeyboardInterrupt, EOFError):
117+
print("Missed! ")
118+
119+
sys.stdout.flush()
120+
totalAttempts += 1
121+
97122
self.stats.hoursSpentFishing += 1
98123
self.timeService.increaseTime()
99124
self.player.energy -= 10 # Consume 10 energy per hour
100125

101-
fishToAdd = random.randint(1, 10) * self.player.fishMultiplier
126+
# Calculate fish caught based on success rate
127+
baseFish = random.randint(1, 10)
128+
if totalAttempts > 0:
129+
successRate = successfulCatches / totalAttempts
130+
fishToAdd = int(baseFish * successRate * self.player.fishMultiplier)
131+
else:
132+
fishToAdd = 0
133+
134+
# Ensure at least 1 fish if player attempted
135+
if fishToAdd == 0 and totalAttempts > 0:
136+
fishToAdd = 1
137+
102138
self.player.fishCount += fishToAdd
103139
self.stats.totalFishCaught += fishToAdd
104140

105141
if fishToAdd == 1:
106142
self.currentPrompt.text = "Nice catch!"
107143
else:
108-
self.currentPrompt.text = "You caught %d fish! It only took %d hours!" % (
144+
self.currentPrompt.text = "You caught %d fish! It only took %d hours! Success rate: %d%%" % (
109145
fishToAdd,
110146
hours,
147+
int((successfulCatches / totalAttempts * 100) if totalAttempts > 0 else 0)
111148
)
112149

113150
def talkToNPC(self):

tests/location/test_docks.py

Lines changed: 131 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from src.stats.stats import Stats
66
from src.ui.userInterface import UserInterface
77
from src.world.timeService import TimeService
8-
from unittest.mock import MagicMock
8+
from unittest.mock import MagicMock, patch
99

1010

1111
def createDocks():
@@ -127,23 +127,31 @@ def test_fish():
127127
docksInstance = createDocks()
128128
docksInstance.userInterface.lotsOfSpace = MagicMock()
129129
docksInstance.userInterface.divider = MagicMock()
130-
docks.print = MagicMock()
131-
docks.sys.stdout.flush = MagicMock()
132-
docks.time.sleep = MagicMock()
133-
docks.random.randint = MagicMock(return_value=3)
134-
docksInstance.timeService.increaseTime = MagicMock()
135-
136-
# call
137-
docksInstance.fish()
138-
139-
# check
140-
docksInstance.userInterface.lotsOfSpace.assert_called_once()
141-
docksInstance.userInterface.divider.assert_called_once()
142-
assert docks.print.call_count == 4
143-
assert docks.sys.stdout.flush.call_count == 4
144-
assert docks.time.sleep.call_count == 4
145-
assert docksInstance.player.fishCount == 3
146-
assert docksInstance.stats.totalFishCaught == 3
130+
131+
# Create a side effect that alternates between 0 and 0.5 indefinitely
132+
def time_side_effect():
133+
while True:
134+
yield 0
135+
yield 0.5
136+
137+
with patch('src.location.docks.print'), \
138+
patch('src.location.docks.sys.stdout.flush'), \
139+
patch('src.location.docks.time.sleep'), \
140+
patch('src.location.docks.time.time', side_effect=time_side_effect()), \
141+
patch('src.location.docks.input', return_value=""), \
142+
patch('src.location.docks.random.randint', return_value=3):
143+
144+
docksInstance.timeService.increaseTime = MagicMock()
145+
146+
# call
147+
docksInstance.fish()
148+
149+
# check
150+
docksInstance.userInterface.lotsOfSpace.assert_called_once()
151+
docksInstance.userInterface.divider.assert_called_once()
152+
# Player should catch fish based on success rate
153+
assert docksInstance.player.fishCount >= 1
154+
assert docksInstance.stats.totalFishCaught >= 1
147155

148156

149157
def test_run_fish_action_low_energy():
@@ -169,19 +177,29 @@ def test_fish_consumes_energy():
169177
docksInstance.player.energy = 100
170178
docksInstance.userInterface.lotsOfSpace = MagicMock()
171179
docksInstance.userInterface.divider = MagicMock()
172-
docks.print = MagicMock()
173-
docks.sys.stdout.flush = MagicMock()
174-
docks.time.sleep = MagicMock()
175-
docks.random.randint = MagicMock(return_value=3) # Fish for 3 hours, catch 3 fish
176-
docksInstance.timeService.increaseTime = MagicMock()
177-
178-
# call
179-
docksInstance.fish()
180-
181-
# check
182-
assert docksInstance.player.energy == 100 - (
183-
3 * 10
184-
) # Should lose 30 energy (3 hours * 10 per hour)
180+
181+
# Create a side effect that alternates between 0 and 0.5 indefinitely
182+
def time_side_effect():
183+
while True:
184+
yield 0
185+
yield 0.5
186+
187+
with patch('src.location.docks.print'), \
188+
patch('src.location.docks.sys.stdout.flush'), \
189+
patch('src.location.docks.time.sleep'), \
190+
patch('src.location.docks.time.time', side_effect=time_side_effect()), \
191+
patch('src.location.docks.input', return_value=""), \
192+
patch('src.location.docks.random.randint', return_value=3):
193+
194+
docksInstance.timeService.increaseTime = MagicMock()
195+
196+
# call
197+
docksInstance.fish()
198+
199+
# check
200+
assert docksInstance.player.energy == 100 - (
201+
3 * 10
202+
) # Should lose 30 energy (3 hours * 10 per hour)
185203

186204

187205
def test_fish_with_limited_energy():
@@ -190,19 +208,85 @@ def test_fish_with_limited_energy():
190208
docksInstance.player.energy = 25 # Only enough for 2 hours
191209
docksInstance.userInterface.lotsOfSpace = MagicMock()
192210
docksInstance.userInterface.divider = MagicMock()
193-
docks.print = MagicMock()
194-
docks.sys.stdout.flush = MagicMock()
195-
docks.time.sleep = MagicMock()
196-
docks.random.randint = MagicMock(
197-
return_value=5
198-
) # Would normally fish for 5 hours, but energy limits to 2
199-
docksInstance.timeService.increaseTime = MagicMock()
200-
201-
# call
202-
docksInstance.fish()
203-
204-
# check
205-
assert docksInstance.player.energy == 5 # Should be 25 - (2 * 10)
206-
assert (
207-
docksInstance.timeService.increaseTime.call_count == 2
208-
) # Only fished for 2 hours due to energy limit
211+
212+
# Create a side effect that alternates between 0 and 0.5 indefinitely
213+
def time_side_effect():
214+
while True:
215+
yield 0
216+
yield 0.5
217+
218+
with patch('src.location.docks.print'), \
219+
patch('src.location.docks.sys.stdout.flush'), \
220+
patch('src.location.docks.time.sleep'), \
221+
patch('src.location.docks.time.time', side_effect=time_side_effect()), \
222+
patch('src.location.docks.input', return_value=""), \
223+
patch('src.location.docks.random.randint', return_value=5):
224+
225+
docksInstance.timeService.increaseTime = MagicMock()
226+
227+
# call
228+
docksInstance.fish()
229+
230+
# check
231+
assert docksInstance.player.energy == 5 # Should be 25 - (2 * 10)
232+
assert (
233+
docksInstance.timeService.increaseTime.call_count == 2
234+
) # Only fished for 2 hours due to energy limit
235+
236+
237+
def test_fish_interactive_success():
238+
# Test that quick reactions result in successful catches
239+
docksInstance = createDocks()
240+
docksInstance.userInterface.lotsOfSpace = MagicMock()
241+
docksInstance.userInterface.divider = MagicMock()
242+
243+
# Create a side effect that alternates between 0 and 0.5 indefinitely (quick reactions)
244+
def time_side_effect():
245+
while True:
246+
yield 0
247+
yield 0.5
248+
249+
with patch('src.location.docks.print'), \
250+
patch('src.location.docks.sys.stdout.flush'), \
251+
patch('src.location.docks.time.sleep'), \
252+
patch('src.location.docks.time.time', side_effect=time_side_effect()), \
253+
patch('src.location.docks.input', return_value=""), \
254+
patch('src.location.docks.random.randint', side_effect=[3, 6]):
255+
256+
docksInstance.timeService.increaseTime = MagicMock()
257+
258+
# call
259+
docksInstance.fish()
260+
261+
# check - with 100% success rate, should get full catch
262+
assert docksInstance.player.fishCount >= 3 # Should get good catch with all successes
263+
assert docksInstance.stats.totalFishCaught >= 3
264+
265+
266+
def test_fish_interactive_failure():
267+
# Test that slow reactions result in fewer catches
268+
docksInstance = createDocks()
269+
docksInstance.userInterface.lotsOfSpace = MagicMock()
270+
docksInstance.userInterface.divider = MagicMock()
271+
272+
# Create a side effect that alternates between 0 and 3.0 indefinitely (slow reactions)
273+
def time_side_effect():
274+
while True:
275+
yield 0
276+
yield 3.0
277+
278+
with patch('src.location.docks.print'), \
279+
patch('src.location.docks.sys.stdout.flush'), \
280+
patch('src.location.docks.time.sleep'), \
281+
patch('src.location.docks.time.time', side_effect=time_side_effect()), \
282+
patch('src.location.docks.input', return_value=""), \
283+
patch('src.location.docks.random.randint', side_effect=[3, 10]):
284+
285+
docksInstance.timeService.increaseTime = MagicMock()
286+
287+
# call
288+
docksInstance.fish()
289+
290+
# check - with 0% success rate, should still get at least 1 fish minimum
291+
assert docksInstance.player.fishCount == 1 # Minimum 1 fish even with failures
292+
assert docksInstance.stats.totalFishCaught == 1

0 commit comments

Comments
 (0)