pish icons im so tired im almost done

This commit is contained in:
2026-10-09 01:40:37 -04:00
parent 9f3bde4fe8
commit ba1a8e5230
8 changed files with 114 additions and 19 deletions
+20
View File
@@ -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.
###############################################
+10
View File
@@ -0,0 +1,10 @@
###############################################
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?
Response:
###############################################
+82 -17
View File
@@ -104,11 +104,11 @@ AVAILABLE_UPGRADES = [ # (name, bonus_type, bonus_modifier, price)
("Galaxy Rod", "count", 3, 40000) ("Galaxy Rod", "count", 3, 40000)
] ]
AVAILABLE_BAITS = [ # (name, max_difficulty) AVAILABLE_BAITS = [ # (name, max_difficulty, price, image_file)
("Worms", 30), ("Worms", 30, 0, "worm.png"),
("Prawns", 50), ("Prawns", 50, 200, "prawn.png"),
("Tuna", 70), ("Tuna", 70, 1000, "tuna.png"),
("Bass", 100) ("Bass", 100, 19410, "bass.png")
] ]
@@ -122,6 +122,8 @@ def onAppStart(app):
# - 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.
app.debug = True
#DRAWING CONSTANTS #DRAWING CONSTANTS
app.width = 600 app.width = 600
app.height = 400 app.height = 400
@@ -165,18 +167,35 @@ def onAppStart(app):
colInd = 0 colInd = 0
XDiff = (endX-startX) // 3 XDiff = (endX-startX) // 3
#build upgrade grid
for columnType in ["power", "timing", "count"]: for columnType in ["power", "timing", "count"]:
upgrades = [u for u in AVAILABLE_UPGRADES if u[1] == columnType] upgrades = [u for u in AVAILABLE_UPGRADES if u[1] == columnType]
YDiff = (endY-startY) // len(upgrades) YDiff = (endY-startY) // len(upgrades)
for i in range(len(upgrades)): for i in range(len(upgrades)):
app.INTERACTABLES.append(("upgrade-buy:" + upgrades[i][0], app.INTERACTABLES.insert(0, ("upgrade-buy:" + upgrades[i][0],
"shop", "shop",
startX + XDiff * colInd, startX + XDiff * colInd,
startX + XDiff * colInd + XDiff, startX + XDiff * colInd + XDiff,
startY + YDiff * i, startY + YDiff * i,
startY + YDiff * i + YDiff)) 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 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) print(app.INTERACTABLES)
@@ -212,13 +231,17 @@ def pickRandomCreature(app):
total = sum([c[1] for c in possibilities]) total = sum([c[1] for c in possibilities])
#now pick randomly #now pick randomly
roll = random.random() * (total - 0.0001) # a value between 0 and total (minus a small amount so we ensure the while loop triggers) roll = random.random() * (total) # a value between 0 and total
while roll > 0: caughtName = "Aubrey Fish"
possibleCatch = possibilities.pop(0) for possibleCatch in possibilities:
roll -= possibleCatch[1] roll -= possibleCatch[1]
if (roll < 0):
caughtName = possibleCatch[0]
break
catch = [c for c in app.creatures if c["name"] == possibleCatch[0]][0] print(caughtName, app.creatures)
catch = [c for c in app.creatures if c["name"] == caughtName][0]
return catch return catch
@@ -319,6 +342,9 @@ def onMousePress(app, mouseX, mouseY):
if name.startswith("upgrade-buy:"): if name.startswith("upgrade-buy:"):
print("buying upgrade", name) print("buying upgrade", name)
buyUpgrade(app, name.split(":")[1]) 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": if not interacted and app.scene == "idle":
@@ -341,10 +367,14 @@ def onKeyPress(app, key):
#decidedly unused for now, i prefer mouse-only. ill keep the definition here in case i need debug tools. #decidedly unused for now, i prefer mouse-only. ill keep the definition here in case i need debug tools.
# DEBUG # DEBUG
if not app.debug: return
if key == "m": if key == "m":
app.save["cash"] = 1000000 app.save["cash"] = 1000000
if key == "escape": if key == "escape":
os._exit(0) #suggested by AI, instantly exits without hanging like sys.exit(0) os._exit(0) #suggested by AI, instantly exits without hanging like sys.exit(0)
if key == "r":
#reset save
app.save = loadSave("thisisntarealsavefile.stupid")
#added by AI, but simply allows me to trigger a breakpoint manually to check state of app and variables #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 #after some debate, this was the best option over something more complex
@@ -412,9 +442,11 @@ def buyUpgrade(app, upgradeId):
# extra attempts, etc.) and are read back in startSkillCheck. # extra attempts, etc.) and are read back in startSkillCheck.
upgrade = [u for u in AVAILABLE_UPGRADES if u[0] == upgradeId][0] #find the specified upgrade upgrade = [u for u in AVAILABLE_UPGRADES if u[0] == upgradeId][0] #find the specified upgrade
name, category, value, price = upgrade name, category, value, price = upgrade
print("attempting buy",upgrade)
if name in app.save["owned_upgrades"]: return False if name in app.save["owned_upgrades"]: return False
if price > app.save["cash"]: return False if price > app.save["cash"]: return False
print("success!")
app.save["owned_upgrades"].append(name) app.save["owned_upgrades"].append(name)
app.save["cash"] -= price app.save["cash"] -= price
@@ -428,10 +460,12 @@ def buyBaitLicense(app, baitId):
# 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.
bait = [b for b in AVAILABLE_BAITS if b[0] == baitId][0] #find the specified upgrade bait = [b for b in AVAILABLE_BAITS if b[0] == baitId][0] #find the specified upgrade
name, max_difficulty, price = bait name, max_difficulty, price, img_file = bait
print("attempting buy",bait)
if name in app.save["owned_baits"]: return False if name in app.save["owned_baits"]: return False
if price > app.save["cash"]: return False if price > app.save["cash"]: return False
print("success!")
app.save["owned_baits"].append(name) app.save["owned_baits"].append(name)
app.save["cash"] -= price app.save["cash"] -= price
@@ -476,7 +510,7 @@ def redrawAll(app):
if app.scene == "result": if app.scene == "result":
drawCatchDisplay(app) drawCatchDisplay(app)
if app.debug:
drawDebug(app) drawDebug(app)
#my self-hosted gitea repo also stores the image files, very convienient #my self-hosted gitea repo also stores the image files, very convienient
@@ -560,6 +594,20 @@ def drawShopEntities(app):
drawRect(right-8-40, top + 10, 40, bottom-top-20, fill="gold") drawRect(right-8-40, top + 10, 40, bottom-top-20, fill="gold")
drawLabel("$" + str(price), right - 10, centerY, align="right", size = 10) 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]
drawRect(left, top, right - left, bottom - top, fill=('gray' if displayName in app.save["owned_baits"] else 'rosyBrown'), border='bisque',borderWidth=4)
drawLabel(displayName, left + (right-left) / 2, bottom - 20, align="center")
img_file = [b for b in AVAILABLE_BAITS if b[0] == displayName][0][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=80,height=80,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
@@ -599,16 +647,31 @@ def drawCatchDisplay(app):
#star of surprise (turns out I cant make a star with assymetric height/width, so ill have to use an image) #star of surprise (turns out I cant make a star with assymetric height/width, so ill have to use an image)
drawImage(BASE_IMAGE_URL + "surprise.png", app.width // 2, app.height // 2) #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 #draw creature/error
imgWidth = 300 imgWidth = 200
if app.hookedFish != None: if app.hookedFish != None:
trueimgW, trueimgH = getImageSize(BASE_IMAGE_URL + f"fish/{app.hookedFish["sprite-file"]}") trueimgW, trueimgH = getImageSize(BASE_IMAGE_URL + f"fish/{app.hookedFish["sprite-file"]}")
imgHeight = imgWidth * (trueimgH / trueimgW) imgHeight = imgWidth * (trueimgH / trueimgW)
drawImage(BASE_IMAGE_URL + f"fish/{app.hookedFish["sprite-file"]}", drawImage(BASE_IMAGE_URL + f"fish/{app.hookedFish["sprite-file"]}",
app.width // 2, app.width // 2,
app.height // 2 - 50, app.height // 2 - 75,
align = "center", align = "center",
width=imgWidth, width=imgWidth,
height=imgHeight, height=imgHeight,
@@ -619,7 +682,7 @@ def drawCatchDisplay(app):
imgHeight = imgWidth * (trueimgH / trueimgW) imgHeight = imgWidth * (trueimgH / trueimgW)
drawImage(BASE_IMAGE_URL + "fail.png", drawImage(BASE_IMAGE_URL + "fail.png",
app.width // 2, app.width // 2,
app.height // 2 - 50, app.height // 2,
align = "center", align = "center",
width=imgWidth, width=imgWidth,
height=imgHeight, height=imgHeight,
@@ -631,6 +694,8 @@ 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.
#I dont really need this unless I decide to add alert/tutorial messages
pass pass
def drawDebug(app): def drawDebug(app):
+1 -1
View File
@@ -1 +1 @@
{"cash": 863268.0, "owned_baits": ["Worms"], "owned_upgrades": ["Spectral Rod", "Galaxy Rod"], "current_bait": "Worms", "catch_log": []} {"cash": 810706.0, "owned_baits": ["Worms", "Prawns", "Tuna", "Bass"], "owned_upgrades": ["Spectral Rod", "Galaxy Rod", "Glowing Rod", "Massive Bobber", "Huge Bobber", "Large Bobber", "Medium Bobber"], "current_bait": "Worms", "catch_log": []}
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB