Files
15112-fishing-miniproject/main.py
T

313 lines
13 KiB
Python

# =============================================================================
# Fishing Mini-Project - Main Game File (OUTLINE ONLY)
# =============================================================================
# This file is a STRUCTURAL OUTLINE: function signatures + descriptive comments.
# The actual implementation will be written by the project author (human).
#
# Engine: cmu_graphics | Pattern: MVC (Model-View-Controller)
# Data: creatures.tsv (tab-separated database at repo root)
# Save: save.json (persistent progress in the script directory)
# =============================================================================
from cmu_graphics import *
import json
# =============================================================================
# SECTION 1: DATA LOADING / FILE I/O
# -----------------------------------------------------------------------------
# Helpers that read from and write to disk. Kept separate from game logic so
# the data source (TSV path, save path) can change without touching gameplay.
# =============================================================================
def loadCreatures(path="creatures.tsv"):
# Read creatures.tsv (tab-separated) and parse it into a usable structure.
# Suggested return: a list of dicts, one per creature, with keys matching
# the header row: name, description, price, sprite-file, difficulty,
# length, weight. Convert numeric columns (price, difficulty, length,
# weight) from strings to int/float here; leave missing fields as sensible
# defaults so partially-filled rows (like the current TSV) don't crash.
creatures = []
with open(path) as f:
lines = f.readlines()
for line in lines[1:]: #ignore header
data = line.split("\t")
creatures.append({
"name": data[0],
"description": data[1],
"price": float(data[2]),
"sprite-file": data[3],
"difficulty": int(data[4]),
"length": float(data[5]), #inches
"weight": float(data[6]) #pounds
})
return creatures
def loadSave(path="save.json"):
# 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
# 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.
defaultState = {
"cash": 0,
"owned_baits": [],
"owned_upgrades": [],
"current_bait": "worm",
"catch_log": []
}
try:
with open(path, "r") as f:
return json.load(f)
except (FileNotFoundError, IOError):
return defaultState
def writeSave(saveState, path="save.json"):
# 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
# sessions. Keep the written structure identical to what loadSave expects.
with open(path, "w") as f: #overwrite existing
f.write(json.dumps(saveState))
# =============================================================================
# SECTION 2: MODEL - STATE SETUP & GAME DATA
# -----------------------------------------------------------------------------
# Everything that initializes and holds game state on the cmu_graphics `app`.
# No drawing and no event handling here - just state.
# =============================================================================
AVAILABLE_UPGRADES = [ # (name, bonus_type, bonus_modifier, price)
#power mods
("Smart Rod", "power", 2, 100),
("Clever Rod", "power", 7, 350),
("Intelligent Rod", "power", 10, 1500),
("Genius Rod", "power", 20, 20000)
]
def onAppStart(app):
# cmu_graphics entry point. Initialize ALL app.* state here:
# - window/layout constants (width, height, dock/water/grass regions)
# - app.creatures (from loadCreatures)
# - app.save (from loadSave) and derived player values
# - fishing state (idle / waiting-for-bite / skill-check / result)
# - skill-check state placeholders (cursor angle, bars, speed, result)
# - app.stepsPerSecond for the game loop cadence
# Delegates to the smaller setup helpers below to keep this readable.
#DRAWING CONSTANTS
app.width = 600
app.height = 400
app.background = 'lightSkyBlue'
app.waterColor = 'midnightBlue'
app.grassColor = 'seaGreen'
app.waterHeight = app.height // 3
app.waterBottom = app.height = app.waterHeight
app.grassHeight = 20
app.grassBottom = 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 = []
app.skillCheckSpeed = 1
app.skillCheckFailed = False
#FISHING
app.fishingTimer = 0
app.hookedFish = None
def resetFishingState(app):
# Return the fishing cycle to its starting "idle" point so a new cast can
# begin. Clears any active skill-check data, the pending random bite timer,
# and the currently-hooked creature. Called at startup and after each catch
# is resolved/dismissed.
pass
def pickRandomCreature(app):
# Choose which creature the player hooks on this cast. Selection is filtered
# by the current bait (bait gates which difficulty tiers are available) and
# weighted toward easier creatures, while still giving rarer/harder ones a
# real chance (per the spec's "skewed toward easier but still fun" goal).
# Returns the chosen creature record (one entry from app.creatures).
pass
def startSkillCheck(app, creature):
# Configure a new skill check based on the hooked creature's difficulty.
# Difficulty maps to: cursor rotation speed, number of target bars, and
# bar length/width. Also apply any owned upgrades that modify these values
# (e.g. slower cursor, wider bars, extra forgiveness). Stores the resulting
# parameters on app for the controller/view to use.
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 = difficulty / fishingPower
pass
# =============================================================================
# SECTION 3: CONTROLLER - GAME LOOP & INPUT HANDLING
# -----------------------------------------------------------------------------
# cmu_graphics event hooks. These advance state and route input; they should
# call Model helpers rather than contain heavy logic themselves.
# =============================================================================
def onStep(app):
# Runs every time step (the game loop / "controller tick").
# Responsibilities depend on the current fishing phase:
# - waiting-for-bite: count down the random timer; when it elapses, pick a
# creature and transition into the skill check.
# - skill-check: advance the cursor's angle around the circle each step.
# - otherwise (idle, viewing a catch, in shop): do nothing time-based.
pass
def onMousePress(app, mouseX, mouseY):
# Primary input router. Behavior depends on app.scene / fishing phase:
# - dock idle: clicking starts a cast (begin waiting-for-bite).
# - skill-check active: register the click as a timing attempt and
# evaluate it via checkSkillHit.
# - catch popup showing: dismiss the popup and reset fishing.
# - shop / UI sprites clicked: open the shop or trigger that UI action.
# Keep this as dispatch only - defer the real work to helper functions.
pass
def onKeyPress(app, key):
# 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
# key during development. Remove if you decide the game is mouse-only.
pass
def checkSkillHit(app):
# Evaluate a skill-check click: is the cursor's current angle inside one of
# the active target bars? Decide hit vs miss. Define the multi-bar flow here
# (e.g. must the player clear every bar, or is one good hit enough?) and
# 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
# the cast without a catch.
pass
# =============================================================================
# SECTION 4: SHOP / ECONOMY LOGIC
# -----------------------------------------------------------------------------
# Buying/selling and the rogue-like upgrade system. Pure state changes that the
# controller invokes; no drawing here.
# =============================================================================
def sellCreature(app, creature):
# Add the creature's market value to the player's money. Value scales with
# difficulty (harder creatures pay more, per the spec). Persist the updated
# money via writeSave. Decide here whether selling is automatic on catch or
# a deliberate action in the shop/market.
pass
def buyUpgrade(app, upgradeId):
# Attempt to purchase an upgrade. Check the player can afford it and doesn't
# already own it; if valid, deduct money, record ownership in app.save, and
# persist. Upgrades modify the skill check (cursor speed, bar size/count,
# extra attempts, etc.) and are read back in startSkillCheck.
pass
def buyBaitLicense(app, baitId):
# Purchase a one-time license for a bait tier. Once owned, the player can
# freely switch to that bait at no further cost (switching handled by
# setActiveBait). Bait determines which creatures are reachable in
# pickRandomCreature. Deduct money, record ownership, persist.
pass
def setActiveBait(app, baitId):
# 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
# pool reflects the new bait. Reject the switch if the bait isn't owned.
pass
# =============================================================================
# SECTION 5: VIEW - DRAWING
# -----------------------------------------------------------------------------
# redrawAll + per-scene draw helpers. These ONLY read state and draw; they must
# never mutate app state. Composed top-down so redrawAll stays legible.
# =============================================================================
def redrawAll(app):
# Master draw routine. Draw the persistent world first (background, grass,
# water, dock, player), then layer the active scene/phase on top:
# - the shop UI, or
# - the active skill-check overlay, or
# - the catch-display popup.
# Delegates each piece to the helpers below.
pass
def drawBackground(app):
# 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
# wave patterns). This is the backdrop present in every scene.
pass
def drawPlayerAndRod(app):
# 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
# water surface. Reflects the current fishing phase visually.
pass
def drawShopEntities(app):
# 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
# clicks land on the right entity.
pass
def drawSkillCheck(app):
# Draw the active skill-check overlay: the circle, the target bars placed
# around it (count/length from difficulty), and the rotating cursor at its
# current angle. Optionally show which bars are already cleared. Read-only.
pass
def drawCatchDisplay(app):
# Draw the "you caught it!" popup: the creature's sprite coming out of the
# water, plus its name, description, and stats (length, weight, value).
# Stays up until dismissed by a click (handled in onMousePress). Read-only.
pass
def drawHud(app):
# Draw the persistent heads-up display: current money, equipped bait, and
# any brief status/instruction text ("click to cast", etc.). Overlaid on
# every scene so the player always sees their core stats.
pass
# =============================================================================
# SECTION 6: PROGRAM ENTRY
# -----------------------------------------------------------------------------
def main():
# Start the cmu_graphics app (runApp with the chosen window dimensions).
pass
main()