SURVIVE CHIWAWA IN ALLEYWAY AT 3AM

A chaotic survival game where you fight off waves of rabid chihuahuas in a dark alleyway. Defeat dogs, earn coins, and buy upgrades like walls, friends, and even a golden god to survive increasingly difficult waves.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make the player faster — Double the player's movement speed so they can dodge more easily
  2. Enemies spawn weaker — Reduce the enemy health scaling so bosses die faster and waves feel easier
  3. Gods do more damage — Increase god projectile damage so your allies become dramatically more powerful
  4. Start with extra coins — Begin each game with coins to buy upgrades immediately without grinding
  5. Make walls super durable — Increase wall health so they block enemies much longer
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a fast-paced survival tower-defense game where you defend yourself against waves of attacking chihuahuas in a 3AM alleyway. The visual atmosphere comes from a radial flashlight gradient that keeps most of the screen dark, making enemies emerge from the shadows. You'll use the draw loop, collision detection, game state management, and vector math to create a full game experience with three difficulty modes and a progression system.

The code is organized around a central game state machine (START, MODE_SELECT, PLAYING, SHOP, LOSE) that determines what runs each frame. Within the PLAYING state, you'll find enemy AI, player input handling, projectile systems, particle effects, and a shop system. By studying this sketch you'll learn how to build a complete game loop with upgradeable mechanics, multiple entity types, and dynamic difficulty scaling.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-screen canvas and gameState is set to 'START', displaying the title screen with a toggle for mobile controls and a secret cheat code detector.
  2. Clicking or tapping the start screen advances to MODE_SELECT, where you choose between CLASSIC (one scaling boss), SWARM (many weak enemies), or HARDCORE (1 HP, instant machine gun). Each mode calls initGame() with different parameters.
  3. initGame() resets all entities and calls startNextWave(), which spawns the first wave of dogs with difficulty scaled by the wave number. The game enters the PLAYING state.
  4. On every frame during PLAYING, updateLogic() handles: player movement (keyboard WASD or touch joystick), punch/gun firing mechanics, friend and god AI that hunt and shoot dogs, projectile physics and collision detection, dog AI that chases the player and damages walls, particle effects from hits, and wave completion detection.
  5. When a wave clears (all dogs defeated), the gameState changes to SHOP. You spend coins earned from that wave on walls (blocking obstacles), a machine gun (rapid-fire upgrade), friends (allied units that shoot), or a golden god (powerful ally with exponential damage scaling). Clicking 'START NEXT WAVE' increments the wave counter and increases enemy difficulty.
  6. If the player health reaches 0, gameState becomes LOSE, showing survival stats. A secret cheat code ('41' typed quickly on the menu) activates god mode with 100 coins, machine gun, 100 walls, 50 friends, and 1 golden god.

🎓 Concepts You'll Learn

Game state machineCollision detectionAI pathfindingParticle effectsUpgrade progressionWave-based difficulty scalingGradient renderingMobile touch input

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. It initializes the canvas and game state that controls what the draw() function renders.

function setup() {
  createCanvas(windowWidth, windowHeight);
  gameState = "START";
}
Line-by-line explanation (2 lines)
createCanvas(windowWidth, windowHeight);
Creates a full-screen canvas that fills the entire browser window, making the game responsive to any screen size
gameState = "START";
Sets the initial game state to START, so the draw loop will render the title screen on first run

initGame(mode)

initGame() is called when you select a mode. It wipes all enemies and resets game variables based on the chosen difficulty, then spawns the first wave.

