# Project export: The Gospel Game

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2026
- Tagline: Classic 8-Bit Miracles, Driven by the Cloud.
- Devpost: https://devpost.com/software/the-gospel-game
- GitHub: https://github.com/PRINT-Phishingisgood/Bible-Game-Walkthrough
- Video: https://www.youtube.com/embed/cOuG_5IjlD8?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — PRINT-Phishingisgood (9 commits), Anisa Chang (4 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

Here is an updated, polished version of your `README.md` that reflects all the massive upgrades, new mini-games, and Redis Cloud features you’ve built into the project!

You can copy and paste this directly into your repository:

---

# Bible Quest 🕹️

**Play the Stories. Conquer the Leaderboard.**

A top-down 2D arcade ecosystem built in Python/Pygame. Players step into a retro arcade hall, select their hero, and walk up to distinct cabinets to play custom, procedurally drawn minigames based on classic biblical narratives.

The entire arcade is backed by a live **Redis Cloud** database for global leaderboards, persistent player wallets, and live remote game balancing.

## 🚀 How to Run

Because the game now connects to the cloud, you will need to install the Redis python library alongside Pygame.

```bash
pip install pygame redis
python main.py

```

## 📂 Project Structure

```text
bible_game/
├── main.py                # The Arcade World — walkable hub & Redis engine
├── character_select.py    # Hero selection screen (Shepherd or Mary)
├── sheep_maze.py          # Game 1: The Lost Sheep (Luke 15:3–7)
├── fish_coin.py           # Game 2: Deep Sea Hook (Matthew 17:27)
├── david_sling.py         # Game 3: Sling Artillery (1 Samuel 17)
├── feed_crowd.py          # Game 4: Catch & Multiply (Matthew 14:20)
├── babel_tower.py         # Game 5: Tower of Babel (Genesis 11)
└── README.md              # This file

```

## 🎮 Controls

| Key | Action |
| --- | --- |
| **WASD / Arrows** | Move your character around the arcade / play minigames |
| **ENTER** | Interact with an arcade cabinet / confirm selection |
| **ESC** | Open Settings Menu / Return to the Arcade Hall |

## 🌟 Features

* **Cloud Leaderboards:** Instant $O(\log N)$ global rank updates submitted directly to Redis Sorted Sets.
* **Persistent Profiles:** Earn "Fish Coins" in minigames that save to your hero's permanent cloud wallet.
* **Live Game Tuning:** Minigame physics (like Goliath's walk speed or stone gravity) are pulled from a live Redis Hash (`game:config`), allowing the developer to re-balance the game without modifying local code.
* **Zero External Assets:** Every sprite, bush, brick, and giant is calculated mathematically using layered geometric primitives (`pygame.draw`).

## 🕹️ The Arcade Cabinets

### 🐑 The Lost Sheep

Find all 5 sheep hidden in a bush-filled maze. The maze is covered in fog — you can only see a small circle around your shepherd. *Based on Luke 15:4.*

### 🐋 Deep Sea Arcade Hook

Cast your line into the depths, dodge obstacles, and surface safely to discover a piece of money inside the fish. Earn Fish Coins for your cloud wallet based on your speed! *Based on Matthew 17:27.*

### 🪨 Sling Artillery (David & Goliath)

A physics-based projectile game with sub-stepped precision hitboxes. Calculate your trajectory and strike Goliath before he crosses the screen. *Based on 1 Samuel 17.*

### 🍞 Catch and Multiply (Feeding the 5,000)

An accelerating inventory-catching game. Move quickly to catch the falling loaves and fishes to feed the growing crowd. *Based on Matthew 14:20.*

### 🧱 Tower of Babel

A frantic platformer where multi-language confusion periodically reverses your controls. *Based on Genesis 11.*

## 🛠️ Technical Notes

* **Maze algorithm:** Recursive Backtracker (Depth-First Search) generating perfect labyrinths.
* **Physics Integration:** Semi-implicit Euler method utilizing discrete sub-stepping ($\Delta t_{\text{sub}}$) to prevent high-velocity projectiles from tunneling through hitboxes.
* **Network Stability:** Strict socket timeouts insulate the `pygame` loop from network latency or dropped packets.


## Detected evidence (automated analysis)

Indexed codebase: 8 recognized source files, 121 KB.
- Python (language) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code
- Redis (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (9 of 9)

```
babel_tower.py
character_select.py
david_sling.py
feed_crowd.py
fish_coin.py
main.py
README.md
samson_pillar
sheep_maze.py
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- Add files via upload
- Update character_select.py
- Add files via upload
- Delete fish_coin
- Delete feed_crowd
- Delete david_sling
- Update README.md
- Add files via upload
- Implement Pillar Balance game with Samson theme
- Add Sling Artillery game with David and Goliath
- Add 'Catch and Multiply' game implementation
- Add Deep Sea Arcade Hook game implementation
- Add files via upload
- Initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### main.py

```python
"""
Bible Quest — Arcade World
===========================
A top-down walkable arcade hall with an active Redis Cloud Leaderboard.
Approach a door cabinet and press ENTER to play that Bible story mini-game.
"""

import pygame
import sys
import math
import random
import character_select
import redis  # Restored to standard desktop library

# ──────────────────────────────────────────────────────────────────────────────
# INIT
# ──────────────────────────────────────────────────────────────────────────────
pygame.init()
pygame.font.init()

SCREEN_W = 1100
SCREEN_H = 700
screen   = pygame.display.set_mode((SCREEN_W, SCREEN_H))
pygame.display.set_caption("Bible Quest — Arcade World")
clock    = pygame.time.Clock()

global_volume = 70
global_fish_coins = 0  # Globally tracked inventory counter synced via cloud save profile

# ──────────────────────────────────────────────────────────────────────────────
# REDIS CLOUD CONFIGURATION & SETUP (RESTORED)
# ──────────────────────────────────────────────────────────────────────────────
REDIS_HOST = 'expansive-wood-substance-85250.db.redis.io'
REDIS_PORT = 12993
REDIS_PASSWORD = '3Yh0iQlU0oVAiLfyXNrwTHSWhGF6KRty'

try:
    r = redis.Redis(
        host=REDIS_HOST,
        port=REDIS_PORT,
        password=REDIS_PASSWORD,
        decode_responses=True,
        socket_timeout=4
    )
    if r.ping():
        print(" Successfully connected to Redis Cloud! Leaderboards are live.")
except Exception as e:
    print(f"⚠️ Redis initialization skipped/failed: {e}")
    r = None

def save_high_score(game_id, player_name, score):
    """Saves or updates a high score using a Sorted Set (ZSET)."""
    if r:
        try:
            r.zadd(f"leaderboard:{game_id}", {player_name: score})
            print(f" Saved score of {score} for {player_name} to cloud!")
        except Exception as e:
            print(f" Could not post score to Redis: {e}")

def get_top_scores(game_id, limit=5):
    """Retrieves top ranks sorted in descending order from Redis Cloud."""
    if r:
        try:
            return r.zrevrange(f"leaderboard:{game_id}", 0, limit - 1, withscores=True)
        except Exception as e:
            print(f" Could not read cloud scores: {e}")
    return []

# ──────────────────────────────────────────────────────────────────────────────
# NEW CLOUD PROFILE & BALANCING ENGINE CORES (RESTORING MISSING CONFIG CORES)
# ──────────────────────────────────────────────────────────────────────────────
def load_player_profile(player_name):
    """Fetches user currency balance and stats persistently from the cloud."""
    global global_fish_coins
    if r:
        try:
            profile_key = f"player:{player_name}"
            if r.exists(profile_key):
                data = r.hgetall(profile_key)
                global_fish_coins = int(data.get("fish_coins", 0))
                print(f" Loaded {player_name}'s cloud profile. Wallet Balance: {global_fish_coins} Fish Coins.")
            else:
                # Initialize fields
                r.hset(profile_key, mapping={"fish_coins": 0, "chosen_skin": player_name})
                global_fish_coins = 0
                print(f" Created brand new cloud profile for {player_name}.")
        except Exception as e:
            print(f" ⚠️ Profile fetch skipped: {e}")

def save_player_profile(player_name):
    """Saves current wallet variables cleanly into an online hash mapping container."""
    if r:
        try:
            profile_key = f"player:{player_name}"
            r.hset(profile_key, mapping={"fish_coins": global_fish_coins})
            print(f" Safely backed up {player_name}'s balance ({global_fish_coins} coins) to Redis Cloud.")
        except Exception as e:
            print(f" Could not update cloud backup hash: {e}")

def get_live_game_config():
    """Queries configuration hash for active balancing metrics from cloud."""
    defaults = {"goliath_speed": 12.0, "gravity": 1350.0, "player_speed": 5.0}
    if r:
        try:
            config_key = "game:config"
            if r.exists(config_key):
                cloud_data = r.hgetall(config_key)
                return {k: float(cloud_data.get(k, v)) for k, v in defaults.items()}
            else:
                r.hset(config_key, mapping={k: str(v) for k, v in defaults.items()})
        except Exception as e:
            print(f" Custom config query failed, pulling default constants: {e}")
    return defaults

# ──────────────────────────────────────────────────────────────────────────────
# WORLD DIMENSIONS
# ──────────────────────────────────────────────────────────────────────────────
WORLD_W = 2200
WORLD_H = 1100

TILE      = 48
PLAYER_R  = 14
SPEED     = 3

# Transformed floor colors into warm sand shades
C_FLOOR_A    = (235, 205, 145)
C_FLOOR_B    = (225, 195, 135)
C_FLOOR_GRID = (210, 180, 120)
C_WALL       = ( 38,  28,  18)
C_WALL_FACE  = ( 55,  42,  28)
C_TORCH_ORG  = (255, 140,  30)
C_TORCH_YEL  = (255, 220,  80)
C_GOLD       = (210, 170,  50)
C_GOLD_LIT   = (255, 215,  80)
C_PARCHMENT  = (230, 205, 155)
C_TEXT_LIGHT = (240, 220, 170)
C_TEXT_DIM   = (160, 130,  80)
C_DARK       = (  8,   5,   2)
C_WHITE      = (255, 255, 255)

CAB_COLORS = [
    {"wood": ( 90,  50,  15), "glow": ( 80, 200, 120), "trim": (120,  80,  30)},
    {"wood": ( 60,  30,  80), "glow": (150, 100, 255), "trim": ( 90,  55, 120)},
    {"wood": ( 80,  20,  20), "glow": (255,  80,  60), "trim": (120,  45,  40)},
    {"wood": ( 20,  50,  70), "glow": ( 60, 180, 255), "trim": ( 35,  80, 110)},
]

def mf(size, bold=False):
    for n in ["Segoe UI Symbol", "Arial", "Georgia", "Times New Roman", None]:
        try:  return pygame.font.SysFont(n, size, bold=bold)
        except Exception: pass
    return pygame.font.Font(None, size)

def mf2(size, bold=False):
    for n in ["Courier New", "Courier", "monospace", None]:
    
[truncated — 24725 more characters]
```

### character_select.py

```python
import pygame
import sys
import math


def run_character_selection():
    """Standard local desktop character selector loop."""
    pygame.init()
    pygame.font.init()

    w, h = 1100, 700
    screen = pygame.display.set_mode((w, h))
    pygame.display.set_caption("Bible Quest — Character Selection")
    clock = pygame.time.Clock()

    # Fonts
    font_main_title = pygame.font.SysFont("Georgia", 48, bold=True)
    font_title = pygame.font.SysFont("Georgia", 32, bold=True)
    font_author = pygame.font.SysFont("Georgia", 16, italic=True)
    font_med = pygame.font.SysFont("Arial", 22)
    font_sm = pygame.font.SysFont("Arial", 16)

    # Options
    characters = [
        {"name": "Shepherd", "color": (170, 130, 70), "inner": (145, 108, 55), "cloth": (140, 105, 55),
         "has_staff": True},
        {"name": "Mary", "color": (80, 120, 190), "inner": (60, 95, 160), "cloth": (220, 220, 240), "has_staff": False}
    ]
    selected = 0

    def draw_preview(surf, cx, cy, char, bob):
        pygame.draw.ellipse(surf, (15, 10, 5), pygame.Rect(cx - 24, cy + 28, 48, 16))
        robe_pts = [(cx, cy - 40 + bob), (cx - 22, cy + 36 + bob), (cx + 22, cy + 36 + bob)]
        pygame.draw.polygon(surf, char["color"], robe_pts)
        inner_pts = [(cx, cy - 28 + bob), (cx - 10, cy + 28 + bob), (cx + 10, cy + 28 + bob)]
        pygame.draw.polygon(surf, char["inner"], inner_pts)

        head_y = cy - 60 + bob
        pygame.draw.circle(surf, (210, 170, 115), (cx, head_y), 20)
        pygame.draw.arc(surf, char["cloth"], pygame.Rect(cx - 22, head_y - 22, 44, 28), 0, 3.14, 6)
        pygame.draw.circle(surf, (40, 25, 10), (cx - 6, head_y + 4), 4)
        pygame.draw.circle(surf, (40, 25, 10), (cx + 6, head_y + 4), 4)

        if char["has_staff"]:
            pygame.draw.line(surf, (100, 65, 25), (cx + 26, cy + 36 + bob), (cx + 26, cy - 70 + bob), 5)
            pygame.draw.arc(surf, (100, 65, 25), pygame.Rect(cx + 12, cy - 86 + bob, 28, 20), 0.6 * 3.14, 2 * 3.14, 5)

    t = 0
    running = True
    while running:
        t += 1
        bob = int(4 * math.sin(t * 0.1))

        screen.fill((25, 18, 12))

        main_title_surf = font_main_title.render("The Gospel Game", True, (235, 190, 60))
        screen.blit(main_title_surf, main_title_surf.get_rect(center=(w // 2, 45)))

        author_surf = font_author.render("Created by Andrew Zheng & Anisa Chang", True, (160, 130, 80))
        screen.blit(author_surf, author_surf.get_rect(center=(w // 2, 85)))

        title_surf = font_title.render("SELECT YOUR HERO", True, (210, 170, 50))
        screen.blit(title_surf, title_surf.get_rect(center=(w // 2, 140)))

        for idx, char in enumerate(characters):
            box_x = w // 2 - 320 + idx * 360
            box_y = 200
            box_rect = pygame.Rect(box_x, box_y, 280, 340)

            bg_col = (50, 38, 26) if idx == selected else (35, 26, 18)
            border_col = (255, 215, 80) if idx == selected else (80, 60, 45)
            border_w = 4 if idx == selected else 2

            pygame.draw.rect(screen, bg_col, box_rect, border_radius=12)
            pygame.draw.rect(screen, border_col, box_rect, border_w, border_radius=12)

            draw_preview(screen, box_rect.centerx, box_rect.centery - 20, char, bob if idx == selected else 0)

            lbl = font_med.render(char["name"], True, (240, 220, 170) if idx == selected else (160, 130, 80))
            screen.blit(lbl, lbl.get_rect(center=(box_rect.centerx, box_rect.bottom - 40)))

        hint = font_sm.render("Use LEFT / RIGHT Arrow Keys to Switch Selection • Press ENTER to Confirm", True,
                              (130, 100, 60))
        screen.blit(hint, hint.get_rect(center=(w // 2, 620)))

        pygame.display.flip()
        clock.tick(60)

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN:
                if event.key in [pygame.K_LEFT, pygame.K_a, pygame.K_RIGHT, pygame.K_d]:
                    selected = 1 - selected
                if event.key in [pygame.K_RETURN, pygame.K_KP_ENTER]:
                    return characters[selected]
```

### sheep_maze.py

```python
"""
The Lost Sheep — Maze Game
===========================
Based on Luke 15:3–7 — "What man of you, having a hundred sheep, if he has
lost one of them, does not leave the ninety-nine ... and go after the one
that is lost?"

Optimized for desktop PyCharm execution windows with native score feedback
hooking mechanisms for automated Redis Cloud Leaderboard synchronization.
"""

import pygame
import sys
import math
import random

# ─── Constants ───────────────────────────────────────────────────────────────
CELL       = 40
COLS       = 19
ROWS       = 15
SCREEN_W   = COLS * CELL
SCREEN_H   = ROWS * CELL + 80

VISION_RADIUS = 130
PLAYER_SPEED  = 3
NUM_SHEEP     = 5

# ─── Colours ─────────────────────────────────────────────────────────────────
C_BG         = ( 20,  40,  10)
C_PATH       = ( 80, 120,  50)
C_WALL_BASE  = ( 30,  60,  20)
C_BUSH_DARK  = ( 20,  70,  15)
C_BUSH_MID   = ( 35,  95,  25)
C_BUSH_LITE  = ( 55, 120,  35)
C_BUSH_HIGH  = ( 75, 140,  45)
C_SHEEP_BODY = (230, 230, 215)
C_SHEEP_LEG  = (100,  80,  60)
C_SHEEP_FACE = (160, 130, 100)
C_PLAYER     = (139,  90,  43)
C_PLAYER_ROBE= (180, 140,  80)
C_STAFF      = (110,  70,  30)
C_GOLD       = (220, 180,  50)
C_HUD_BG     = ( 25,  15,   5)
C_WHITE      = (255, 255, 255)
C_FOG        = (  0,   0,   0)

WALL = 0
PATH = 1

def generate_maze(cols, rows):
    grid = [[WALL] * cols for _ in range(rows)]

    def carve(r, c):
        grid[r][c] = PATH
        directions = [(0, 2), (0, -2), (2, 0), (-2, 0)]
        random.shuffle(directions)
        for dr, dc in directions:
            nr, nc = r + dr, c + dc
            if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == WALL:
                grid[r + dr // 2][c + dc // 2] = PATH
                carve(nr, nc)

    carve(1, 1)
    return grid

# ─── Procedural Sprite Drawers ────────────────────────────────────────────────

def draw_bush(surface, px, py, seed):
    rng = random.Random(seed)
    cx, cy = px + CELL // 2, py + CELL // 2
    r = CELL // 2 - 2

    pygame.draw.circle(surface, C_BUSH_DARK, (cx, cy), r)
    for _ in range(4):
        ox = rng.randint(-r // 2, r // 2)
        oy = rng.randint(-r // 2, r // 2)
        pygame.draw.circle(surface, C_BUSH_MID, (cx + ox, cy + oy), r - 4)
    for _ in range(3):
        ox = rng.randint(-r // 3, r // 3)
        oy = rng.randint(-r // 2, -2)
        pygame.draw.circle(surface, C_BUSH_LITE, (cx + ox, cy + oy), r - 8)
    pygame.draw.circle(surface, C_BUSH_HIGH, (cx + rng.randint(-4, 4), cy - r // 3), r // 4)


def draw_sheep(surface, px, py, collected=False):
    if collected:
        return
    pygame.draw.ellipse(surface, C_SHEEP_BODY, pygame.Rect(px - 14, py - 9, 28, 18))
    for bx, by in [(-8, -12), (0, -14), (8, -12)]:
        pygame.draw.circle(surface, C_SHEEP_BODY, (px + bx, py + by), 7)
    pygame.draw.circle(surface, C_SHEEP_FACE, (px + 16, py - 4), 7)
    pygame.draw.circle(surface, (30, 20, 10), (px + 18, py - 6), 2)
    for lx in [-8, -2, 6, 12]:
        pygame.draw.line(surface, C_SHEEP_LEG, (px + lx, py + 9), (px + lx, py + 17), 2)


def draw_player(surface, px, py, skin_data=None):
    """Draw the shepherd using dynamic configuration mapping options."""
    if skin_data:
        robe_color  = skin_data["color"]
        inner_color = skin_data["inner"]
        cloth_color = skin_data["cloth"]
        has_staff   = skin_data.get("has_staff", False)
    else:
        robe_color  = C_PLAYER_ROBE
        inner_color = C_PLAYER
        cloth_color = C_PLAYER
        has_staff   = True

    # Staff tool asset parsing
    if has_staff:
        pygame.draw.line(surface, C_STAFF, (px + 10, py - 20), (px + 10, py + 20), 3)
        pygame.draw.arc(surface, C_STAFF, pygame.Rect(px + 4, py - 26, 14, 12), math.pi * 0.8, math.pi * 2.2, 3)

    # Robe geometry construction vectors
    points = [(px, py - 14), (px - 9, py + 18), (px + 9, py + 18)]
    pygame.draw.polygon(surface, robe_color, points)

    # Inner dynamic vest panel accenting
    inner_pts = [(px, py - 6), (px - 4, py + 18), (px + 4, py + 18)]
    pygame.draw.polygon(surface, inner_color, inner_pts)

    # Belt loop line
    pygame.draw.line(surface, inner_color, (px - 7, py + 2), (px + 7, py + 2), 2)

    # Head & Head Cloth shroud overlay
    pygame.draw.circle(surface, (200, 160, 110), (px, py - 18), 8)
    pygame.draw.arc(surface, cloth_color, pygame.Rect(px - 9, py - 28, 18, 16), 0, math.pi, 3)


def build_maze_surface(grid):
    surf = pygame.Surface((COLS * CELL, ROWS * CELL))
    for r in range(ROWS):
        for c in range(COLS):
            px, py = c * CELL, r * CELL
            if grid[r][c] == PATH:
                pygame.draw.rect(surf, C_PATH, pygame.Rect(px, py, CELL, CELL))
                rng = random.Random(r * 1000 + c)
                for _ in range(6):
                    gx = px + rng.randint(2, CELL - 2)
                    gy = py + rng.randint(2, CELL - 2)
                    pygame.draw.circle(surf, (60, 100, 35), (gx, gy), 1)
            else:
                pygame.draw.rect(surf, C_WALL_BASE, pygame.Rect(px, py, CELL, CELL))
                draw_bush(surf, px, py, seed=r * 1000 + c)
    return surf


def build_fog_surface(width, height, cx, cy, radius):
    fog = pygame.Surface((width, height), pygame.SRCALPHA)
    fog.fill((0, 0, 0, 255))

    steps = 30
    for i in range(steps):
        frac = i / steps
        r = int(radius * (1 - frac))
        alpha = int(255 * frac * frac)
        pygame.draw.circle(fog, (0, 0, 0, alpha), (cx, cy), r)

    return fog


def draw_hud(surface, found, total, verse_surf, font_hud, font_small):
    hud_rect = pygame.Rect(0, ROWS * CELL, SCREEN_W, 80)
    pygame.draw.rect(surface, C_HUD_BG, hud_rect)
    pygame.draw.line(surface, C_GOLD, (0, ROWS * CELL), (SCREEN_W, ROWS * CELL), 2)

    label = font_hud.render(f"Sheep found: {found} / {tota
[truncated — 6281 more characters]
```

### babel_tower.py

```python
"""
Tower of Babel - Action Builder
================================
Based on Genesis 11. Navigate the scaffolding to build the tower, but avoid
the other workers—their confused languages will temporarily reverse your controls!

Optimized for desktop PyCharm execution and Redis Cloud Leaderboard synchronization.
"""

import pygame
import sys
import random

# ──────────────────────────────────────────────────────────────────────────────
# INIT & CONSTANTS
# ──────────────────────────────────────────────────────────────────────────────
SCREEN_W = 1100
SCREEN_H = 700

# Colors
C_SKY = (135, 206, 235)
C_CLOUDS = (255, 255, 255)
C_BRICK = (180, 70, 40)
C_BRICK_OUT = (100, 30, 20)
C_WOOD = (139, 69, 19)
C_SCAFFOLD = (205, 133, 63)
C_PLAYER = (40, 100, 200)
C_WORKER_SHIRT = (200, 180, 40)
C_SKIN = (220, 180, 140)
C_HAT = (200, 50, 50)
C_CONFUSED = (255, 100, 255)
C_TEXT = (255, 255, 255)
C_UI_BG = (0, 0, 0, 150)

GRAVITY = 0.5
PLAYER_SPEED = 5
JUMP_POWER = -10
CONFUSION_TIME = 180


def mf(size, bold=False):
    for n in ["Segoe UI Symbol", "Arial", "Georgia", None]:
        try:
            return pygame.font.SysFont(n, size, bold=bold)
        except Exception:
            pass
    return pygame.font.Font(None, size)


# ──────────────────────────────────────────────────────────────────────────────
# GAME ENTITIES
# ──────────────────────────────────────────────────────────────────────────────
class Player:
    def __init__(self, skin_data=None):
        self.rect = pygame.Rect(100, SCREEN_H - 80, 20, 40)
        self.vel_y = 0
        self.on_ground = False
        self.on_ladder = False
        self.has_brick = False
        self.confusion_timer = 0
        self.fnt_small = mf(16, bold=True)

        # Configure local player palette from chosen character selection matrix
        if skin_data:
            self.base_color = skin_data["color"]
            self.skin_color = (210, 170, 115)
        else:
            self.base_color = C_PLAYER
            self.skin_color = C_SKIN

    def draw(self, surf):
        is_confused = self.confusion_timer > 0
        shirt_color = C_CONFUSED if is_confused and (self.confusion_timer // 5) % 2 == 0 else self.base_color

        pygame.draw.rect(surf, shirt_color, (self.rect.x, self.rect.y + 15, 20, 25))
        pygame.draw.rect(surf, (0, 0, 0), (self.rect.x, self.rect.y + 15, 20, 25), 2)
        pygame.draw.circle(surf, self.skin_color, (self.rect.centerx, self.rect.y + 10), 10)

        if self.has_brick:
            pygame.draw.rect(surf, C_BRICK, (self.rect.centerx - 15, self.rect.top - 20, 30, 20))
            pygame.draw.rect(surf, C_BRICK_OUT, (self.rect.centerx - 15, self.rect.top - 20, 30, 20), 2)

        if is_confused:
            txt = self.fnt_small.render("CONFUSED!", True, (255, 50, 50))
            surf.blit(txt, txt.get_rect(center=(self.rect.centerx, self.rect.top - (30 if self.has_brick else 15))))


class Worker:
    def __init__(self, x, y, speed, min_x, max_x):
        self.rect = pygame.Rect(x, y, 20, 40)
        self.speed = speed
        self.min_x = min_x
        self.max_x = max_x

    def update(self):
        self.rect.x += self.speed
        if self.rect.left <= self.min_x:
            self.rect.left = self.min_x + 1
            self.speed *= -1
        elif self.rect.right >= self.max_x:
            self.rect.right = self.max_x - 1
            self.speed *= -1

    def draw(self, surf):
        pygame.draw.rect(surf, C_WORKER_SHIRT, (self.rect.x, self.rect.y + 15, 20, 25))
        pygame.draw.rect(surf, (0, 0, 0), (self.rect.x, self.rect.y + 15, 20, 25), 2)
        pygame.draw.circle(surf, C_SKIN, (self.rect.centerx, self.rect.y + 10), 10)
        pygame.draw.polygon(surf, C_HAT, [(self.rect.x - 2, self.rect.y + 5), (self.rect.right + 2, self.rect.y + 5),
                                          (self.rect.centerx, self.rect.y - 5)])

        pygame.draw.circle(surf, (255, 255, 255), (self.rect.centerx + 15, self.rect.top - 10), 10)
        pygame.draw.circle(surf, (0, 0, 0), (self.rect.centerx + 15, self.rect.top - 10), 10, 1)
        pygame.draw.line(surf, (0, 0, 0), (self.rect.centerx + 10, self.rect.top - 12),
                         (self.rect.centerx + 20, self.rect.top - 12), 2)
        pygame.draw.line(surf, (0, 0, 0), (self.rect.centerx + 12, self.rect.top - 8),
                         (self.rect.centerx + 18, self.rect.top - 8), 2)


# ──────────────────────────────────────────────────────────────────────────────
# MAIN RUN FUNCTION
# ──────────────────────────────────────────────────────────────────────────────
def run(skin_data=None):
    screen = pygame.display.set_mode((SCREEN_W, SCREEN_H))
    pygame.display.set_caption("Tower of Babel - Action Builder")
    clock = pygame.time.Clock()

    fnt_big = mf(36, bold=True)
    fnt_med = mf(24)

    player = Player(skin_data)

    platforms = [
        pygame.Rect(0, SCREEN_H - 40, SCREEN_W, 40),
        pygame.Rect(150, SCREEN_H - 180, 800, 20),
        pygame.Rect(150, SCREEN_H - 320, 800, 20),
        pygame.Rect(250, SCREEN_H - 460, 600, 20),
        pygame.Rect(350, SCREEN_H - 600, 400, 20),
    ]

    ladders = [
        pygame.Rect(200, SCREEN_H - 180, 40, 140),
        pygame.Rect(850, SCREEN_H - 180, 40, 140),
        pygame.Rect(530, SCREEN_H - 320, 40, 140),
        pygame.Rect(300, SCREEN_H - 460, 40, 140),
        pygame.Rect(750, SCREEN_H - 460, 40, 140),
        pygame.Rect(530, SCREEN_H - 600, 40, 140),
    ]

    workers = [
        Worker(300, SCREEN_H - 220, 2, 150, 950),
        Worker(600, SCREEN_H - 220, -2.5, 150, 950),
        Worker(200, SCREEN_H - 360, 3, 150, 950),
        Worker(700, SCREEN_H - 360, -2, 150, 950),
        Worker(400, SCREEN_H - 500, 3.5, 250, 850),
    ]

    supply_zone = pygame.Rect(50, SCREEN_H - 100, 80, 60)
    target_zone = pygame.Rect(500, SCREEN_H - 680, 10
[truncated — 6125 more characters]
```

### feed_crowd.py

```python
"""
Catch and Multiply — Feeding of the 5,000
============================================
"And they all ate and were filled... and they took up of the fragments
that remained twelve baskets full." — Matthew 14:20 (KJV)
"""

import pygame
import sys
import math
import random

# ─── Screen Constants ───────────────────────────────────────────────────────
SCREEN_W, SCREEN_H = 800, 600
HUD_HEIGHT = 90
GROUND_Y = SCREEN_H - 60          # where the disciple stands

# ─── Gameplay Constants ──────────────────────────────────────────────────────
SURVIVE_SECONDS     = 60.0
PLAYER_SPEED        = 360.0       # px/sec
BASKET_W, BASKET_H  = 70, 36

ITEM_FALL_SPEED_MIN = 140.0
ITEM_FALL_SPEED_MAX = 230.0
SPAWN_INTERVAL_MIN  = 0.35        # seconds between spawns
SPAWN_INTERVAL_MAX  = 0.85

ROCK_CHANCE         = 0.16        # probability a spawned item is a rock
CATCH_MULTIPLY      = 3           # +3 per loaf/fish caught
FEED_THRESHOLD      = 15          # combined inventory that triggers feeding
HUNGER_REFILL       = 25.0        # % restored when crowd is fed
HUNGER_DRAIN_PER_SEC = 100.0 / 23.0   # empties in ~23s if never fed

ROCK_STUN_DURATION  = 1.5
ROCK_PENALTY        = 5           # loaves & fish lost when hit by a rock

FEED_FLASH_DURATION = 1.2

# ─── Colours ────────────────────────────────────────────────────────────────
C_SKY        = (160, 200, 225)
C_HILL_FAR   = (140, 175, 110)
C_HILL_NEAR  = (100, 150, 80)
C_GROUND     = (150, 190, 110)
C_CROWD_DARK = ( 90,  70,  55)
C_LOAF       = (170, 120,  60)
C_LOAF_DARK  = (120,  80,  35)
C_FISH_BODY  = (170, 190, 200)
C_FISH_DARK  = ( 90, 120, 140)
C_ROCK       = (110, 110, 115)
C_ROCK_DARK  = ( 70,  70,  75)
C_BASKET     = (160, 120,  60)
C_BASKET_DARK= (110,  75,  35)
C_ROBE       = (140, 100, 170)
C_ROBE_DARK  = ( 95,  65, 125)
C_SKIN       = (210, 170, 130)
C_HUD_BG     = ( 25,  20,  15)
C_WHITE      = (255, 255, 255)
C_GOLD       = (235, 195, 100)
C_HUNGER_FULL= ( 90, 190,  90)
C_HUNGER_MID = (220, 180,  60)
C_HUNGER_LOW = (210,  70,  60)


def load_font(size, bold=False):
    for name in ["Georgia", "Times New Roman", "serif", None]:
        try: return pygame.font.SysFont(name, size, bold=bold)
        except Exception: pass
    return pygame.font.Font(None, size)


# ─── Falling Items ──────────────────────────────────────────────────────────
class FallingItem:
    def __init__(self, kind):
        self.kind = kind                       # "loaf" | "fish" | "rock"
        self.size = 26 if kind != "rock" else 24
        self.x = random.uniform(40, SCREEN_W - 40)
        self.y = -self.size
        self.speed = random.uniform(ITEM_FALL_SPEED_MIN, ITEM_FALL_SPEED_MAX)
        self.wobble_phase = random.uniform(0, math.tau)
        self.spin = random.uniform(-1.5, 1.5)
        self.angle = 0.0

    def update(self, dt, t):
        self.y += self.speed * dt
        self.x += math.sin(t * 2 + self.wobble_phase) * 12 * dt
        self.angle += self.spin * dt

    def off_screen(self):
        return self.y > SCREEN_H + 40

    def radius(self):
        return self.size * 0.55

    def draw(self, surface):
        x, y = int(self.x), int(self.y)
        if self.kind == "loaf":
            draw_loaf(surface, x, y, self.size)
        elif self.kind == "fish":
            draw_fish_item(surface, x, y, self.size, self.angle)
        elif self.kind == "rock":
            draw_rock(surface, x, y, self.size, self.angle)


def draw_loaf(surface, x, y, size):
    rect = pygame.Rect(0, 0, size * 1.5, size)
    rect.center = (x, y)
    pygame.draw.ellipse(surface, C_LOAF, rect)
    pygame.draw.ellipse(surface, C_LOAF_DARK, rect, width=2)
    for i in range(-1, 2):
        sx = x + i * size * 0.3
        pygame.draw.line(surface, C_LOAF_DARK, (sx, y - size * 0.3), (sx + 4, y), 2)


def draw_fish_item(surface, x, y, size, angle):
    body_w, body_h = size * 1.7, size * 0.8
    facing = 1 if math.cos(angle) >= 0 else -1
    tail_pts = [
        (x - facing * body_w * 0.5, y),
        (x - facing * (body_w * 0.5 + size * 0.5), y - size * 0.4),
        (x - facing * (body_w * 0.5 + size * 0.5), y + size * 0.4),
    ]
    pygame.draw.polygon(surface, C_FISH_DARK, tail_pts)
    body_rect = pygame.Rect(0, 0, body_w, body_h)
    body_rect.center = (x, y)
    pygame.draw.ellipse(surface, C_FISH_BODY, body_rect)
    eye_x = x + facing * body_w * 0.28
    pygame.draw.circle(surface, (20, 20, 20), (int(eye_x), int(y - 1)), 2)


def draw_rock(surface, x, y, size, angle):
    pts = []
    n = 7
    rng_amp = size * 0.5
    for i in range(n):
        a = angle + (math.tau / n) * i
        r = rng_amp * (0.8 + 0.2 * math.sin(i * 2.3))
        pts.append((x + math.cos(a) * r, y + math.sin(a) * r))
    pygame.draw.polygon(surface, C_ROCK, pts)
    pygame.draw.polygon(surface, C_ROCK_DARK, pts, width=2)


def draw_scenery(surface, t):
    surface.fill(C_SKY)
    pygame.draw.ellipse(surface, C_HILL_FAR, pygame.Rect(-100, GROUND_Y - 140, 600, 200))
    pygame.draw.ellipse(surface, C_HILL_NEAR, pygame.Rect(300, GROUND_Y - 110, 650, 180))
    pygame.draw.rect(surface, C_GROUND, pygame.Rect(0, GROUND_Y, SCREEN_W, SCREEN_H - GROUND_Y))

    # Procedural crowd silhouettes standing in background hills
    for x in range(30, SCREEN_W, 45):
        cy = GROUND_Y - 15 + int(4 * math.sin(x * 0.05))
        pygame.draw.circle(surface, C_CROWD_DARK, (x, cy - 24), 8)
        pygame.draw.polygon(surface, C_CROWD_DARK, [(x, cy - 16), (x - 12, cy + 10), (x + 12, cy + 10)])


def run(skin_data=None):
    pygame.display.set_caption("Bible Quest — Catch & Multiply")
    screen = pygame.display.set_mode((SCREEN_W, SCREEN_H))
    clock = pygame.time.Clock()

    font_big   = load_font(44, bold=True)
    font_med   = load_font(22, bold=True)
    font_hud   = load_font(18, bold=True)
    font_small = load_font(15)

    px = float(SCREEN_W 
[truncated — 7549 more characters]
```

### fish_coin.py

```python
"""
Deep Sea Arcade Hook — Matthew 17:27
======================================
"...go thou to the sea, and cast an hook, and take up the fish that first
cometh up; and when thou hast opened his mouth, thou shalt find a piece
of money..." — Matthew 17:27 (KJV)

Asynchronous engine updated for native desktop PyCharm and linked directly
to automated Redis Cloud high-score submission vectors.
"""

import asyncio
import pygame
import sys
import math
import random

# ─── Screen / Layout Constants ─────────────────────────────────────────────
SCREEN_W, SCREEN_H = 800, 600
HUD_HEIGHT = 70
VIEWPORT_TOP = HUD_HEIGHT
VIEWPORT_H = SCREEN_H - HUD_HEIGHT

# ─── World Constants ────────────────────────────────────────────────────────
BOAT_WORLD_Y      = 0
HOOK_START_Y      = 14
WORLD_DEPTH       = 1700
TARGET_WORLD_Y    = WORLD_DEPTH

CAMERA_MIN = BOAT_WORLD_Y - 90
CAMERA_MAX = TARGET_WORLD_Y - VIEWPORT_H + 60

DROP_SPEED       = 900.0
ASCENT_DURATION  = 15.0
HOOK_H_SPEED     = 260.0
HOOK_RADIUS      = 14

NUM_OBSTACLES = 16

# ─── Colours ────────────────────────────────────────────────────────────────
C_SKY        = (130, 180, 210)
C_WATER_TOP  = ( 40, 110, 150)
C_WATER_DEEP = (  6,  25,  45)
C_BOAT_HULL  = (110,  70,  35)
C_BOAT_DARK  = ( 70,  40,  15)
C_BOAT_DECK  = (160, 120,  70)
C_ROPE       = (200, 180, 140)
C_HOOK       = (210, 210, 215)
C_HOOK_DARK  = (120, 120, 130)
C_GOLD       = (235, 190,  60)
C_GOLD_DARK  = (160, 120,  30)
C_COIN       = (250, 215, 100)
C_FISH_BODY  = [(180, 90, 90), (90, 130, 180), (90, 170, 110), (190, 150, 80), (150, 90, 170), (90, 160, 170)]
C_FISH_FIN   = (255, 255, 255)
C_HUD_BG     = ( 10,  20,  30)
C_WHITE      = (255, 255, 255)
C_TEXT_GOLD  = (235, 195, 100)
C_BUBBLE     = (200, 230, 240)


def load_font(size, bold=False):
    for name in ["Georgia", "Times New Roman", "serif", None]:
        try: return pygame.font.SysFont(name, size, bold=bold)
        except Exception: pass
    return pygame.font.Font(None, size)


def compute_camera_y(focus_world_y):
    cam = focus_world_y - VIEWPORT_H / 2
    return max(CAMERA_MIN, min(CAMERA_MAX, cam))


def world_to_screen_y(world_y, camera_y):
    return VIEWPORT_TOP + (world_y - camera_y)


# ─── Obstacle Fish ──────────────────────────────────────────────────────────
class ObstacleFish:
    def __init__(self, world_y=None):
        self.reset(random_x=True, world_y=world_y)

    def reset(self, random_x=False, world_y=None):
        self.world_y = world_y if world_y is not None else random.uniform(60, WORLD_DEPTH - 60)
        self.speed = random.uniform(50, 160)
        self.direction = random.choice([-1, 1])
        self.size = random.randint(14, 24)
        self.color = random.choice(C_FISH_BODY)
        self.bob_phase = random.uniform(0, math.tau)
        if random_x:
            self.x = random.uniform(0, SCREEN_W)
        else:
            self.x = -40 if self.direction == 1 else SCREEN_W + 40

    def update(self, dt, t):
        self.x += self.speed * self.direction * dt
        self.draw_world_y = self.world_y + math.sin(t * 2 + self.bob_phase) * 5
        if self.x < -50 or self.x > SCREEN_W + 50:
            self.reset(random_x=False, world_y=self.world_y)

    def rect_radius(self):
        return self.size * 0.9

    def draw(self, surface, camera_y):
        sy = world_to_screen_y(self.draw_world_y, camera_y)
        if sy < VIEWPORT_TOP - 40 or sy > SCREEN_H + 40:
            return
        x, y = int(self.x), int(sy)
        facing_right = self.direction > 0
        body_w, body_h = self.size * 1.8, self.size

        tail_dx = -1 if facing_right else 1
        tail_pts = [
            (x + tail_dx * body_w * 0.55, y),
            (x + tail_dx * (body_w * 0.55 + self.size * 0.7), y - self.size * 0.55),
            (x + tail_dx * (body_w * 0.55 + self.size * 0.7), y + self.size * 0.55),
        ]
        pygame.draw.polygon(surface, self.color, tail_pts)

        body_rect = pygame.Rect(0, 0, body_w, body_h)
        body_rect.center = (x, y)
        pygame.draw.ellipse(surface, self.color, body_rect)

        pygame.draw.polygon(surface, C_FISH_FIN, [
            (x, y - body_h * 0.5),
            (x - 6, y - body_h * 0.95),
            (x + 6, y - body_h * 0.95),
        ])

        eye_dx = body_w * 0.28 if facing_right else -body_w * 0.28
        pygame.draw.circle(surface, C_WHITE, (int(x + eye_dx), int(y - 2)), 4)
        pygame.draw.circle(surface, (10, 10, 10), (int(x + eye_dx + (2 if facing_right else -2)), int(y - 2)), 2)

    def collides_with(self, hook_x, hook_world_y, radius):
        dist = math.hypot(self.x - hook_x, self.draw_world_y - hook_world_y)
        return dist < (self.rect_radius() + radius)


# ─── Procedural Drawing ─────────────────────────────────────────────────────
def draw_water(surface, camera_y, t):
    surface.fill(C_SKY)
    pygame.draw.rect(surface, C_WATER_TOP, pygame.Rect(0, VIEWPORT_TOP, SCREEN_W, VIEWPORT_H))

    steps = 50
    band_h = VIEWPORT_H / steps
    for i in range(steps):
        screen_y = VIEWPORT_TOP + i * band_h
        world_y = camera_y + (screen_y - VIEWPORT_TOP)
        frac = max(0.0, min(1.0, world_y / WORLD_DEPTH))
        r = int(C_WATER_TOP[0] + (C_WATER_DEEP[0] - C_WATER_TOP[0]) * frac)
        g = int(C_WATER_TOP[1] + (C_WATER_DEEP[1] - C_WATER_TOP[1]) * frac)
        b = int(C_WATER_TOP[2] + (C_WATER_DEEP[2] - C_WATER_TOP[2]) * frac)
        pygame.draw.rect(surface, (r, g, b), pygame.Rect(0, int(screen_y), SCREEN_W, int(band_h) + 1))

    surface_screen_y = world_to_screen_y(0, camera_y)
    if VIEWPORT_TOP - 10 <= surface_screen_y <= SCREEN_H + 10:
        for x in range(0, SCREEN_W, 6):
            wy = surface_screen_y + math.sin(x * 0.05 + t * 2) * 3
            pygame.draw.line(surface, (200, 230, 240), (x, wy), (x + 6, wy), 2)


def draw_boat(surface, camera_y, t):
    b
[truncated — 11101 more characters]
```

[1 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]