Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5bf00cceb | ||
|
|
6b6cd0842f | ||
|
|
9ef96a1606 | ||
|
|
2fba806a43 | ||
|
|
5eefcd9f3b | ||
|
|
8d742b89cf | ||
|
|
66802216fd | ||
|
|
6fbb8342ef | ||
|
|
a954cb0fbc | ||
|
|
b4d2f2dc72 | ||
|
|
ba1a8e5230 | ||
|
|
9f3bde4fe8 | ||
|
|
1010be435a | ||
|
|
f0a3d5af55 | ||
|
|
f8d9299d74 | ||
|
|
0b9607d236 | ||
|
|
e472800bc9 | ||
|
|
1d7ca8c3af | ||
|
|
1092940b3f | ||
|
|
4998c30165 | ||
|
|
41459d9b78 | ||
|
|
2bac3a9e22 | ||
|
|
9db6c1b50b | ||
|
|
f0864afa0e | ||
|
|
5cc27ae5fa | ||
|
|
9860908777 | ||
|
|
61a6b199f7 | ||
|
|
130e62b326 | ||
|
|
35c67d5b59 | ||
|
|
9eaff2d837 | ||
|
|
1fad510038 |
@@ -1,9 +1,28 @@
|
|||||||
###############################################
|
###############################################
|
||||||
Prompt to Kiro AI (Default):
|
Prompt to Kiro AI (Default):
|
||||||
|
|
||||||
|
from now on, I will mostly be asking questions about approach, so please do not perform any actions or code edits unless explicitly requested. also, please be less verbose - I want nice, quick answers that provide the necessary context. How can I check if a file exists/isreadable for my loadSave function?
|
||||||
|
|
||||||
Response:
|
Response:
|
||||||
|
|
||||||
|
Two clean options:
|
||||||
|
|
||||||
|
os.path.exists(path) — checks the file is there before opening.
|
||||||
|
|
||||||
|
python
|
||||||
|
|
||||||
|
import os
|
||||||
|
if os.path.exists(path):
|
||||||
|
# open and read
|
||||||
|
try/except — just attempt to open and catch the failure. This is the more Pythonic approach and also handles the "exists but unreadable/corrupt" case in one shot:
|
||||||
|
|
||||||
|
python
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(path) as f:
|
||||||
|
...
|
||||||
|
except (FileNotFoundError, IOError):
|
||||||
|
return defaultState()
|
||||||
|
For loadSave, the try/except is the better fit since you also want to catch bad JSON — wrap json.load in the same block and catch json.JSONDecodeError too, falling back to a fresh default state on any failure.
|
||||||
|
|
||||||
###############################################
|
###############################################
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
###############################################
|
||||||
|
Prompt to Kiro AI (Default):
|
||||||
|
|
||||||
|
In pickRandomCreature, I want to smooth out the probabilities of picking a creature. normally, I could just do total-difficulty / total to get a chance for each one, but I want my selection to be slightly smoother, so that rarer fish are easier to catch (only once unlocked, of course, but that is already checked in possibilties list). what approach could you reccomend?
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
You want to compress the spread between common and rare weights. The standard trick is to transform each weight before normalizing, rather than using raw difficulty.
|
||||||
|
|
||||||
|
Power/exponent smoothing — raise each weight to a power between 0 and 1:
|
||||||
|
|
||||||
|
|
||||||
|
weight = difficulty ** k # k = 0.5 (sqrt) is a good start
|
||||||
|
k = 1 → unchanged
|
||||||
|
k = 0.5 → square root, pulls rare fish up noticeably
|
||||||
|
k → 0 → approaches uniform (everything equally likely)
|
||||||
|
One knob (k) tunes the whole curve, which is nice for playtesting.
|
||||||
|
|
||||||
|
Alternatives if you want finer control:
|
||||||
|
|
||||||
|
Additive floor: weight = difficulty + c. Adding a constant to every fish shrinks the ratio between big and small, so rares get a flat boost. Simple but less tunable than the exponent.
|
||||||
|
Invert first: if higher difficulty should mean rarer, remember raw difficulty makes hard fish more likely. You may want weight = (1 / difficulty) ** k or (maxDiff - difficulty + 1) depending on which direction "rare" means for you.
|
||||||
|
Mechanically, once you have smoothed weights: sum them, roll random.random() * total, then walk the list subtracting each weight until you cross zero — that's your pick.
|
||||||
|
|
||||||
|
Start with sqrt (k=0.5) and adjust from there.
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
|
||||||
|
Follow up:
|
||||||
|
|
||||||
|
look at my implementation. is that sufficient?
|
||||||
|
|
||||||
|
Response: (wow it found a lot of bugs oops, glad they got caught now)
|
||||||
|
|
||||||
|
Good progress on the logic. The weighting approach in pickRandomCreature is sound, but there are several bugs — some will crash, some are subtle. Grouped by severity:
|
||||||
|
|
||||||
|
Crashers:
|
||||||
|
|
||||||
|
AVAILABLE_BAITS — missing commas between tuples. This is a syntax error (Python tries to call the tuples). Add commas after each.
|
||||||
|
|
||||||
|
pickRandomCreature:
|
||||||
|
|
||||||
|
possibilties is misspelled (vs possibilities) on the total line → NameError.
|
||||||
|
b[0] should be bait[0] in the max_difficulty comprehension.
|
||||||
|
You're comparing bait[0] (the name) but then take bait[0] as max_difficulty — index mismatch. Name is [0], max_difficulty is [1]. You want: filter on bait[0] == current_bait, return bait[1].
|
||||||
|
onAppStart:
|
||||||
|
|
||||||
|
app.waterBottom = app.height = app.waterHeight — double assignment. This overwrites app.height with waterHeight (133). Almost certainly meant app.height - app.waterHeight.
|
||||||
|
Logic bugs:
|
||||||
|
|
||||||
|
The weighted pick is backwards. You pop() from the end of possibilities, but roll was computed starting from the full total. Popping removes items but the roll walks fine mechanically — except possibleCatch leaks out of the loop scope and if roll logic ever lands exactly, it's fragile. The bigger issue: pop() mutates and the final lookup re-scans app.creatures by name. Cleaner to iterate with a for-loop and break, returning the creature directly. Also — you never return catch.
|
||||||
|
|
||||||
|
Smoothing direction. difficulty ** 0.5 still makes harder fish have a higher weight (49 → 7 beats 4 → 2). You said you want rarer fish easier to catch once unlocked — sqrt compresses the gap but hard fish are still more common than easy ones here. If "rare" means hard fish should be the uncommon ones, you want to invert: weight on something like (max_difficulty - difficulty + 1) ** k. Worth confirming which direction you intend.
|
||||||
|
|
||||||
|
Minor:
|
||||||
|
|
||||||
|
resetFishingState has a trailing pass and doesn't reset app.fishingTimer / app.hookedFish (the comment says it should).
|
||||||
|
|
||||||
|
loadSave returns the same defaultState dict object — fine since it's fresh each call, but note json.load success path skips the default-key merge, so an old save missing a new key (e.g. you add a field later) would break downstream. Consider merging into defaults.
|
||||||
|
|
||||||
|
startSkillCheck: random.randrange(...) takes an int; your newLen expression is a float → TypeError. Wrap in int(). Same risk on the newPos divisor if it hits 0 (last bar, numBars - len == 0) → ZeroDivisionError on the final iteration.
|
||||||
|
|
||||||
|
The #TODO about overlap is a real one — your sequential placement mostly prevents it, but the min(newPos, 360-newLen) cap can push a bar back onto the previous one.
|
||||||
|
|
||||||
|
Want me to walk through a corrected version of just pickRandomCreature, or leave it for you to fix?
|
||||||
|
|
||||||
|
###############################################
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
###############################################
|
||||||
|
Prompt to Kiro AI (Default):
|
||||||
|
|
||||||
|
im getting a list index out of range error very rarely, right as the fishing timer reaches zero and it begins the catching process. any idea what could be causing that? dont fix, explain.
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
It's almost certainly the pop() loop in pickRandomCreature.
|
||||||
|
|
||||||
|
The loop pops until roll drops to 0 or below. The guard roll = random.random() * (total - 0.0001) is meant to guarantee the roll is always strictly less than total, so the loop empties enough weight before running out of items. But floating-point rounding breaks that guarantee in rare cases.
|
||||||
|
|
||||||
|
Here's the problem: total is the sum of all the individual weights. Due to float accumulation error, sum(weights) is not exactly equal to subtracting those same weights one by one. In the unlucky case where roll lands extremely close to total, you can pop every item in possibilities and roll is still above 0 by a tiny epsilon — then the next .pop() hits an empty list → index out of range.
|
||||||
|
|
||||||
|
The - 0.0001 fudge shrinks the odds but doesn't eliminate them: if the summed total is itself slightly larger than the re-subtracted total, that 0.0001 margin can be smaller than the accumulated error, especially with your sqrt'd weights which are non-round floats.
|
||||||
|
|
||||||
|
Why it's rare and tied to the timer hitting zero: pickRandomCreature only runs at that moment (bite triggers the pick), and it only fails when the random roll lands in that thin top sliver near total. Most rolls land comfortably in the middle, so you almost never see it.
|
||||||
|
|
||||||
|
Side note that makes it worse: you pop() from the end of the list every time, which is fine mechanically, but it means the walk order and the weight order are the same list being mutated — so once it's empty there's no fallback. A for-loop over a fixed list with a final default pick would avoid the empty-list crash entirely.
|
||||||
|
|
||||||
|
###############################################
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
###############################################
|
||||||
|
Prompt to Kiro AI (Default):
|
||||||
|
|
||||||
|
Hard fish should be more difficult AND rarer to catch than easy fish (easy = lower difficulty number). However, im not getting the opportunity to catch a lot of hard fish in the first place, and it really feels like this game isn't very balanced. Can you go ahead and modify any of the numbers you need (but not logic nor code unless absolutely necessary, and you must ask first) to improve the balance, chance, and fairness of the game? It feels like some fish are insanely possible to catch, while others are too easy.
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
I have the full picture now. Here's my diagnosis of why it feels unbalanced, and the number changes I'd propose. I won't touch anything until you approve.
|
||||||
|
|
||||||
|
Why it feels broken
|
||||||
|
1. Hard fish barely appear. Your weight is (max_difficulty - difficulty + 1) ** 0.5. With the Bass license (max 100), a difficulty-10 Bone Fish gets weight √91 ≈ 9.5, while the difficulty-100 Hammerhead gets √1 = 1. That's a ~9.5x gap — hard fish are extremely rare. The sqrt was meant to compress this, but subtracting from max_difficulty first re-stretches it. This is the main "I never see hard fish" cause.
|
||||||
|
|
||||||
|
2. The difficulty→skill-check mapping is brutally nonlinear. Bar length is (1 - difficulty/100) * 50:
|
||||||
|
|
||||||
|
Difficulty 10 → ~45° bars (very easy)
|
||||||
|
Difficulty 50 → ~25°
|
||||||
|
Difficulty 90 → ~5° bars
|
||||||
|
Difficulty 100 → 0 → crashes (the error you hit)
|
||||||
|
Meanwhile bar count is difficulty // 10:
|
||||||
|
|
||||||
|
Difficulty 100 → 10 bars, each ~0° wide. Mathematically impossible.
|
||||||
|
Difficulty 90 → 9 bars at ~5°.
|
||||||
|
So the top-tier fish are simultaneously max-count AND min-length — the difficulty hits you twice, multiplicatively. That's your "insanely impossible" fish.
|
||||||
|
|
||||||
|
3. Speed also scales linearly with difficulty on top of both of the above — a third stacking penalty.
|
||||||
|
|
||||||
|
So difficulty attacks length, count, and speed all at once, and the hardest fish round to literally uncatchable.
|
||||||
|
|
||||||
|
Proposed number changes (data/constants only, no logic)
|
||||||
|
A. Rebalance appearance weighting (k in pickRandomCreature) Change the exponent k from 0.5 to 0.75. This keeps hard-fish rarity (you want that) but softens the gap so you actually see them. Hammerhead goes from 1/9.5 odds to roughly 1/5.5 relative to the easiest fish. If you want them rarer, I'd stay nearer 0.6; for more frequent, 0.85.
|
||||||
|
|
||||||
|
B. Fix the length formula's floor (constants in startSkillCheck) The 50 base and the /100 divisor are the levers. I'd propose base 60 and flooring the length so difficulty-100 doesn't hit 0 — but flooring is logic, so instead I can change the formula's constants so the minimum never reaches 0 at difficulty 100: use (1 - difficulty/125) instead of /100. At difficulty 100 that's (1 - 0.8) = 0.2 → 12° bars instead of 0. This also fixes the crash without a logic change.
|
||||||
|
|
||||||
|
C. Soften bar count difficulty // 10 is steep. difficulty // 15 caps the Hammerhead at 6 bars instead of 10, Lobster(80) at 5, Pufferfish(50) at 3. Much more playable.
|
||||||
|
|
||||||
|
D. Creature difficulty spread (optional, in creatures.tsv) Your difficulties cluster at round numbers (10,20,40,50,60,70,80,90,100). That's fine. I'd leave the TSV alone unless you want me to spread them.
|
||||||
|
|
||||||
|
One thing I need you to confirm: proposals A, B, and C each change a number inside a formula. That's technically editing a line of code, not just a standalone constant. You said numbers-only and ask before logic. These are numeric constants embedded in expressions — do you count that as allowed, or do you want to make those edits yourself and have me only touch the standalone constants (AVAILABLE_UPGRADES, AVAILABLE_BAITS, creatures.tsv, the k value)?
|
||||||
|
|
||||||
|
Tell me which scope you want and I'll proceed.
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
|
||||||
|
Follow Up:
|
||||||
|
|
||||||
|
thats fine to modify - theyre still constants. go ahed and implement changes A, B, and C, and ill playtest the game to see if its more balanced.
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
All three done:
|
||||||
|
|
||||||
|
A — k: 0.5 → 0.75 (hard fish appear more often while staying rarer than easy ones).
|
||||||
|
B — length formula: (1 - difficulty/100) * 50 → (1 - difficulty/125) * 60. Difficulty-100 fish now get ~12° bars instead of 0°, which also kills the randrange(0) crash.
|
||||||
|
C — bar count: difficulty // 10 → difficulty // 15. Hammerhead (100) drops from 10 bars to 6, Lobster (80) to 5, Pufferfish (50) to 3.
|
||||||
|
Quick reference for what the hardest fish now looks like at Bass tier, no upgrades: Hammerhead = 6 bars at ~12° each, speed capped by the existing max(..., 5). Tough but passable.
|
||||||
|
|
||||||
|
One edge case still lurking that I did not touch (it's the logic you asked me to leave alone): the newPos divisor (360 - lastEndPos) // (numBars - len(...)) can still produce randrange(5, 5) if early bars eat most of the circle. Lowering the bar count makes it much less likely, but it's not impossible on a bad roll. Flag it if you hit a crash during playtesting and we can revisit.
|
||||||
|
|
||||||
|
Go ahead and playtest — let me know how the frequency and difficulty feel.
|
||||||
|
|
||||||
|
###############################################
|
||||||
@@ -8,4 +8,14 @@ Description: The main AI agent in kiro, used for most if not all of the promptin
|
|||||||
###############################################
|
###############################################
|
||||||
Source: Google Search / Google Search AI
|
Source: Google Search / Google Search AI
|
||||||
Description: Used infrequently just for general questions regarding python or graphics, no specific usage (and google search AI is just kind of always there)
|
Description: Used infrequently just for general questions regarding python or graphics, no specific usage (and google search AI is just kind of always there)
|
||||||
|
###############################################
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
Source: CMU Graphics Documentation (https://academy.cs.cmu.edu/cpcs-docs)
|
||||||
|
Description: Refferred to for implementation and MVC architecture along with how to interface with graphics
|
||||||
|
###############################################
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
Source: Aubrey Pulawski (Girlfriend)
|
||||||
|
Description: Drew all of the creature sprites that are used in the game (I absolutely CANNOT draw)
|
||||||
###############################################
|
###############################################
|
||||||
@@ -10,6 +10,14 @@
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
from cmu_graphics import *
|
from cmu_graphics import *
|
||||||
|
import json
|
||||||
|
import random
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
|
||||||
|
#debug vars to store mouse pos
|
||||||
|
gmouseX = 0
|
||||||
|
gmouseY = 0
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -19,6 +27,35 @@ from cmu_graphics import *
|
|||||||
# the data source (TSV path, save path) can change without touching gameplay.
|
# the data source (TSV path, save path) can change without touching gameplay.
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
#hopefully this will help with lag
|
||||||
|
def preLoadImages(app):
|
||||||
|
print("PRELOADING images")
|
||||||
|
IMAGES = [
|
||||||
|
"fail",
|
||||||
|
"fisherman",
|
||||||
|
"salesman",
|
||||||
|
"surprise",
|
||||||
|
"bait-icons/bass",
|
||||||
|
"bait-icons/prawn",
|
||||||
|
"bait-icons/tuna",
|
||||||
|
"bait-icons/worm",
|
||||||
|
"fish/aubrey-fish",
|
||||||
|
"fish/bone-fish",
|
||||||
|
"fish/book",
|
||||||
|
"fish/hammerhead",
|
||||||
|
"fish/lobster",
|
||||||
|
"fish/mudge-fish",
|
||||||
|
"fish/pufferfish",
|
||||||
|
"fish/rainbow-fish",
|
||||||
|
"fish/shrimp",
|
||||||
|
"fish/turtle"
|
||||||
|
]
|
||||||
|
for img_name in IMAGES:
|
||||||
|
print("loading: " + BASE_IMAGE_URL + img_name + ".png")
|
||||||
|
drawImage(BASE_IMAGE_URL + img_name + ".png", 0, 0, visible=False)
|
||||||
|
|
||||||
|
|
||||||
def loadCreatures(path="creatures.tsv"):
|
def loadCreatures(path="creatures.tsv"):
|
||||||
# Read creatures.tsv (tab-separated) and parse it into a usable structure.
|
# Read creatures.tsv (tab-separated) and parse it into a usable structure.
|
||||||
# Suggested return: a list of dicts, one per creature, with keys matching
|
# Suggested return: a list of dicts, one per creature, with keys matching
|
||||||
@@ -44,19 +81,30 @@ def loadCreatures(path="creatures.tsv"):
|
|||||||
return creatures
|
return creatures
|
||||||
|
|
||||||
|
|
||||||
def loadSave(path):
|
def loadSave(path="save.json"):
|
||||||
# Load save.json and return the player's saved state (money, owned baits,
|
# Load save.json and return the player's saved state (money, owned baits,
|
||||||
# owned upgrades, current bait, caught log, etc.). If the file is missing
|
# owned upgrades, current bait, caught log, etc.). If the file is missing
|
||||||
# or unreadable, return a fresh default-state dict so a first-time player
|
# or unreadable, return a fresh default-state dict so a first-time player
|
||||||
# starts cleanly. This is the only place that reads the save file.
|
# starts cleanly. This is the only place that reads the save file.
|
||||||
pass
|
defaultState = {
|
||||||
|
"cash": 0,
|
||||||
|
"owned_baits": ["Worms"],
|
||||||
|
"owned_upgrades": [],
|
||||||
|
"current_bait": "Worms"
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
with open(path, "r") as f:
|
||||||
|
return json.load(f)
|
||||||
|
except (FileNotFoundError, IOError):
|
||||||
|
return defaultState
|
||||||
|
|
||||||
|
|
||||||
def writeSave(path, saveState):
|
def writeSave(saveState, path="save.json"):
|
||||||
# Serialize the current player state to save.json. Call this at key moments
|
# Serialize the current player state to save.json. Call this at key moments
|
||||||
# (after a sale, after a purchase, on quit) so progress persists between
|
# (after a sale, after a purchase, on quit) so progress persists between
|
||||||
# sessions. Keep the written structure identical to what loadSave expects.
|
# sessions. Keep the written structure identical to what loadSave expects.
|
||||||
pass
|
with open(path, "w") as f: #overwrite existing
|
||||||
|
f.write(json.dumps(saveState))
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -66,17 +114,118 @@ def writeSave(path, saveState):
|
|||||||
# No drawing and no event handling here - just state.
|
# No drawing and no event handling here - just state.
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
AVAILABLE_UPGRADES = [ # (name, bonus_type, bonus_modifier, price)
|
||||||
|
#power mods - lowers speed of cursor (additive)
|
||||||
|
("Smart Reel", "power", 2, 100),
|
||||||
|
("Clever Reel", "power", 7, 350),
|
||||||
|
("Intelligent Reel", "power", 10, 1500),
|
||||||
|
("Genius Reel", "power", 20, 20000),
|
||||||
|
#timing mods - lengthens bars in skill check (multiplicative)
|
||||||
|
("Medium Bobber", "timing", 1.1, 100),
|
||||||
|
("Large Bobber", "timing", 1.2, 300),
|
||||||
|
("Huge Bobber", "timing", 1.5, 1600),
|
||||||
|
("Massive Bobber", "timing", 2, 25000),
|
||||||
|
#count mods - lowers number of bars in skill check (subtractive (capped at 1))
|
||||||
|
("Shiny Rod", "count", 1, 450),
|
||||||
|
("Spectral Rod", "count", 1, 1200),
|
||||||
|
("Glowing Rod", "count", 2, 5000),
|
||||||
|
("Galaxy Rod", "count", 3, 40000)
|
||||||
|
]
|
||||||
|
|
||||||
|
AVAILABLE_BAITS = [ # (name, max_difficulty, price, image_file)
|
||||||
|
("Worms", 30, 0, "worm.png"),
|
||||||
|
("Prawns", 50, 200, "prawn.png"),
|
||||||
|
("Tuna", 70, 1000, "tuna.png"),
|
||||||
|
("Bass", 100, 19410, "bass.png")
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
def onAppStart(app):
|
def onAppStart(app):
|
||||||
# cmu_graphics entry point. Initialize ALL app.* state here:
|
# cmu_graphics entry point. Initialize ALL app.* state here:
|
||||||
# - window/layout constants (width, height, dock/water/grass regions)
|
# - window/layout constants (width, height, dock/water/grass regions)
|
||||||
# - app.creatures (from loadCreatures)
|
# - app.creatures (from loadCreatures)
|
||||||
# - app.save (from loadSave) and derived player values
|
# - app.save (from loadSave) and derived player values
|
||||||
# - app.scene (which screen is active: 'dock', 'shop', 'catch', ...)
|
|
||||||
# - fishing state (idle / waiting-for-bite / skill-check / result)
|
# - fishing state (idle / waiting-for-bite / skill-check / result)
|
||||||
# - skill-check state placeholders (cursor angle, bars, speed, result)
|
# - skill-check state placeholders (cursor angle, bars, speed, result)
|
||||||
# - app.stepsPerSecond for the game loop cadence
|
# - app.stepsPerSecond for the game loop cadence
|
||||||
# Delegates to the smaller setup helpers below to keep this readable.
|
# Delegates to the smaller setup helpers below to keep this readable.
|
||||||
pass
|
app.debug = True
|
||||||
|
app.debugCreatureInd = 0
|
||||||
|
|
||||||
|
#DRAWING CONSTANTS
|
||||||
|
app.width = 600
|
||||||
|
app.height = 400
|
||||||
|
app.background = 'lightSkyBlue'
|
||||||
|
app.waterColor = 'midnightBlue'
|
||||||
|
app.grassColor = 'seaGreen'
|
||||||
|
app.waterHeight = app.height // 3
|
||||||
|
app.waterTop = app.height - app.waterHeight
|
||||||
|
app.grassHeight = 20
|
||||||
|
app.grassTop = app.height - app.waterHeight - app.grassHeight
|
||||||
|
|
||||||
|
#GAME STATE/SETUP
|
||||||
|
app.stepsPerSecond = 30
|
||||||
|
app.creatures = loadCreatures()
|
||||||
|
app.save = loadSave()
|
||||||
|
app.scene = "idle" # idle, waiting-for-bite, skill-check, result, shopping, menu
|
||||||
|
|
||||||
|
#SKILL CHECK
|
||||||
|
app.skillCheckAngle = 0
|
||||||
|
app.skillCheckSpeed = 1
|
||||||
|
app.skillCheckBars = [] # tuples of (position, length)
|
||||||
|
app.skillCheckFailed = False
|
||||||
|
app.showPatternTimer = 30
|
||||||
|
|
||||||
|
#FISHING
|
||||||
|
app.fishingTimer = 0
|
||||||
|
app.hookedFish = None
|
||||||
|
app.timeSinceInit = 0
|
||||||
|
|
||||||
|
#list of interactables with name and their positions
|
||||||
|
#(name, sceneFilter, left, right, top, bottom)
|
||||||
|
app.INTERACTABLES = [
|
||||||
|
("open-shop", "idle", 400, 540, app.grassTop - 130, app.grassTop + 30),
|
||||||
|
("close-shop", "shop", app.width-50, app.width, 0, 50)
|
||||||
|
]
|
||||||
|
|
||||||
|
#add all of our shop purchase upgrades
|
||||||
|
startX = 75
|
||||||
|
startY = 75
|
||||||
|
endX = 525
|
||||||
|
endY = 250
|
||||||
|
colInd = 0
|
||||||
|
XDiff = (endX-startX) // 3
|
||||||
|
|
||||||
|
#build upgrade grid
|
||||||
|
for columnType in ["power", "timing", "count"]:
|
||||||
|
upgrades = [u for u in AVAILABLE_UPGRADES if u[1] == columnType]
|
||||||
|
YDiff = (endY-startY) // len(upgrades)
|
||||||
|
for i in range(len(upgrades)):
|
||||||
|
app.INTERACTABLES.insert(0, ("upgrade-buy:" + upgrades[i][0],
|
||||||
|
"shop",
|
||||||
|
startX + XDiff * colInd,
|
||||||
|
startX + XDiff * colInd + XDiff,
|
||||||
|
startY + YDiff * i,
|
||||||
|
startY + YDiff * i + YDiff)) #this used to just be append
|
||||||
|
#but that was causing issues where the shop bounding box was
|
||||||
|
#checked first, then opening the shop gui, and allowing the
|
||||||
|
#player to purchase an upgrade with the same click (not ideal)
|
||||||
|
colInd += 1
|
||||||
|
|
||||||
|
#build bait grid
|
||||||
|
startX = 125
|
||||||
|
startY = 250
|
||||||
|
endX = 475
|
||||||
|
diffX = (endX - startX) // len(AVAILABLE_BAITS)
|
||||||
|
for i in range(len(AVAILABLE_BAITS)):
|
||||||
|
app.INTERACTABLES.insert(0, ("bait-buy:" + AVAILABLE_BAITS[i][0],
|
||||||
|
"shop",
|
||||||
|
startX + diffX * i,
|
||||||
|
startX + diffX * i + diffX,
|
||||||
|
startY,
|
||||||
|
startY + diffX))
|
||||||
|
|
||||||
|
print(app.INTERACTABLES)
|
||||||
|
|
||||||
|
|
||||||
def resetFishingState(app):
|
def resetFishingState(app):
|
||||||
@@ -84,6 +233,16 @@ def resetFishingState(app):
|
|||||||
# begin. Clears any active skill-check data, the pending random bite timer,
|
# begin. Clears any active skill-check data, the pending random bite timer,
|
||||||
# and the currently-hooked creature. Called at startup and after each catch
|
# and the currently-hooked creature. Called at startup and after each catch
|
||||||
# is resolved/dismissed.
|
# is resolved/dismissed.
|
||||||
|
app.skillCheckAngle = 0 #measured in degrees
|
||||||
|
app.skillCheckSpeed = 1
|
||||||
|
app.skillCheckBars = [] # list of tuples of form (position, length)
|
||||||
|
app.skillCheckFailed = False
|
||||||
|
app.showPatternTimer = 30
|
||||||
|
|
||||||
|
app.hookedFish = None
|
||||||
|
app.fishingTimer = 0
|
||||||
|
|
||||||
|
app.scene = "idle"
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -93,7 +252,32 @@ def pickRandomCreature(app):
|
|||||||
# weighted toward easier creatures, while still giving rarer/harder ones a
|
# weighted toward easier creatures, while still giving rarer/harder ones a
|
||||||
# real chance (per the spec's "skewed toward easier but still fun" goal).
|
# real chance (per the spec's "skewed toward easier but still fun" goal).
|
||||||
# Returns the chosen creature record (one entry from app.creatures).
|
# Returns the chosen creature record (one entry from app.creatures).
|
||||||
pass
|
max_difficulty = [bait[1] for bait in AVAILABLE_BAITS if bait[0] == app.save["current_bait"]][0]
|
||||||
|
print("max difficulty:", max_difficulty)
|
||||||
|
|
||||||
|
#here we take the root of the difficulty to represent the weight of that fish, this makes higher difficulty fish easier to catch once unlocked
|
||||||
|
k = 0.75
|
||||||
|
#invert the difficulty and add one so it avoids zero
|
||||||
|
possibilities = [(creature["name"], (max_difficulty - creature["difficulty"] + 1) ** k) for creature in app.creatures if creature["difficulty"] <= max_difficulty]
|
||||||
|
print("possibilities:",possibilities)
|
||||||
|
total = sum([c[1] for c in possibilities])
|
||||||
|
print("total:",total)
|
||||||
|
|
||||||
|
#now pick randomly
|
||||||
|
roll = random.random() * (total) # a value between 0 and total
|
||||||
|
|
||||||
|
caughtName = "Aubrey Fish"
|
||||||
|
for possibleCatch in possibilities:
|
||||||
|
print("possible catch:",possibleCatch)
|
||||||
|
roll -= possibleCatch[1]
|
||||||
|
if (roll < 0):
|
||||||
|
caughtName = possibleCatch[0]
|
||||||
|
break
|
||||||
|
|
||||||
|
print(caughtName, app.creatures)
|
||||||
|
catch = [c for c in app.creatures if c["name"] == caughtName][0]
|
||||||
|
return catch
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def startSkillCheck(app, creature):
|
def startSkillCheck(app, creature):
|
||||||
@@ -102,7 +286,39 @@ def startSkillCheck(app, creature):
|
|||||||
# bar length/width. Also apply any owned upgrades that modify these values
|
# bar length/width. Also apply any owned upgrades that modify these values
|
||||||
# (e.g. slower cursor, wider bars, extra forgiveness). Stores the resulting
|
# (e.g. slower cursor, wider bars, extra forgiveness). Stores the resulting
|
||||||
# parameters on app for the controller/view to use.
|
# parameters on app for the controller/view to use.
|
||||||
pass
|
app.scene = "skill-check"
|
||||||
|
difficulty = creature["difficulty"]
|
||||||
|
|
||||||
|
fishingPower = 1 + sum([upgrade[2] for upgrade in AVAILABLE_UPGRADES if (upgrade[0] in app.save["owned_upgrades"] and upgrade[1] == "power")])
|
||||||
|
|
||||||
|
app.skillCheckSpeed = max(difficulty / fishingPower, 5) #this got INCREDIBLY slow with good upgrades
|
||||||
|
|
||||||
|
numBars = difficulty // 15 - sum([upgrade[2] for upgrade in AVAILABLE_UPGRADES if (upgrade[0] in app.save["owned_upgrades"] and upgrade[1] == "count")])
|
||||||
|
numBars = max(numBars, 1)
|
||||||
|
|
||||||
|
lastPos = 0
|
||||||
|
lastLen = 0
|
||||||
|
while len(app.skillCheckBars) < numBars:
|
||||||
|
#50 is default length, adjust it proportionally to difficulty, then add upgrade length multipliers. also set to int instead of float
|
||||||
|
newLen = random.randrange(int((1 - (difficulty / 125)) * 60 * math.prod([upgrade[2] for upgrade in AVAILABLE_UPGRADES if (upgrade[0] in app.save["owned_upgrades"] and upgrade[1] == "timing")])))
|
||||||
|
#take the end of the last bar, and add a random amount between 5 degrees
|
||||||
|
#and the remaining amount to the end divided by the number of bars left
|
||||||
|
lastEndPos = (lastPos + lastLen)
|
||||||
|
newPos = lastEndPos + random.randrange(5, (360 - lastEndPos) // (numBars - len(app.skillCheckBars)))
|
||||||
|
#cap it to ensure we dont go over
|
||||||
|
newPos = min(newPos, 360-newLen)
|
||||||
|
#TODO: double check that multiple bars do not overlap
|
||||||
|
app.skillCheckBars.append((newPos, newLen))
|
||||||
|
lastPos = newPos
|
||||||
|
lastLen = newLen
|
||||||
|
|
||||||
|
#speed is set, bars are set, scene is good, should be fine?
|
||||||
|
|
||||||
|
def beginFishing(app):
|
||||||
|
app.fishingTimer = random.random() * app.stepsPerSecond * 25 + 5 # completely random between 5 and 30
|
||||||
|
app.scene = "waiting-for-bite"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -119,7 +335,24 @@ def onStep(app):
|
|||||||
# creature and transition into the skill check.
|
# creature and transition into the skill check.
|
||||||
# - skill-check: advance the cursor's angle around the circle each step.
|
# - skill-check: advance the cursor's angle around the circle each step.
|
||||||
# - otherwise (idle, viewing a catch, in shop): do nothing time-based.
|
# - otherwise (idle, viewing a catch, in shop): do nothing time-based.
|
||||||
pass
|
|
||||||
|
app.timeSinceInit += 1
|
||||||
|
|
||||||
|
#had to look up how to do switch-case statement in python, I know it from JS though
|
||||||
|
match app.scene:
|
||||||
|
case "waiting-for-bite":
|
||||||
|
app.fishingTimer -= 1
|
||||||
|
if app.fishingTimer < 1:
|
||||||
|
selectedCreature = pickRandomCreature(app)
|
||||||
|
app.hookedFish = selectedCreature
|
||||||
|
startSkillCheck(app, selectedCreature)
|
||||||
|
case "skill-check":
|
||||||
|
if app.showPatternTimer < 1:
|
||||||
|
app.skillCheckAngle += app.skillCheckSpeed / 6 #this was far too fast
|
||||||
|
if (app.skillCheckAngle > 360):
|
||||||
|
finishSkillCheck(app)
|
||||||
|
else:
|
||||||
|
app.showPatternTimer -= 1
|
||||||
|
|
||||||
|
|
||||||
def onMousePress(app, mouseX, mouseY):
|
def onMousePress(app, mouseX, mouseY):
|
||||||
@@ -130,14 +363,76 @@ def onMousePress(app, mouseX, mouseY):
|
|||||||
# - catch popup showing: dismiss the popup and reset fishing.
|
# - catch popup showing: dismiss the popup and reset fishing.
|
||||||
# - shop / UI sprites clicked: open the shop or trigger that UI action.
|
# - shop / UI sprites clicked: open the shop or trigger that UI action.
|
||||||
# Keep this as dispatch only - defer the real work to helper functions.
|
# Keep this as dispatch only - defer the real work to helper functions.
|
||||||
pass
|
match app.scene:
|
||||||
|
case "idle" | "shop": #also had to look this up - pipe operator is dumb. just let me have multiple cases
|
||||||
|
interacted = False
|
||||||
|
#check if we clicked a hud element/interactable
|
||||||
|
for name, sceneFilter, left, right, top, bottom in app.INTERACTABLES:
|
||||||
|
if left < mouseX < right and top < mouseY < bottom \
|
||||||
|
and sceneFilter == app.scene:
|
||||||
|
interacted = True
|
||||||
|
match name:
|
||||||
|
case "open-shop":
|
||||||
|
app.scene = "shop"
|
||||||
|
case "close-shop":
|
||||||
|
app.scene = "idle"
|
||||||
|
if name.startswith("upgrade-buy:"):
|
||||||
|
print("buying upgrade", name)
|
||||||
|
buyUpgrade(app, name.split(":")[1])
|
||||||
|
if name.startswith("bait-buy:"):
|
||||||
|
print("buying bait", name)
|
||||||
|
buyBaitLicense(app, name.split(":")[1])
|
||||||
|
|
||||||
|
|
||||||
|
if not interacted and app.scene == "idle":
|
||||||
|
beginFishing(app)
|
||||||
|
case "result":
|
||||||
|
sellCreature(app, app.hookedFish)
|
||||||
|
resetFishingState(app)
|
||||||
|
case "skill-check":
|
||||||
|
checkSkillHit(app)
|
||||||
|
case "waiting-for-bite":
|
||||||
|
resetFishingState(app)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def onKeyPress(app, key):
|
def onKeyPress(app, key):
|
||||||
# Optional keyboard controls. Candidates: a key to cast/reel as an
|
# Optional keyboard controls. Candidates: a key to cast/reel as an
|
||||||
# alternative to clicking, a key to open/close the shop, or a debug/reset
|
# alternative to clicking, a key to open/close the shop, or a debug/reset
|
||||||
# key during development. Remove if you decide the game is mouse-only.
|
# key during development. Remove if you decide the game is mouse-only.
|
||||||
pass
|
|
||||||
|
#decidedly unused for now, i prefer mouse-only. ill keep the definition here in case i need debug tools.
|
||||||
|
|
||||||
|
if key == "escape":
|
||||||
|
writeSave(app.save)
|
||||||
|
os._exit(0) #suggested by AI, instantly exits without hanging like sys.exit(0)
|
||||||
|
|
||||||
|
# DEBUG
|
||||||
|
if not app.debug: return
|
||||||
|
if key == "h":
|
||||||
|
if app.debugCreatureInd < len(app.creatures) - 1:
|
||||||
|
app.debugCreatureInd += 1
|
||||||
|
else:
|
||||||
|
app.debugCreatureInd = 0
|
||||||
|
|
||||||
|
app.hookedFish = app.creatures[app.debugCreatureInd]
|
||||||
|
app.scene = "result"
|
||||||
|
if key == "m":
|
||||||
|
app.save["cash"] = 1000000
|
||||||
|
if key == "r":
|
||||||
|
#reset save
|
||||||
|
app.save = loadSave("thisisntarealsavefile.stupid")
|
||||||
|
if key == "f":
|
||||||
|
app.fishingTimer = 30
|
||||||
|
|
||||||
|
#added by AI, but simply allows me to trigger a breakpoint manually to check state of app and variables
|
||||||
|
#after some debate, this was the best option over something more complex
|
||||||
|
if key == 'd':
|
||||||
|
print("breakpoint here")
|
||||||
|
|
||||||
|
def onMouseMove(app, mouseX, mouseY):
|
||||||
|
global gmouseX, gmouseY
|
||||||
|
gmouseX, gmouseY = mouseX, mouseY
|
||||||
|
|
||||||
|
|
||||||
def checkSkillHit(app):
|
def checkSkillHit(app):
|
||||||
@@ -147,7 +442,25 @@ def checkSkillHit(app):
|
|||||||
# advance or fail the skill check accordingly. On full success, mark the
|
# advance or fail the skill check accordingly. On full success, mark the
|
||||||
# creature as caught and move to the catch-display phase; on failure, end
|
# creature as caught and move to the catch-display phase; on failure, end
|
||||||
# the cast without a catch.
|
# the cast without a catch.
|
||||||
pass
|
try:
|
||||||
|
targetPos, targetLen = app.skillCheckBars.pop(0)
|
||||||
|
except IndexError:
|
||||||
|
targetPos = targetLen = -1 #make it impossible to succeed, this means they clicked after already getting rid of all bars
|
||||||
|
if not ( 0 < (app.skillCheckAngle - targetPos) < targetLen): #check if we hit a bar
|
||||||
|
#if we succeed, nothing needs to happen, we popped the bar
|
||||||
|
#if we fail, we either didnt hit or missed a past bar, either way we can jump straight to the fail screen
|
||||||
|
app.hookedFish = None
|
||||||
|
app.scene = "result"
|
||||||
|
print("failed",targetPos,targetLen,app.skillCheckAngle)
|
||||||
|
if len(app.skillCheckBars) < 1: #QOL: dont make them wait on an empty loop
|
||||||
|
finishSkillCheck(app)
|
||||||
|
|
||||||
|
|
||||||
|
def finishSkillCheck(app):
|
||||||
|
if len(app.skillCheckBars) > 0:
|
||||||
|
app.hookedFish = None #meaning we failed
|
||||||
|
app.scene = "result"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -162,7 +475,13 @@ def sellCreature(app, creature):
|
|||||||
# difficulty (harder creatures pay more, per the spec). Persist the updated
|
# difficulty (harder creatures pay more, per the spec). Persist the updated
|
||||||
# money via writeSave. Decide here whether selling is automatic on catch or
|
# money via writeSave. Decide here whether selling is automatic on catch or
|
||||||
# a deliberate action in the shop/market.
|
# a deliberate action in the shop/market.
|
||||||
pass
|
|
||||||
|
#selling will be automatic
|
||||||
|
if creature == None: return
|
||||||
|
|
||||||
|
app.save["cash"] += creature["price"]
|
||||||
|
|
||||||
|
writeSave(app.save)
|
||||||
|
|
||||||
|
|
||||||
def buyUpgrade(app, upgradeId):
|
def buyUpgrade(app, upgradeId):
|
||||||
@@ -170,7 +489,18 @@ def buyUpgrade(app, upgradeId):
|
|||||||
# already own it; if valid, deduct money, record ownership in app.save, and
|
# already own it; if valid, deduct money, record ownership in app.save, and
|
||||||
# persist. Upgrades modify the skill check (cursor speed, bar size/count,
|
# persist. Upgrades modify the skill check (cursor speed, bar size/count,
|
||||||
# extra attempts, etc.) and are read back in startSkillCheck.
|
# extra attempts, etc.) and are read back in startSkillCheck.
|
||||||
pass
|
upgrade = [u for u in AVAILABLE_UPGRADES if u[0] == upgradeId][0] #find the specified upgrade
|
||||||
|
name, category, value, price = upgrade
|
||||||
|
print("attempting buy",upgrade)
|
||||||
|
|
||||||
|
if name in app.save["owned_upgrades"]: return False
|
||||||
|
if price > app.save["cash"]: return False
|
||||||
|
print("success!")
|
||||||
|
|
||||||
|
app.save["owned_upgrades"].append(name)
|
||||||
|
app.save["cash"] -= price
|
||||||
|
|
||||||
|
writeSave(app.save)
|
||||||
|
|
||||||
|
|
||||||
def buyBaitLicense(app, baitId):
|
def buyBaitLicense(app, baitId):
|
||||||
@@ -178,14 +508,34 @@ def buyBaitLicense(app, baitId):
|
|||||||
# freely switch to that bait at no further cost (switching handled by
|
# freely switch to that bait at no further cost (switching handled by
|
||||||
# setActiveBait). Bait determines which creatures are reachable in
|
# setActiveBait). Bait determines which creatures are reachable in
|
||||||
# pickRandomCreature. Deduct money, record ownership, persist.
|
# pickRandomCreature. Deduct money, record ownership, persist.
|
||||||
pass
|
bait = [b for b in AVAILABLE_BAITS if b[0] == baitId][0] #find the specified upgrade
|
||||||
|
name, max_difficulty, price, img_file = bait
|
||||||
|
|
||||||
|
print("attempting buy",bait)
|
||||||
|
if name in app.save["owned_baits"]:
|
||||||
|
setActiveBait(app, name)
|
||||||
|
print("activated bait", name)
|
||||||
|
return False
|
||||||
|
if price > app.save["cash"]: return False
|
||||||
|
print("success!")
|
||||||
|
|
||||||
|
app.save["owned_baits"].append(name)
|
||||||
|
app.save["cash"] -= price
|
||||||
|
|
||||||
|
writeSave(app.save)
|
||||||
|
|
||||||
|
|
||||||
def setActiveBait(app, baitId):
|
def setActiveBait(app, baitId):
|
||||||
# Switch the currently-equipped bait to any bait the player already owns a
|
# Switch the currently-equipped bait to any bait the player already owns a
|
||||||
# license for. Free action. Updates app state so the next cast's creature
|
# license for. Free action. Updates app state so the next cast's creature
|
||||||
# pool reflects the new bait. Reject the switch if the bait isn't owned.
|
# pool reflects the new bait. Reject the switch if the bait isn't owned.
|
||||||
pass
|
bait = [b for b in AVAILABLE_BAITS if b[0] == baitId][0] #find the specified upgrade
|
||||||
|
name, max_difficulty, price, img_url = bait
|
||||||
|
|
||||||
|
if name in app.save["owned_baits"]:
|
||||||
|
app.save["current_bait"] = name
|
||||||
|
|
||||||
|
writeSave(app.save)
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -202,49 +552,236 @@ def redrawAll(app):
|
|||||||
# - the active skill-check overlay, or
|
# - the active skill-check overlay, or
|
||||||
# - the catch-display popup.
|
# - the catch-display popup.
|
||||||
# Delegates each piece to the helpers below.
|
# Delegates each piece to the helpers below.
|
||||||
pass
|
if(app.timeSinceInit < 1):
|
||||||
|
preLoadImages(app)
|
||||||
|
|
||||||
|
drawBackground(app)
|
||||||
|
if app.scene == "waiting-for-bite":
|
||||||
|
drawBobber(app)
|
||||||
|
if app.scene == "shop":
|
||||||
|
drawShopEntities(app)
|
||||||
|
if app.scene == "skill-check":
|
||||||
|
drawSkillCheck(app)
|
||||||
|
if app.scene == "result":
|
||||||
|
drawCatchDisplay(app)
|
||||||
|
drawHud(app)
|
||||||
|
|
||||||
|
if app.debug:
|
||||||
|
drawDebug(app)
|
||||||
|
|
||||||
|
#my self-hosted gitea repo also stores the image files, very convienient
|
||||||
|
#necessary because docs say that drawImage requires a url (which is weird but whatever)
|
||||||
|
BASE_IMAGE_URL = "https://gitea.lockeshor.com/lockeshor/15112-fishing-miniproject/raw/branch/main/sprites/"
|
||||||
|
|
||||||
|
#REMINDERS:
|
||||||
|
# drawRect(left, top, width, height, fill="color")
|
||||||
|
# drawImage(url, )
|
||||||
|
# drawLabel(text, centerX, centerY)
|
||||||
def drawBackground(app):
|
def drawBackground(app):
|
||||||
# Draw the static scene layout from the spec: grass behind, the dock, and
|
# Draw the static scene layout from the spec: grass behind, the dock, and
|
||||||
# the lake occupying the bottom ~1/3 of the screen (blue water, optional
|
# the lake occupying the bottom ~1/3 of the screen (blue water, optional
|
||||||
# wave patterns). This is the backdrop present in every scene.
|
# wave patterns). This is the backdrop present in every scene.
|
||||||
pass
|
|
||||||
|
#water
|
||||||
|
drawRect(0, app.waterTop, app.width, app.waterHeight, fill=app.waterColor)
|
||||||
|
#grass
|
||||||
|
drawRect(0, app.grassTop, app.width, app.grassHeight, fill=app.grassColor)
|
||||||
|
#dock
|
||||||
|
dockColor = 'sienna'
|
||||||
|
dockLegColor = 'maroon'
|
||||||
|
drawRect(0,app.grassTop - 50, 200, 40, fill = dockColor)
|
||||||
|
drawRect(40, app.grassTop - 10, 20, app.height, fill = dockLegColor)
|
||||||
|
drawRect(90, app.grassTop - 10, 20, app.height, fill = dockLegColor)
|
||||||
|
drawRect(140, app.grassTop - 10, 20, app.height, fill = dockLegColor)
|
||||||
|
|
||||||
|
#image cred: https://pngtree.com/freepng/cartoon-style-illustration-of-a-young-sleeping-men-fishing-on-a-wood-chair_15775817.html
|
||||||
|
#fisherman
|
||||||
|
drawImage(BASE_IMAGE_URL + "fisherman.png",
|
||||||
|
60, app.grassTop - 50 - 140, width=140, height=170)
|
||||||
|
|
||||||
|
#image cred: https://www.artstation.com/artwork/xzLbe2
|
||||||
|
#salesman
|
||||||
|
drawImage(BASE_IMAGE_URL + "salesman.png",
|
||||||
|
400, app.grassTop - 130, width = 140, height= 160)
|
||||||
|
|
||||||
|
#cash
|
||||||
|
drawLabel(f"${app.save["cash"]}",app.width - 5,5,align="right-top",size=26,fill='darkgoldenrod',font='orbitron')
|
||||||
|
|
||||||
|
|
||||||
def drawPlayerAndRod(app):
|
def drawBobber(app):
|
||||||
# Draw the character sprite seated at the end of the dock with the fishing
|
# Draw the character sprite seated at the end of the dock with the fishing
|
||||||
# rod. When a cast is active, draw the line and the bobber dropping onto the
|
# rod. When a cast is active, draw the line and the bobber dropping onto the
|
||||||
# water surface. Reflects the current fishing phase visually.
|
# water surface. Reflects the current fishing phase visually.
|
||||||
pass
|
|
||||||
|
#cast line
|
||||||
|
drawLine(172, 64, 370, 280, fill="white")
|
||||||
|
|
||||||
|
#bobber?
|
||||||
|
|
||||||
|
|
||||||
def drawShopEntities(app):
|
def drawShopEntities(app):
|
||||||
# Draw the clickable shop/UI sprites standing on the grass (per the spec).
|
# Draw the clickable shop/UI sprites standing on the grass (per the spec).
|
||||||
# Positions here must line up with the hit-testing done in onMousePress so
|
# Positions here must line up with the hit-testing done in onMousePress so
|
||||||
# clicks land on the right entity.
|
# clicks land on the right entity.
|
||||||
pass
|
|
||||||
|
|
||||||
|
#dimming
|
||||||
|
drawRect(0,0,app.width,app.height,opacity=50)
|
||||||
|
#menu background
|
||||||
|
drawRect(50,50,app.width-100,app.height-100,fill='bisque',border='lightCoral',borderWidth=5)
|
||||||
|
#exit "X"
|
||||||
|
drawLine(app.width-45, 5, app.width-5, 45, fill='red', lineWidth=5)
|
||||||
|
drawLine(app.width-5, 5, app.width-45, 45, fill='red', lineWidth=5)
|
||||||
|
|
||||||
|
|
||||||
|
#draw upgrade purchases
|
||||||
|
for name, sceneFilter, left, right, top, bottom in [interactable for interactable in app.INTERACTABLES if interactable[0].startswith("upgrade-buy:")]:
|
||||||
|
#print("drawing",name,left,right,top,bottom)
|
||||||
|
displayName = name.split(":")[1]
|
||||||
|
#bg
|
||||||
|
drawRect(left, top, right - left, bottom - top, fill=('gray' if displayName in app.save["owned_upgrades"] else 'rosyBrown'), border='bisque',borderWidth=4)
|
||||||
|
|
||||||
|
centerY = top+(bottom-top)//2
|
||||||
|
#name
|
||||||
|
|
||||||
|
drawLabel(displayName, left+8, centerY, align="left",size=12)
|
||||||
|
|
||||||
|
#price
|
||||||
|
price = [u for u in AVAILABLE_UPGRADES if u[0] == displayName][0][3]
|
||||||
|
#pricebg
|
||||||
|
drawRect(right-8-40, top + 10, 40, bottom-top-20, fill="gold")
|
||||||
|
drawLabel("$" + str(price), right - 10, centerY, align="right", size = 10)
|
||||||
|
|
||||||
|
#draw bait purchases
|
||||||
|
for name, sceneFilter, left, right, top, bottom in [interactable for interactable in app.INTERACTABLES if interactable[0].startswith("bait-buy:")]:
|
||||||
|
displayName = name.split(":")[1]
|
||||||
|
bait = [b for b in AVAILABLE_BAITS if b[0] == displayName][0]
|
||||||
|
|
||||||
|
drawRect(left, top, right - left, bottom - top, fill=('gray' if displayName in app.save["owned_baits"] else 'rosyBrown'), border=('green' if displayName == app.save["current_bait"] else'bisque'),borderWidth=4)
|
||||||
|
|
||||||
|
drawLabel(displayName, left + (right-left) / 2, bottom - 20, align="center")
|
||||||
|
|
||||||
|
price = bait[2]
|
||||||
|
|
||||||
|
drawLabel("$" + str(price), left + (right-left) / 2, bottom - 10, align="center",size=8)
|
||||||
|
|
||||||
|
|
||||||
|
img_file = bait[3]
|
||||||
|
|
||||||
|
|
||||||
|
#Image creds: ALL ICONS FROM https://www.flaticon.com
|
||||||
|
drawImage(BASE_IMAGE_URL + "bait-icons/" + img_file, left + (right-left) / 2, top + (bottom-top) / 2 - 10, width=50,height=50,align="center")
|
||||||
|
|
||||||
def drawSkillCheck(app):
|
def drawSkillCheck(app):
|
||||||
# Draw the active skill-check overlay: the circle, the target bars placed
|
# Draw the active skill-check overlay: the circle, the target bars placed
|
||||||
# around it (count/length from difficulty), and the rotating cursor at its
|
# around it (count/length from difficulty), and the rotating cursor at its
|
||||||
# current angle. Optionally show which bars are already cleared. Read-only.
|
# current angle. Optionally show which bars are already cleared. Read-only.
|
||||||
pass
|
|
||||||
|
#IMPORTANT: circle is like unit circle for drawing, but we want it to have 0 at the top. transformations will be needed
|
||||||
|
|
||||||
|
#radius of circle
|
||||||
|
r = 90
|
||||||
|
|
||||||
|
#basic path
|
||||||
|
#REMINDER: cx, cy, r
|
||||||
|
drawCircle(300, 130, r, fill=None,border='dimGray')
|
||||||
|
|
||||||
|
#bars
|
||||||
|
for barPos, barLen in app.skillCheckBars:
|
||||||
|
drawArc(300,130, r * 2 + 20, r * 2 + 20, -barPos + 90 - barLen, barLen, fill="white")
|
||||||
|
|
||||||
|
#cover up center of bars
|
||||||
|
drawCircle(300, 130, r - 10, fill=app.background)
|
||||||
|
|
||||||
|
#cursor
|
||||||
|
#REMINDER: width and height are for oval (so r * 2)
|
||||||
|
#REMINDER: cx, cy, width, height, startangle, sweepangle
|
||||||
|
drawArc(300,130, r * 2 + 20, r * 2 + 20, -app.skillCheckAngle + 90, 2, fill="red")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def drawCatchDisplay(app):
|
def drawCatchDisplay(app):
|
||||||
# Draw the "you caught it!" popup: the creature's sprite coming out of the
|
# Draw the "you caught it!" popup: the creature's sprite coming out of the
|
||||||
# water, plus its name, description, and stats (length, weight, value).
|
# water, plus its name, description, and stats (length, weight, value).
|
||||||
# Stays up until dismissed by a click (handled in onMousePress). Read-only.
|
# Stays up until dismissed by a click (handled in onMousePress). Read-only.
|
||||||
pass
|
|
||||||
|
#dimming
|
||||||
|
drawRect(0,0,app.width,app.height,opacity=75)
|
||||||
|
|
||||||
|
|
||||||
|
#star of surprise (turns out I cant make a star with assymetric height/width, so ill have to use an image)
|
||||||
|
#Image cred: https://pngtree.com/free-png-vectors/comics-bubbles
|
||||||
|
drawImage(BASE_IMAGE_URL + "surprise.png", app.width // 2, app.height // 2 - 40, align = "center",width = 530, height = 530)
|
||||||
|
|
||||||
|
|
||||||
|
#draw label
|
||||||
|
if app.hookedFish != None:
|
||||||
|
drawLabel(f"You Caught A {app.hookedFish["name"]}!",app.width // 2,210,align="center",size=18)
|
||||||
|
drawLabel(app.hookedFish["description"],app.width // 2,230,align="center",size=12)
|
||||||
|
#money bonus
|
||||||
|
drawRect(460, 180, 120, 45, align = "center", rotateAngle = -25, fill='mediumSpringGreen', border="black")
|
||||||
|
drawLabel(f"+ ${app.hookedFish["price"]}",460, 180, size=24, align = "center", rotateAngle = -25, fill='black')
|
||||||
|
|
||||||
|
#length and weight
|
||||||
|
drawLabel(f"{app.hookedFish["length"]} in.",app.width // 2 - 40,260,align="center",size=14)
|
||||||
|
drawLabel(f"{app.hookedFish["weight"]} lbs.",app.width // 2 + 40,260,align="center",size=14)
|
||||||
|
|
||||||
|
|
||||||
|
#draw creature/error
|
||||||
|
imgWidth = 160
|
||||||
|
if app.hookedFish != None:
|
||||||
|
trueimgW, trueimgH = getImageSize(BASE_IMAGE_URL + f"fish/{app.hookedFish["sprite-file"]}")
|
||||||
|
imgHeight = imgWidth * (trueimgH / trueimgW)
|
||||||
|
drawImage(BASE_IMAGE_URL + f"fish/{app.hookedFish["sprite-file"]}",
|
||||||
|
app.width // 2,
|
||||||
|
app.height // 2 - 75,
|
||||||
|
align = "center",
|
||||||
|
width=imgWidth,
|
||||||
|
height=imgHeight,
|
||||||
|
rotateAngle = math.sin(app.timeSinceInit / 20) * 10)
|
||||||
|
else:
|
||||||
|
#Image Cred: https://freepngimg.com/png/13474-fail-stamp-free-download-png
|
||||||
|
trueimgW, trueimgH = getImageSize(BASE_IMAGE_URL + "fail.png")
|
||||||
|
imgHeight = imgWidth * (trueimgH / trueimgW)
|
||||||
|
drawImage(BASE_IMAGE_URL + "fail.png",
|
||||||
|
app.width // 2,
|
||||||
|
app.height // 2,
|
||||||
|
align = "center",
|
||||||
|
width=imgWidth,
|
||||||
|
height=imgHeight,
|
||||||
|
rotateAngle = math.sin(app.timeSinceInit / 2) * 30 - 15)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def drawHud(app):
|
def drawHud(app):
|
||||||
# Draw the persistent heads-up display: current money, equipped bait, and
|
# Draw the persistent heads-up display: current money, equipped bait, and
|
||||||
# any brief status/instruction text ("click to cast", etc.). Overlaid on
|
# any brief status/instruction text ("click to cast", etc.). Overlaid on
|
||||||
# every scene so the player always sees their core stats.
|
# every scene so the player always sees their core stats.
|
||||||
pass
|
|
||||||
|
#I dont really need this unless I decide to add alert/tutorial messages
|
||||||
|
#but i can draw the current bait in the corner
|
||||||
|
img_file = [b[3] for b in AVAILABLE_BAITS if b[0] == app.save["current_bait"]][0]
|
||||||
|
drawRect(app.width - 75, app.height - 75, 70, 70, fill='saddleBrown', border="black")
|
||||||
|
drawImage(BASE_IMAGE_URL + "bait-icons/" + img_file, app.width - 10, app.height - 10, width=60,height=60,align="bottom-right")
|
||||||
|
|
||||||
|
def drawDebug(app):
|
||||||
|
debugStr = f"""\
|
||||||
|
mX: {gmouseX}, mY: {gmouseY}
|
||||||
|
Scene: {app.scene}
|
||||||
|
fishingTimer: {rounded(app.fishingTimer)}
|
||||||
|
hookedFish: {app.hookedFish["name"] if app.hookedFish else "N/A"} @ {app.hookedFish["difficulty"] if app.hookedFish else "N/A"} difficulty
|
||||||
|
activeBait: {app.save["current_bait"]}
|
||||||
|
Angle: {rounded(app.skillCheckAngle)}
|
||||||
|
NextBar: {app.skillCheckBars[0] if len(app.skillCheckBars) > 0 else "N/A"}
|
||||||
|
showPatternTimer: {rounded(app.showPatternTimer)}
|
||||||
|
"""
|
||||||
|
debugLines = debugStr.splitlines()
|
||||||
|
size = 16
|
||||||
|
for i in range(len(debugLines)):
|
||||||
|
drawLabel(debugLines[i], 0, app.height - 125 + size * i, fill="white", size=size, align = "left")
|
||||||
|
|
||||||
|
#debug keys
|
||||||
|
drawLabel("' h ' = test fish img, ' m ' = give cash, ' r ' = reset save, ' f ' = skip fishing timer, ' d ' = trigger breakpoint", 5, 5, size=12, align = "top-left")
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -252,7 +789,7 @@ def drawHud(app):
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
def main():
|
def main():
|
||||||
# Start the cmu_graphics app (runApp with the chosen window dimensions).
|
# Start the cmu_graphics app (runApp with the chosen window dimensions).
|
||||||
pass
|
runApp()
|
||||||
|
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"cash": 318.0, "owned_baits": ["Worms", "Prawns"], "owned_upgrades": [], "current_bait": "Prawns"}
|
||||||
@@ -32,4 +32,13 @@ As previously stated, cmu_graphics should be the main backend engine, with the M
|
|||||||
- bottom 1/3 of the screen is the lake/water that you are fishing into, just blue and maybe some wave patterns or improvements
|
- bottom 1/3 of the screen is the lake/water that you are fishing into, just blue and maybe some wave patterns or improvements
|
||||||
- there is a dock with your character sprite sitting on the end with it's fishing rod, with the bobber going down onto the water when you fish
|
- there is a dock with your character sprite sitting on the end with it's fishing rod, with the bobber going down onto the water when you fish
|
||||||
- shop and other ui will appear as sprites standing on grass behind the dock/water area, and are clickable to interact with the shop/ui
|
- shop and other ui will appear as sprites standing on grass behind the dock/water area, and are clickable to interact with the shop/ui
|
||||||
- caught fish should have their sprite appear to come up flying out of the water, and stay on the screen while it displays the fish stats and name + description, until the user clicks it away
|
- caught fish should have their sprite appear to come up flying out of the water, and stay on the screen while it displays the fish stats and name + description, until the user clicks it away
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## UI Planning
|
||||||
|
|
||||||
|
- shop menu includes purchases at the top in 3 columns for each type, with the bait selector / purchaser at the bottom
|
||||||
|
- idle screen has fisherman on dock on left, water in bottom middle, shop guy in top middle, cash and current bait in top right
|
||||||
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 259 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 39 KiB |
@@ -1,5 +1,5 @@
|
|||||||
###############################################
|
###############################################
|
||||||
Total Time: 50 minutes
|
Total Time: 530 minutes
|
||||||
###############################################
|
###############################################
|
||||||
|
|
||||||
###############################################
|
###############################################
|
||||||
@@ -17,3 +17,67 @@ End Time: 9:50pm
|
|||||||
Duration: 10 minutes
|
Duration: 10 minutes
|
||||||
Description: coming up with fish and discussing with the artist
|
Description: coming up with fish and discussing with the artist
|
||||||
###############################################
|
###############################################
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
Date: 06/07-Oct
|
||||||
|
Start Time: 11:30pm
|
||||||
|
End Time: 12:30am
|
||||||
|
Duration: 60 minutes
|
||||||
|
Description: outlining, fleshing out creatures, beginning to implement functions, and reconsidered AI involvement after TA review (ensuring it is not one shotted, so I'm writing all the actual code completely independently)
|
||||||
|
###############################################
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
Date: 07-Oct
|
||||||
|
Start Time: 1:00pm
|
||||||
|
End Time: 1:20pm
|
||||||
|
Duration: 20 minutes
|
||||||
|
Description: adding more upgrades, fixing citations, considering how I actually want game state to progress
|
||||||
|
###############################################
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
Date: 07-Oct
|
||||||
|
Start Time: 1:30pm
|
||||||
|
End Time: 1:50pm
|
||||||
|
Duration: 20 minutes
|
||||||
|
Description: continuing work with making upgrades take effect for skill checks
|
||||||
|
###############################################
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
Date: 08-Oct
|
||||||
|
Start Time: 12:40am
|
||||||
|
End Time: 1:20am
|
||||||
|
Duration: 40 minutes
|
||||||
|
Description: finish up (mostly) basic functions and move onto controller, work out a bunch of finer logic and complex math
|
||||||
|
###############################################
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
Date: 08-Oct
|
||||||
|
Start Time: 7:20pm
|
||||||
|
End Time: 9:40pm
|
||||||
|
Duration: 140 minutes
|
||||||
|
Description: get a decent chunk of the visuals done and finish logic, much closer to being done
|
||||||
|
###############################################
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
Date: 08-Oct
|
||||||
|
Start Time: 10:45pm
|
||||||
|
End Time: 11:15pm
|
||||||
|
Duration: 30 minutes
|
||||||
|
Description: need to take a brief break, just making sure time is accurate
|
||||||
|
###############################################
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
Date: 08/09-Oct
|
||||||
|
Start Time: 11:35pm
|
||||||
|
End Time: 1:55 am
|
||||||
|
Duration: 25 + 60 + 55 = 140 minutes
|
||||||
|
Description: finish pretty much most of the project, all the view phase stuff is in and render properly, also gave me the opportunity to fully playtest and chase down bugs
|
||||||
|
###############################################
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
Date: 09-Oct
|
||||||
|
Start Time: 10:50am
|
||||||
|
End Time: 11:20 am
|
||||||
|
Duration: 30 minutes
|
||||||
|
Description: turn off debug, playtest, run prompt5 and playtest new balancing.
|
||||||
|
###############################################
|
||||||