function initGame(mode) {
  gameMode = mode;
  wave = 1;
  coins = 0;
  hasMachineGun = (mode === "HARDCORE"); 
  walls = [];
  friends = [];
  gods = [];
  
  player = {
    pos: createVector(width / 2, height / 2 + 100),
    health: (mode === "HARDCORE") ? 1 : 100,
    maxHealth: (mode === "HARDCORE") ? 1 : 100,
    speed: 4.0,
    radius: 20,
    punchRadius: 65,
    punchFrames: 0,
    punchAngle: 0
  };
  
  if (cheatActivated) {
    coins = 10000;
    hasMachineGun = true;
    
    for (let i = 0; i < 100; i++) {
      walls.push({ 
        pos: createVector(player.pos.x + random(-350, 350), player.pos.y + random(-350, 350)), 
        radius: 25, hp: 150 
      });
    }
    
    for (let i = 0; i < 50; i++) {
      friends.push({ 
        pos: createVector(player.pos.x + random(-150, 150), player.pos.y + random(-150, 150)), 
        speed: 3.0, radius: 12 
      });
    }
    
    gods.push({ pos: createVector(player.pos.x, player.pos.y), speed: 6.0, radius: 30 });
    
    cheatActivated = false;
    secretCode = "";
  }
  
  startNextWave();
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

conditional Mode-based player setup health: (mode === "HARDCORE") ? 1 : 100,

Sets player health to 1 for HARDCORE mode (instant death) or 100 for other modes

conditional Secret cheat activation if (cheatActivated) {

If the cheat code was typed, spawns massive upgrades instantly for debug/fun play

gameMode = mode;
Stores the selected mode (CLASSIC, SWARM, or HARDCORE) so the game knows which rules to apply
wave = 1;
Resets wave counter to 1 when starting a new game
coins = 0;
Resets coins to 0, clearing any wealth from previous games
hasMachineGun = (mode === "HARDCORE");
In HARDCORE mode you start with a machine gun; in other modes you punch until you buy one
health: (mode === "HARDCORE") ? 1 : 100,
HARDCORE mode gives you 1 HP (one hit and you die); other modes start with 100 HP
startNextWave();
Calls the wave spawner function and transitions to the PLAYING state

startNextWave()

startNextWave() clears all projectiles and particles, then spawns enemies scaled by the current wave number. Each mode has different scaling to create three unique difficulty curves.

🔬 CLASSIC spawns one boss at the top of the screen. What happens if you change the spawn position from 'height / 2 - 300' to 'random(0, height)' so bosses spawn at random heights?

  if (gameMode === "CLASSIC") {
    dogs.push({
      pos: createVector(width / 2, height / 2 - 300),
      speed: min(7.5, 3.8 + (wave * 0.2)),
      radius: 15,
      hitsTaken: 0,
      maxHits: floor(10 * pow(1.25, wave - 1)),
      stunFrames: 0, flashRed: 0
    });
function startNextWave() {
  dogs = [];
  
  if (gameMode === "CLASSIC") {
    dogs.push({
      pos: createVector(width / 2, height / 2 - 300),
      speed: min(7.5, 3.8 + (wave * 0.2)),
      radius: 15,
      hitsTaken: 0,
      maxHits: floor(10 * pow(1.25, wave - 1)),
      stunFrames: 0, flashRed: 0
    });
  } 
  else if (gameMode === "SWARM") {
    let numDogs = 2 + (wave * 3); 
    for (let i = 0; i < numDogs; i++) {
      dogs.push({
        pos: createVector(width / 2 + random(-200, 200), height / 2 - 400 + random(-150, 150)),
        speed: random(2.5, 4.5) + (wave * 0.1),
        radius: 12,
        hitsTaken: 0,
        maxHits: ceil(wave * 0.5), 
        stunFrames: 0, flashRed: 0
      });
    }
  } 
  else if (gameMode === "HARDCORE") {
    dogs.push({
      pos: createVector(width / 2, height / 2 - 300),
      speed: min(9.0, 4.5 + (wave * 0.3)), 
      radius: 15,
      hitsTaken: 0,
      maxHits: floor(15 * pow(1.3, wave - 1)),
      stunFrames: 0, flashRed: 0
    });
  }

  projectiles = [];
  particles = [];
  shakeAmount = 0;
  gameState = "PLAYING";
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

conditional Classic mode spawning if (gameMode === "CLASSIC") {

CLASSIC spawns 1 boss dog that gets progressively faster and tankier each wave

conditional Swarm mode spawning else if (gameMode === "SWARM") {

SWARM spawns 2 + 3*wave dogs with randomized positions, creating a crowd

conditional Hardcore mode spawning else if (gameMode === "HARDCORE") {

HARDCORE is like CLASSIC but with higher starting speed and health scaling (1.3x per wave vs 1.25x)

dogs = [];
Clears the dogs array so a new wave spawns with no leftovers from the previous one
speed: min(7.5, 3.8 + (wave * 0.2)),
Speed increases by 0.2 per wave but caps at 7.5—makes bosses get faster but not infinitely so
maxHits: floor(10 * pow(1.25, wave - 1)),
Health scales exponentially: wave 1 needs 10 hits, wave 2 needs ~12.5, wave 3 needs ~15.6, etc.
let numDogs = 2 + (wave * 3);
In SWARM mode, the number of dogs grows with each wave: wave 1 has 5 dogs, wave 2 has 8, wave 3 has 11, etc.
gameState = "PLAYING";
Transitions from SHOP or START to PLAYING, so draw() will run game logic and render the level

keyPressed()

keyPressed() is called once per keypress by p5.js. This function detects a hidden cheat code by checking if the last two keys typed were '4' and '1' on the menu screen.

function keyPressed() {
  secretCode += key;
  if (secretCode.length > 2) secretCode = secretCode.substring(secretCode.length - 2);
  
  if (secretCode === "41" && (gameState === "START" || gameState === "MODE_SELECT")) {
    cheatActivated = true;
    shakeAmount = 25;
    secretCode = ""; 
  }
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

calculation Secret code tracking secretCode += key;

Builds up a string of the last 2 keypresses to detect the cheat code '41'

conditional Cheat code validation if (secretCode === "41" && (gameState === "START" || gameState === "MODE_SELECT")) {

When you type '4' then '1' on the menu, activates god mode and shakes the screen

secretCode += key;
Every keypress appends to secretCode, building up a string of recent keys
if (secretCode.length > 2) secretCode = secretCode.substring(secretCode.length - 2);
Keeps only the last 2 characters of secretCode, so we only check the most recent two keys—this limits memory use
if (secretCode === "41" && (gameState === "START" || gameState === "MODE_SELECT")) {
Only activates the cheat if you typed '41' AND you're on the START or MODE_SELECT screens (not mid-game)
shakeAmount = 25;
Sets camera shake to 25 pixels—visible feedback that the cheat worked

draw()

draw() runs 60 times per second. It clears the canvas, applies effects like screen shake, then uses a state machine to decide what to render (game screen, menu, shop, etc.). This is the main loop of the entire sketch.

🔬 This shake effect multiplies by 0.9 each frame. What happens if you change 0.9 to 0.99 so the shake lasts longer? Or to 0.8 so it stops faster?

  if (shakeAmount > 0) {
    translate(random(-shakeAmount, shakeAmount), random(-shakeAmount, shakeAmount));
    shakeAmount *= 0.9;
    if (shakeAmount < 0.5) shakeAmount = 0;
  }
function draw() {
  background(10); 
  
  push();
  if (shakeAmount > 0) {
    translate(random(-shakeAmount, shakeAmount), random(-shakeAmount, shakeAmount));
    shakeAmount *= 0.9;
    if (shakeAmount < 0.5) shakeAmount = 0;
  }

  if (gameState === "PLAYING") {
    updateLogic();
    drawEnvironment();
    drawWalls();
    drawFriends();
    drawGods();
    drawDogs();
    drawPlayer();
    drawProjectiles();
    drawParticles();
    drawFlashlight();
    drawHUD();
    
    if (mobileMode) drawMobileControls();
    
  } else if (gameState === "START") {
    drawStartScreen();
  } else if (gameState === "MODE_SELECT") {
    drawModeSelectScreen();
  } else if (gameState === "SHOP") {
    drawEnvironment(); 
    drawShopScreen();
  } else if (gameState === "LOSE") {
    drawLoseScreen();
  }
  pop(); 
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

conditional Screen shake effect if (shakeAmount > 0) {

Applies random camera jitter that fades out each frame, creating a shake effect on impacts

switch-case Game state routing if (gameState === "PLAYING") {

Routes to different rendering functions based on current game state

background(10);
Fills the canvas with dark gray (RGB value 10), clearing previous frames and setting the nighttime mood
if (shakeAmount > 0) {
When shakeAmount is above 0, applies camera shake by translating the canvas to a random offset
translate(random(-shakeAmount, shakeAmount), random(-shakeAmount, shakeAmount));
Shifts the entire canvas by a random amount in X and Y, making everything on screen vibrate
shakeAmount *= 0.9;
Multiplies shake by 0.9 each frame, causing it to decay exponentially and fade smoothly to zero
if (gameState === "PLAYING") {
When the game is actively running, execute game logic and draw all game entities
updateLogic();
Calls the main game update function that handles movement, collisions, and AI

updateLogic()

updateLogic() is called every frame while PLAYING and handles all game mechanics: player input, NPC AI, projectile physics, collision detection, and wave progression. It's the game's core engine.

🔬 Friends chase when > 180 pixels away and shoot when closer. What happens if you change 180 to 300 so they start attacking from farther away?

  for (let f of friends) {
    let closest = null; let record = Infinity;
    for (let d of dogs) {
      let dToDog = dist(f.pos.x, f.pos.y, d.pos.x, d.pos.y);
      if (dToDog < record) { record = dToDog; closest = d; }
    }
    if (closest) {
      if (record > 180) {
        let dir = p5.Vector.sub(closest.pos, f.pos).normalize().mult(f.speed);
        f.pos.add(dir);
      } else if (frameCount % 40 === 0) {
        let dir = p5.Vector.sub(closest.pos, f.pos).normalize().mult(12);
        projectiles.push({ pos: f.pos.copy(), vel: dir, life: 60, isGod: false, damage: 1 });
      }
    }
  }

🔬 When a dog gets hit, it spawns particles—8 for god shots, 3 for normal shots. What happens if you change 3 to 12 so every punch creates more visual impact?

        for(let k=0; k < (p.isGod ? 8 : 3); k++) {
          particles.push({pos: d.pos.copy(), vel: p5.Vector.random2D().mult(random(2,8)), life: 255});
        }
function updateLogic() {
  let isMoving = false;
  
  if (keyIsDown(65) || keyIsDown(LEFT_ARROW)) { player.pos.x -= player.speed; isMoving = true; }
  if (keyIsDown(68) || keyIsDown(RIGHT_ARROW)) { player.pos.x += player.speed; isMoving = true; }
  if (keyIsDown(87) || keyIsDown(UP_ARROW)) { player.pos.y -= player.speed; isMoving = true; }
  if (keyIsDown(83) || keyIsDown(DOWN_ARROW)) { player.pos.y += player.speed; isMoving = true; }

  let joyBaseX = 120; let joyBaseY = height - 120;
  let shootBtnX = width - 120; let shootBtnY = height - 120;
  joyKnob = { x: joyBaseX, y: joyBaseY };
  let shootActive = false;

  if (mobileMode) {
    let inputs = [];
    if (touches.length > 0) {
      for (let i = 0; i < touches.length; i++) inputs.push(touches[i]);
    } else if (mouseIsPressed) {
      inputs.push({ x: mouseX, y: mouseY });
    }

    for (let t of inputs) {
      if (t.x < width / 2 && t.y > height / 2) {
        let d = dist(t.x, t.y, joyBaseX, joyBaseY);
        if (d < 150) { 
          let angle = atan2(t.y - joyBaseY, t.x - joyBaseX);
          let mag = min(d, 50);
          joyKnob.x = joyBaseX + cos(angle) * mag;
          joyKnob.y = joyBaseY + sin(angle) * mag;
          let moveVec = createVector(cos(angle), sin(angle)).mult(player.speed * (mag/50));
          player.pos.add(moveVec);
          isMoving = true;
        }
      }
      if (t.x > width / 2 && t.y > height / 2) {
        if (dist(t.x, t.y, shootBtnX, shootBtnY) < 100) shootActive = true;
      }
    }

    if (shootActive) {
      let closestDog = null; let record = Infinity;
      for (let d of dogs) {
        let dDist = dist(player.pos.x, player.pos.y, d.pos.x, d.pos.y);
        if (dDist < record) { record = dDist; closestDog = d; }
      }
      let targetAngle = player.punchAngle;
      if (closestDog) targetAngle = atan2(closestDog.pos.y - player.pos.y, closestDog.pos.x - player.pos.x);

      if (hasMachineGun && player.punchFrames === 0) {
        if (frameCount % 6 === 0) {
          let dir = p5.Vector.fromAngle(targetAngle).mult(15);
          projectiles.push({ pos: player.pos.copy(), vel: dir, life: 60, isGod: false, damage: 1 });
          player.punchAngle = targetAngle;
          shakeAmount = 1.5; 
        }
      } else if (!hasMachineGun && player.punchFrames === 0) {
        player.punchFrames = 15;
        player.punchAngle = targetAngle;
        executePunch(targetAngle);
      }
    }
  } 
  else {
    if (touches.length > 0 && !isMoving) {
      let t = touches[0];
      let dir = createVector(t.x - player.pos.x, t.y - player.pos.y);
      if (dir.mag() > 20) {
        dir.normalize().mult(player.speed);
        player.pos.add(dir);
      }
    }

    if (mouseIsPressed && hasMachineGun && player.punchFrames === 0) {
      if (frameCount % 6 === 0) {
        let tx = touches.length > 0 ? touches[0].x : mouseX;
        let ty = touches.length > 0 ? touches[0].y : mouseY;
        let dir = createVector(tx - player.pos.x, ty - player.pos.y).normalize().mult(15);
        projectiles.push({ pos: player.pos.copy(), vel: dir, life: 60, isGod: false, damage: 1 });
        player.punchAngle = dir.heading();
        shakeAmount = 1.5; 
      }
    }
  }

  let leftWall = width / 2 - alleyWidth / 2;
  let rightWall = width / 2 + alleyWidth / 2;
  player.pos.x = constrain(player.pos.x, leftWall + player.radius, rightWall - player.radius);
  player.pos.y = constrain(player.pos.y, player.radius, height - player.radius);

  if (player.punchFrames > 0) player.punchFrames--;

  for (let f of friends) {
    let closest = null; let record = Infinity;
    for (let d of dogs) {
      let dToDog = dist(f.pos.x, f.pos.y, d.pos.x, d.pos.y);
      if (dToDog < record) { record = dToDog; closest = d; }
    }
    if (closest) {
      if (record > 180) {
        let dir = p5.Vector.sub(closest.pos, f.pos).normalize().mult(f.speed);
        f.pos.add(dir);
      } else if (frameCount % 40 === 0) {
        let dir = p5.Vector.sub(closest.pos, f.pos).normalize().mult(12);
        projectiles.push({ pos: f.pos.copy(), vel: dir, life: 60, isGod: false, damage: 1 });
      }
    }
  }

  let godFireRate = max(2, 8 - floor(wave / 3));
  let godDamage = floor(25 * pow(1.2, wave - 1));

  for (let g of gods) {
    let closest = null; let record = Infinity;
    for (let d of dogs) {
      let dToDog = dist(g.pos.x, g.pos.y, d.pos.x, d.pos.y);
      if (dToDog < record) { record = dToDog; closest = d; }
    }
    if (closest) {
      if (record > 200) {
        let dir = p5.Vector.sub(closest.pos, g.pos).normalize().mult(g.speed);
        g.pos.add(dir);
      } else if (frameCount % godFireRate === 0) {
        let dir = p5.Vector.sub(closest.pos, g.pos).normalize().mult(25);
        projectiles.push({ pos: g.pos.copy(), vel: dir, life: 100, isGod: true, damage: godDamage });
      }
    }
  }

  for (let i = projectiles.length - 1; i >= 0; i--) {
    let p = projectiles[i];
    p.pos.add(p.vel);
    p.life--;
    
    let hit = false;
    for (let j = dogs.length - 1; j >= 0; j--) {
      let d = dogs[j];
      let hitRadius = p.isGod ? 25 : 15;
      
      if (dist(p.pos.x, p.pos.y, d.pos.x, d.pos.y) < d.radius + hitRadius) {
        let dmg = p.damage || 1;
        d.hitsTaken += dmg;
        d.flashRed = 8;
        d.pos.add(p.vel.copy().setMag(p.isGod ? 8 : 2)); 
        
        for(let k=0; k < (p.isGod ? 8 : 3); k++) {
          particles.push({pos: d.pos.copy(), vel: p5.Vector.random2D().mult(random(2,8)), life: 255});
        }
        
        if (d.hitsTaken >= d.maxHits) dogs.splice(j, 1);
        hit = true; break;
      }
    }
    if (hit || p.life <= 0) projectiles.splice(i, 1);
  }

  for (let j = dogs.length - 1; j >= 0; j--) {
    let d = dogs[j];
    if (d.stunFrames > 0) {
      d.stunFrames--;
    } else {
      let dirToPlayer = p5.Vector.sub(player.pos, d.pos);
      let distToPlayer = dirToPlayer.mag();
      
      let hitWall = false;
      for (let i = walls.length - 1; i >= 0; i--) {
        let w = walls[i];
        if (dist(d.pos.x, d.pos.y, w.pos.x, w.pos.y) < d.radius + w.radius) {
          hitWall = true;
          w.hp -= 1.5; 
          d.pos.sub(p5.Vector.sub(d.pos, w.pos).setMag(3)); 
          if (w.hp <= 0) walls.splice(i, 1);
        }
      }

      if (!hitWall && distToPlayer > d.radius + player.radius) {
        dirToPlayer.normalize().mult(d.speed);
        d.pos.add(dirToPlayer);
      }

      if (distToPlayer < d.radius + player.radius + 5) {
        player.health -= (gameMode === "HARDCORE") ? 1 : 0.8;
        shakeAmount = 2;
        if (player.health <= 0) {
          gameState = "LOSE";
          deathCooldown = 60;
        }
      }
    }

    d.pos.x = constrain(d.pos.x, leftWall + d.radius, rightWall - d.radius);
    d.pos.y = constrain(d.pos.y, d.radius, height - d.radius);
    if (d.flashRed > 0) d.flashRed--;
  }

  if (dogs.length === 0 && gameState === "PLAYING") {
    let earned = (gameMode === "SWARM") ? wave * 15 + 20 : wave * 10 + 10;
    coins += earned;
    player.health = player.maxHealth; 
    gameState = "SHOP";
  }

  for (let i = particles.length - 1; i >= 0; i--) {
    particles[i].pos.add(particles[i].vel);
    particles[i].life -= 5;
    if (particles[i].life <= 0) particles.splice(i, 1);
  }
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

conditional Keyboard input handling if (keyIsDown(65) || keyIsDown(LEFT_ARROW)) { player.pos.x -= player.speed; isMoving = true; }

Detects WASD or arrow keys and updates player position continuously

for-loop Friend AI targeting and shooting for (let f of friends) {

Each frame, hired friends pathfind toward the nearest dog and fire projectiles

for-loop God AI with wave scaling for (let g of gods) {

Gods hunt dogs and fire projectiles with exponentially increasing damage and fire rate per wave

for-loop Projectile updates and collision for (let i = projectiles.length - 1; i >= 0; i--) {

Moves projectiles, checks if they hit dogs, applies damage, spawns particles, and removes dead projectiles

for-loop Enemy pathfinding and damage for (let j = dogs.length - 1; j >= 0; j--) {

Dogs pursue the player, collide with walls, damage the player on contact, and get knocked back by hits

conditional Wave completion detection if (dogs.length === 0 && gameState === "PLAYING") {

When all dogs are defeated, awards coins and transitions to the shop

if (keyIsDown(65) || keyIsDown(LEFT_ARROW)) { player.pos.x -= player.speed; isMoving = true; }
Checks if the player is holding A or LEFT_ARROW and moves them left by their speed value
let closestDog = null; let record = Infinity;
Sets up variables to track which dog is nearest during the friend's target search
if (dToDog < record) { record = dToDog; closest = d; }
Compares distances to find the closest dog, updating 'closest' and 'record' when a nearer enemy is found
let godFireRate = max(2, 8 - floor(wave / 3));
Gods fire faster as waves increase: every 8 frames at wave 1, every 6 at wave 3, every 2 at wave 8 (capped)
let godDamage = floor(25 * pow(1.2, wave - 1));
God damage scales exponentially: 25 at wave 1, 30 at wave 2, 36 at wave 3, etc. (20% boost per wave)
p.pos.add(p.vel);
Moves projectile by adding its velocity vector to its position each frame
let hitRadius = p.isGod ? 25 : 15;
God projectiles have a larger hit radius (25) than normal ones (15), making them easier to land
d.hitsTaken += dmg;
Adds damage to the dog's hit counter—when hitsTaken reaches maxHits, the dog dies
if (dogs.length === 0 && gameState === "PLAYING") {
Detects when the player has cleared a wave and transitions to the shop to buy upgrades

executePunch(angle)

executePunch() is called when you punch (without machine gun). It checks all dogs and damages those within range AND within the direction you punched, creating a cone-based melee attack system.

🔬 This punch stuns enemies for 15 frames and knocks them back by 50 pixels. What happens if you change 50 to 100 so punches have more knockback? Or 15 to 30 for longer stun?

    if (distToDog < player.punchRadius && angleDiff < PI / 2) {
      shakeAmount = 5;
      let knockback = p5.Vector.fromAngle(angleToDog).mult(50);
      d.pos.add(knockback);
      d.stunFrames = 15;
      d.hitsTaken++;
      d.flashRed = 8;
function executePunch(angle) {
  for (let j = dogs.length - 1; j >= 0; j--) {
    let d = dogs[j];
    let distToDog = dist(player.pos.x, player.pos.y, d.pos.x, d.pos.y);
    let angleToDog = atan2(d.pos.y - player.pos.y, d.pos.x - player.pos.x);
    let angleDiff = abs(angle - angleToDog);
    if (angleDiff > PI) angleDiff = TWO_PI - angleDiff;

    if (distToDog < player.punchRadius && angleDiff < PI / 2) {
      shakeAmount = 5;
      let knockback = p5.Vector.fromAngle(angleToDog).mult(50);
      d.pos.add(knockback);
      d.stunFrames = 15;
      d.hitsTaken++;
      d.flashRed = 8;
      for (let i = 0; i < 5; i++) particles.push({pos: d.pos.copy(), vel: p5.Vector.random2D().mult(random(2, 6)), life: 255});
      if (d.hitsTaken >= d.maxHits) dogs.splice(j, 1);
    }
  }
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

conditional Punch collision detection if (distToDog < player.punchRadius && angleDiff < PI / 2) {

Only damages dogs within punch range and within the forward-facing cone (90 degree angle)

calculation Knockback vector creation let knockback = p5.Vector.fromAngle(angleToDog).mult(50);

Creates a force vector that pushes the dog away from the player

let distToDog = dist(player.pos.x, player.pos.y, d.pos.x, d.pos.y);
Calculates the distance from player to this dog
let angleToDog = atan2(d.pos.y - player.pos.y, d.pos.x - player.pos.x);
Calculates the angle from player to dog using atan2, giving a direction in radians
let angleDiff = abs(angle - angleToDog);
Finds the angular difference between where the player punched and where the dog is
if (angleDiff > PI) angleDiff = TWO_PI - angleDiff;
Wraps angle difference so it's always 0 to PI—handles the wraparound at 360 degrees
if (distToDog < player.punchRadius && angleDiff < PI / 2) {
Only damages dogs within punch range (punchRadius=65) AND within a 90-degree cone (PI/2) in front
let knockback = p5.Vector.fromAngle(angleToDog).mult(50);
Creates a vector pointing away from the player with magnitude 50, pushing the dog
d.stunFrames = 15;
Stuns the dog for 15 frames, preventing it from moving or attacking during that time

handleInput(tx, ty)

handleInput() is called by mousePressed() and touchStarted() and routes clicks to the right function based on game state. It's the central input dispatcher.

function handleInput(tx, ty) {
  if (gameState === "START") {
    let btnY = height / 2 + 160;
    if (tx > width/2 - 130 && tx < width/2 + 130 && ty > btnY - 25 && ty < btnY + 25) {
      mobileMode = !mobileMode;
      return; 
    }
    gameState = "MODE_SELECT";
    return;
  }
  
  if (gameState === "MODE_SELECT") {
    handleModeSelectClick(tx, ty);
    return;
  }
  
  if (gameState === "LOSE") {
    if (deathCooldown <= 0) gameState = "MODE_SELECT";
    return;
  }
  
  if (gameState === "SHOP") {
    handleShopClick(tx, ty);
    return;
  }

  if (gameState === "PLAYING" && player.punchFrames === 0 && !mobileMode) {
    player.punchFrames = 15;
    player.punchAngle = atan2(ty - player.pos.y, tx - player.pos.x);
    executePunch(player.punchAngle);
  }
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

conditional Start screen input handling if (gameState === "START") {

Handles mobile toggle button and transitions to mode selection

switch-case Input routing by game state if (gameState === "MODE_SELECT") {

Routes clicks to the appropriate handler based on which screen is active

conditional Desktop punch execution if (gameState === "PLAYING" && player.punchFrames === 0 && !mobileMode) {

On desktop, clicking in-game punches toward the cursor (mobile uses joystick button)

if (gameState === "START") {
When on the START screen, handle the mobile toggle button and menu navigation
if (tx > width/2 - 130 && tx < width/2 + 130 && ty > btnY - 25 && ty < btnY + 25) {
Checks if the click is within the mobile toggle button's rectangular area
mobileMode = !mobileMode;
Toggles mobile mode on/off—true uses joystick/button controls, false uses keyboard/mouse
player.punchAngle = atan2(ty - player.pos.y, tx - player.pos.x);
Calculates the angle from player to click position so the punch goes in the right direction

drawPlayer()

drawPlayer() uses push/pop and translate to rotate the punch/gun around the player's center. The punch extends outward during the punchFrames animation, visually connecting to the damage area.

function drawPlayer() {
  push(); translate(player.pos.x, player.pos.y);
  fill(100, 150, 255); stroke(0); strokeWeight(2); circle(0, 0, player.radius * 2);
  if (player.punchFrames > 0 || (mouseIsPressed && hasMachineGun && !mobileMode)) {
    rotate(player.punchAngle); fill(255, 200, 150);
    let ext = hasMachineGun ? 25 : map(player.punchFrames, 15, 0, player.punchRadius - 20, 0);
    circle(ext, 0, 15);
  }
  pop();
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

calculation Player body rendering circle(0, 0, player.radius * 2);

Draws the main player circle in blue

conditional Punch/gun visualization if (player.punchFrames > 0 || (mouseIsPressed && hasMachineGun && !mobileMode)) {

Extends a fist or gun barrel from the player when punching or firing

push(); translate(player.pos.x, player.pos.y);
Saves the current transform state and moves the origin to the player's position
fill(100, 150, 255); stroke(0); strokeWeight(2); circle(0, 0, player.radius * 2);
Draws the player as a blue circle with a black outline at the origin (now the player's position)
rotate(player.punchAngle);
Rotates all subsequent drawing by the punch angle, so the fist/gun points in the right direction
let ext = hasMachineGun ? 25 : map(player.punchFrames, 15, 0, player.punchRadius - 20, 0);
If firing a gun, extend 25 pixels. If punching, extend from punchRadius-20 to 0 as the punch animates
circle(ext, 0, 15);
Draws the fist/gun barrel at the extended position

drawDogs()

drawDogs() renders each enemy with rotation-based orientation (ears point toward player), hit feedback (flash red), and stun feedback (freeze jitter). The rotation technique makes enemies feel alive.

🔬 The jitter range is -2 to 2 pixels, making dogs wiggle slightly. What happens if you change 2 to 5 so they shake more violently? Or to 0 for perfectly smooth movement?

    let jitterX = d.stunFrames > 0 ? 0 : random(-2, 2);
    let jitterY = d.stunFrames > 0 ? 0 : random(-2, 2);
    translate(d.pos.x + jitterX, d.pos.y + jitterY);
function drawDogs() {
  for (let d of dogs) {
    push();
    let jitterX = d.stunFrames > 0 ? 0 : random(-2, 2);
    let jitterY = d.stunFrames > 0 ? 0 : random(-2, 2);
    translate(d.pos.x + jitterX, d.pos.y + jitterY);
    
    fill(d.flashRed > 0 ? color(255, 0, 0) : color(139, 69, 19));
    stroke(0); strokeWeight(2); circle(0, 0, d.radius * 2);
    
    let dir = p5.Vector.sub(player.pos, d.pos); rotate(dir.heading());
    triangle(0, -d.radius, 15, -d.radius - 15, 10, -d.radius); 
    triangle(0, d.radius, 15, d.radius + 15, 10, d.radius); 
    fill(255, 0, 0); noStroke(); circle(5, -5, 4); circle(5, 5, 4);
    pop();
  }
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

conditional Stun visual feedback let jitterX = d.stunFrames > 0 ? 0 : random(-2, 2);

When stunned, dog stays still; when moving, adds small random jitter for liveliness

conditional Damage indicator fill(d.flashRed > 0 ? color(255, 0, 0) : color(139, 69, 19));

Dog turns red briefly when hit, then back to brown

let jitterX = d.stunFrames > 0 ? 0 : random(-2, 2);
Adds tiny random X offset each frame (only when not stunned) to make dogs wiggle slightly while alive
fill(d.flashRed > 0 ? color(255, 0, 0) : color(139, 69, 19));
Dogs flash red when freshly hit (flashRed > 0), otherwise stay brown (RGB 139,69,19)
let dir = p5.Vector.sub(player.pos, d.pos); rotate(dir.heading());
Calculates direction to player and rotates subsequent shapes to face that direction
triangle(0, -d.radius, 15, -d.radius - 15, 10, -d.radius);
Draws an ear-like triangle pointing toward the player

drawFlashlight()

drawFlashlight() uses the canvas 2D context directly (not p5.js) to draw a radial gradient, creating the dark vignette effect that makes the player feel surrounded by darkness. This technique is essential to the game's atmosphere.

🔬 This creates the flashlight vignette. What happens if you change the final color stop from 0.98 to 0.5 (50% opacity) so the edges are less dark? Or to 1.0 for pure black?

  let gradient = ctx.createRadialGradient(player.pos.x, player.pos.y, player.radius * 2, player.pos.x, player.pos.y, width * 0.4);
  gradient.addColorStop(0, "rgba(0, 0, 0, 0)"); gradient.addColorStop(0.3, "rgba(0, 0, 0, 0.4)"); gradient.addColorStop(1, "rgba(0, 0, 0, 0.98)");
function drawFlashlight() {
  let ctx = drawingContext;
  let gradient = ctx.createRadialGradient(player.pos.x, player.pos.y, player.radius * 2, player.pos.x, player.pos.y, width * 0.4);
  gradient.addColorStop(0, "rgba(0, 0, 0, 0)"); gradient.addColorStop(0.3, "rgba(0, 0, 0, 0.4)"); gradient.addColorStop(1, "rgba(0, 0, 0, 0.98)");
  ctx.fillStyle = gradient; ctx.fillRect(0, 0, width, height);
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

calculation Radial gradient setup let gradient = ctx.createRadialGradient(player.pos.x, player.pos.y, player.radius * 2, player.pos.x, player.pos.y, width * 0.4);

Creates a radial gradient centered on the player that fades from transparent near the player to nearly opaque at the edges

let ctx = drawingContext;
Gets the raw canvas 2D context to use advanced canvas API functions not exposed in p5.js by default
let gradient = ctx.createRadialGradient(player.pos.x, player.pos.y, player.radius * 2, player.pos.x, player.pos.y, width * 0.4);
Creates a gradient from a circle at the player (radius 2*player.radius) expanding to a large circle (radius 40% of screen width)
gradient.addColorStop(0, "rgba(0, 0, 0, 0)");
At the gradient's inner edge (close to player), color is fully transparent black
gradient.addColorStop(0.3, "rgba(0, 0, 0, 0.4)");
At 30% of the way out, color is semi-transparent black (40% opacity)
gradient.addColorStop(1, "rgba(0, 0, 0, 0.98)");
At the far edge, color is nearly opaque black (98% opacity), creating the dark vignette
ctx.fillRect(0, 0, width, height);
Fills the entire canvas with the gradient, darkening the edges and leaving the player area bright

drawHUD()

drawHUD() displays game information: health bar, coins, wave number, and enemy info. It changes dynamically based on game mode and equipment (showing different help text for mobile vs. desktop, gun vs. punch).

function drawHUD() {
  fill(50); rect(20, 20, 200, 20);
  fill(player.health > 30 ? color(0, 255, 0) : color(255, 0, 0));
  rect(20, 20, map(player.health, 0, player.maxHealth, 0, 200), 20);
  
  fill(255); noStroke(); textSize(16); textAlign(LEFT, TOP);
  text(`HP: ${ceil(player.health)} / ${player.maxHealth}`, 20, 45);
  fill(255, 215, 0); textSize(20); text(`Coins: ${coins}`, 20, 70);
  fill(255); text(`Wave: ${wave}`, 20, 95);

  textAlign(RIGHT, TOP); textSize(24);
  if (gameMode === "SWARM") {
    text(`Dogs Remaining: ${dogs.length}`, width - 20, 20);
  } else {
    let bossHp = dogs.length > 0 ? (dogs[0].maxHits - dogs[0].hitsTaken) : 0;
    text(`Boss HP: ${bossHp}`, width - 20, 20);
  }
  
  textAlign(CENTER, BOTTOM); textSize(14); fill(200);
  if (mobileMode) {
    text("Use Joystick to move • Tap Button to Auto-Attack", width / 2, height - 20);
  } else {
    if (hasMachineGun) text("WASD to move • HOLD Click to Fire Machine Gun", width / 2, height - 10);
    else text("WASD to move • Click towards dog to PUNCH", width / 2, height - 10);
  }
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

calculation Health bar rendering rect(20, 20, map(player.health, 0, player.maxHealth, 0, 200), 20);

Draws a green health bar that shrinks as player.health decreases, scales with map()

conditional Mode-specific info display if (gameMode === "SWARM") {

Shows 'Dogs Remaining' in SWARM, 'Boss HP' in other modes

fill(50); rect(20, 20, 200, 20);
Draws a dark gray background for the health bar
fill(player.health > 30 ? color(0, 255, 0) : color(255, 0, 0));
Health bar is green when health > 30, red when health <= 30 (warning indicator)
rect(20, 20, map(player.health, 0, player.maxHealth, 0, 200), 20);
Draws the health bar from 0 to 200 pixels wide, scaled proportionally to health using map()
let bossHp = dogs.length > 0 ? (dogs[0].maxHits - dogs[0].hitsTaken) : 0;
Calculates remaining health of the boss by subtracting hits taken from max hits

drawStartScreen()

drawStartScreen() uses sin() and frameCount to create animated pulsing text, a common technique for drawing attention. It also provides a visual toggle for mobile controls.

function drawStartScreen() {
  textAlign(CENTER, CENTER);
  fill(255, 0, 0); textSize(60);
  text("3AM ALLEYWAY", width / 2, height / 2 - 50);
  fill(255); textSize(20);
  text("Survive endless waves of the rabid beast.", width / 2, height / 2 + 20);
  
  let alpha = map(sin(frameCount * 0.05), -1, 1, 100, 255);
  fill(100, 255, 100, alpha); textSize(18);
  text("Click or Tap to begin", width / 2, height / 2 + 90);
  
  let btnY = height / 2 + 160; let btnW = 260; let btnH = 50;
  fill(mobileMode ? color(50, 200, 100) : color(80));
  stroke(255); strokeWeight(2); rectMode(CENTER);
  rect(width / 2, btnY, btnW, btnH, 10); rectMode(CORNER);
  
  fill(255); noStroke(); textSize(20);
  text(`Mobile Controls: ${mobileMode ? "ON" : "OFF"}`, width / 2, btnY);
  
  fill(150); textSize(14); textAlign(CENTER, BOTTOM);
  text("made by corbun powerd by p5js.ai", width / 2, height - 20);
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

calculation Pulsing 'Click to begin' text let alpha = map(sin(frameCount * 0.05), -1, 1, 100, 255);

Maps the sine wave (oscillating -1 to 1) to alpha values (100 to 255), creating a fade in/out pulsing effect

conditional Mobile mode toggle visual fill(mobileMode ? color(50, 200, 100) : color(80));

Button turns green when mobile mode is ON, stays dark gray when OFF

fill(255, 0, 0); textSize(60); text("3AM ALLEYWAY", width / 2, height / 2 - 50);
Draws the red title text centered on screen
let alpha = map(sin(frameCount * 0.05), -1, 1, 100, 255);
Creates a smooth pulsing effect using sin(frameCount): as frameCount increases, sin oscillates, making alpha fade in and out
fill(mobileMode ? color(50, 200, 100) : color(80));
Button color changes based on mobileMode: green when ON, dark when OFF

drawShopScreen()

drawShopScreen() is the upgrade menu. It displays all purchasable items with dynamic colors: green for affordable, dark for unaffordable, gold for the god (ultimate upgrade), and dull green for already-owned gun.

function drawShopScreen() {
  background(10, 10, 15, 220); 
  textAlign(CENTER, CENTER);
  fill(0, 255, 0); textSize(50);
  text(`WAVE ${wave - 1} CLEARED!`, width / 2, 100);
  fill(255, 215, 0); textSize(30);
  text(`Coins: ${coins}`, width / 2, 150);

  let btnW = 300; let btnH = 55; let startY = 220; let gap = 65;
  let items = [
    { name: "Buy Wall (Blocks Dogs)", cost: 15, action: "WALL" },
    { name: hasMachineGun ? "Machine Gun (OWNED)" : "Punch Machine Gun", cost: 50, action: "GUN" },
    { name: "Hire Friend (Shoots Dogs)", cost: 40, action: "FRIEND" },
    { name: "Hire a GOD", cost: 10000, action: "GOD" },
    { name: "START NEXT WAVE", cost: 0, action: "NEXT" }
  ];

  for (let i = 0; i < items.length; i++) {
    let item = items[i];
    let y = startY + i * gap;
    let x = width / 2 - btnW / 2;
    
    let canAfford = coins >= item.cost;
    let isOwnedGun = (item.action === "GUN" && hasMachineGun);
    
    if (item.action === "NEXT") fill(100, 255, 100);
    else if (item.action === "GOD" && canAfford) fill(255, 215, 0);
    else if (item.action === "GOD" && !canAfford) fill(80, 80, 0);
    else if (isOwnedGun) fill(50, 150, 50);
    else if (!canAfford) fill(80);
    else fill(50, 150, 255);
    
    stroke(0); strokeWeight(2);
    rect(x, y, btnW, btnH, 10);
    
    fill(255); noStroke(); textSize(18);
    let label = item.name;
    if (item.cost > 0 && !isOwnedGun) label += ` [${item.cost}c]`;
    text(label, width / 2, y + btnH / 2);
  }
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

conditional Item affordability logic let canAfford = coins >= item.cost;

Determines if the player has enough coins to buy an item

conditional Dynamic button colors if (item.action === "NEXT") fill(100, 255, 100);

Colors buttons based on type (next wave=green, god=gold, unaffordable=dark, owned=dull green)

background(10, 10, 15, 220);
Draws a semi-transparent dark blue background over the game world, darkening it slightly
text(`WAVE ${wave - 1} CLEARED!`, width / 2, 100);
Shows the wave just cleared (wave - 1, because we incremented wave after this one started)
let canAfford = coins >= item.cost;
Checks if player has enough coins to afford the item
if (item.action === "NEXT") fill(100, 255, 100);
The 'START NEXT WAVE' button is always green, regardless of coins (it's free)
if (item.cost > 0 && !isOwnedGun) label += ` [${item.cost}c]`;
Appends the cost to the button label (e.g., 'Buy Wall [15c]') unless it's free or already owned

drawLoseScreen()

drawLoseScreen() shows the game over state with a brief cooldown (deathCooldown) before allowing restart, preventing accidental rapid clicks.

function drawLoseScreen() {
  if (deathCooldown > 0) deathCooldown--;
  
  background(50, 0, 0, 200);
  textAlign(CENTER, CENTER);
  fill(255, 0, 0); textSize(60);
  text("YOU DIED", width / 2, height / 2 - 40);
  fill(255); textSize(24);
  text(`You survived until Wave ${wave} in ${gameMode} mode`, width / 2, height / 2 + 30);
  
  textSize(16);
  if (deathCooldown <= 0) {
    let alpha = map(sin(frameCount * 0.05), -1, 1, 100, 255);
    fill(255, alpha);
    text("Click or Tap to return to menu", width / 2, height / 2 + 90);
  } else {
    fill(150); text("...", width / 2, height / 2 + 90);
  }
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

conditional Death screen delay if (deathCooldown > 0) deathCooldown--;

Prevents immediate restart, giving time for player to see they died

if (deathCooldown > 0) deathCooldown--;
Decrements the cooldown counter each frame; when it reaches 0, the restart prompt becomes clickable
background(50, 0, 0, 200);
Draws a semi-transparent red tint over the screen to signal failure
text(`You survived until Wave ${wave} in ${gameMode} mode`, width / 2, height / 2 + 30);
Shows the player's final stats: how far they got and which mode they played

📦 Key Variables

gameState string

Tracks which screen is active: START, MODE_SELECT, PLAYING, SHOP, or LOSE. Controls what draw() renders.

let gameState = "START";
gameMode string

Tracks the selected difficulty: CLASSIC (one scaling boss), SWARM (many weak enemies), or HARDCORE (1 HP instant machine gun).

let gameMode = "CLASSIC";
mobileMode boolean

Toggles between mobile joystick/button controls and desktop keyboard/mouse controls.

let mobileMode = false;
wave number

Tracks the current wave number, used to scale enemy difficulty, health, and speed.

let wave = 1;
coins number

Tracks currency earned from clearing waves, spent on upgrades in the shop.

let coins = 0;
hasMachineGun boolean

Flag indicating whether the player has purchased the machine gun upgrade. If true, clicking holds to fire instead of punching.

let hasMachineGun = false;
player object

Object storing player state: position (Vector), health, max health, movement speed, punch range, and punch animation frames.

let player = { pos: createVector(200, 200), health: 100, maxHealth: 100, speed: 4.0, ... };
dogs array

Array of enemy objects. Each dog has position, speed, health (hitsTaken/maxHits), stun duration, and hit flash state.

let dogs = [{pos: createVector(0, -300), speed: 5, ...}];
projectiles array

Array of bullets/fists in flight. Each has position, velocity, lifetime, damage, and isGod flag (god projectiles are stronger).

let projectiles = [{pos: createVector(x, y), vel: createVector(15, 0), life: 60, damage: 1, isGod: false}];
particles array

Array of visual effects (impact explosions). Each has position, velocity, and remaining lifetime before fade.

let particles = [{pos: createVector(x, y), vel: p5.Vector.random2D().mult(3), life: 255}];
walls array

Array of defensive obstacles that block enemy movement. Each wall has position, radius, and health points.

let walls = [{pos: createVector(200, 200), radius: 25, hp: 150}];
friends array

Array of allied NPCs hired in the shop. They chase and shoot dogs with weak projectiles.

let friends = [{pos: createVector(100, 100), speed: 3.0, radius: 12}];
gods array

Array of the most powerful allies. Gods shoot with exponentially scaling damage and faster fire rate as waves progress.

let gods = [{pos: createVector(200, 200), speed: 6.0, radius: 30}];
alleyWidth number

The width of the playable alley in pixels. Players and enemies are constrained to this horizontal zone.

let alleyWidth = 600;
shakeAmount number

Current intensity of screen shake effect, decays by multiplying by 0.9 each frame until reaching 0.

let shakeAmount = 0;
deathCooldown number

Frames to wait after death before allowing restart. Prevents accidental rapid clicking.

let deathCooldown = 0;

🔧 Potential Improvements (5)

Here are some ways this code could be enhanced:

BUG executeP​unch() collision detection

The punch cone check uses PI/2 (90 degrees) which is quite wide. Dogs slightly behind the player can still be hit.

💡 Reduce the cone angle to PI/4 (45 degrees) for more precise directional punching, or add a dot product check for more accurate forward-facing detection.

PERFORMANCE updateLogic() AI loops

Every frame, each friend and god loops through ALL dogs to find the closest one, and each dog loops through ALL walls. With many entities this gets expensive.

💡 Implement a spatial grid or quadtree to quickly find nearby entities, or cache the closest target for a few frames instead of recalculating every frame.

STYLE Projectile hit detection and damage

God projectiles are treated as a special case throughout (different radius, damage, particle count). This magic-number approach is hard to maintain.

💡 Create a projectile type system: define PROJECTILE_TYPES = {NORMAL: {radius: 15, damage: 1, particles: 3}, GOD: {radius: 25, damage: 'scaling', particles: 8}} to make upgrades easier.

FEATURE Shop system

Players can buy unlimited friends and walls but no cap exists. Very wealthy players can create lag with hundreds of entities on screen.

💡 Add purchase limits (e.g., max 10 friends, max 50 walls) or implement entity cleanup when count exceeds a threshold, OR increase purchase costs exponentially (cost = baseCost * (1.1 ^ numOwned)).

BUG Mobile mode input handling

On desktop, when holding down the mouse in gun mode, projectiles fire every 6 frames. But if frameRate drops, bullets become inconsistent, since frameCount % 6 is frame-dependent rather than time-dependent.

💡 Use millis() instead of frameCount for timing: `let timeSinceLastShot = millis() - lastShotTime; if (timeSinceLastShot > 100) { fire(); lastShotTime = millis(); }`

🔄 Code Flow

Code flow showing setup, initgame, startNextWave, keypressed, draw, updatelogic, executepunch, handleinput, drawplayer, drawdogs, drawflashlight, drawhud, drawstartscreen, drawshopscreen, drawlosescreen

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> initgame[initGame] initgame --> startNextWave[startNextWave] startNextWave --> draw[draw loop] draw --> updatelogic[updateLogic] updatelogic --> handleinput[handleInput] handleinput --> game-state-router[game-state-router] game-state-router -->|Menu| drawstartscreen[drawStartScreen] game-state-router -->|Shop| drawshopscreen[drawShopScreen] game-state-router -->|Lose| drawlosescreen[drawLoseScreen] game-state-router -->|Playing| drawplayer[drawPlayer] drawplayer --> punch-extension[punch-extension] drawplayer --> player-body[player-body] drawplayer --> punch-trigger[punch-trigger] drawplayer --> stun-jitter[stun-jitter] drawplayer --> hit-flash[hit-flash] drawplayer --> health-bar[health-bar] drawplayer --> dynamic-mode-info[dynamic-mode-info] drawplayer --> drawhud[drawHUD] drawhud --> gradient-creation[gradient-creation] drawhud --> mobile-mode-button[mobile-mode-button] drawhud --> affordability-check[affordability-check] drawhud --> button-coloring[button-coloring] drawplayer --> drawflashlight[drawFlashlight] drawplayer --> drawdogs[drawDogs] drawdogs --> dog-ai[dog-ai] dog-ai --> punch-range-check[punch-range-check] dog-ai --> wave-clear-check[wave-clear-check] drawdogs --> friend-ai[friend-ai] drawdogs --> god-ai[god-ai] drawdogs --> screen-shake[screen-shake] updatelogic --> keyboard-movement[keyboard-movement] updatelogic --> projectile-physics[projectile-physics] updatelogic --> mode-based-init[mode-based-init] mode-based-init -->|Hardcore| cheat-activation[cheat-activation] cheat-activation --> code-accumulator[code-accumulator] code-accumulator --> cheat-detection[cheat-detection] cheat-detection --> screen-shake click setup href "#fn-setup" click initgame href "#fn-initgame" click startNextWave href "#fn-startNextWave" click draw href "#fn-draw" click updatelogic href "#fn-updateLogic" click handleinput href "#fn-handleInput" click drawstartscreen href "#fn-drawStartScreen" click drawshopscreen href "#fn-drawShopScreen" click drawlosescreen href "#fn-drawLoseScreen" click drawplayer href "#fn-drawPlayer" click punch-extension href "#sub-punch-extension" click player-body href "#sub-player-body" click punch-trigger href "#sub-punch-trigger" click stun-jitter href "#sub-stun-jitter" click hit-flash href "#sub-hit-flash" click health-bar href "#sub-health-bar" click dynamic-mode-info href "#sub-dynamic-mode-info" click drawhud href "#fn-drawHUD" click gradient-creation href "#sub-gradient-creation" click mobile-mode-button href "#sub-mobile-mode-button" click affordability-check href "#sub-affordability-check" click button-coloring href "#sub-button-coloring" click drawflashlight href "#fn-drawFlashlight" click drawdogs href "#fn-drawDogs" click dog-ai href "#sub-dog-ai" click punch-range-check href "#sub-punch-range-check" click wave-clear-check href "#sub-wave-clear-check" click friend-ai href "#sub-friend-ai" click god-ai href "#sub-god-ai" click screen-shake href "#sub-screen-shake" click keyboard-movement href "#sub-keyboard-movement" click projectile-physics href "#sub-projectile-physics" click mode-based-init href "#sub-mode-based-init" click cheat-activation href "#sub-cheat-activation" click code-accumulator href "#sub-code-accumulator" click cheat-detection href "#sub-cheat-detection"

❓ Frequently Asked Questions

What visual experience does the SURVIVE CHIWAWA IN ALLEYWAY AT 3AM sketch provide?

This sketch creates a dynamic and chaotic alleyway scene where players must survive waves of animated dogs and manage their resources.

How can players interact with the SURVIVE CHIWAWA IN ALLEYWAY AT 3AM game?

Players can navigate their character, engage in combat, collect coins, and activate a secret cheat code for enhanced gameplay.

What creative coding techniques are showcased in the SURVIVE CHIWAWA IN ALLEYWAY AT 3AM sketch?

The sketch demonstrates concepts such as game state management, procedural generation of entities, and dynamic interactions within a game environment.

Preview

SURVIVE CHIWAWA IN ALLEYWAY AT 3AM - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of SURVIVE CHIWAWA IN ALLEYWAY AT 3AM - Code flow showing setup, initgame, startNextWave, keypressed, draw, updatelogic, executepunch, handleinput, drawplayer, drawdogs, drawflashlight, drawhud, drawstartscreen, drawshopscreen, drawlosescreen
Code Flow Diagram