html, body {
  margin: 0;
  padding: 0;
  overflow: hidden;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  background-color: #333;
}

canvas {
  display: block;
}
let puckPos, puckVel;
let puckRadius, puckFriction, puckMinSpeed, puckMaxSpeed;

let paddlePos, prevPaddlePos;
let paddleRadius;

let arenaMargin, canvasWidth, canvasHeight;
let goalRadius;
let cornerRadius, cornerCenters = [];

let playerScore = 0;
let opponentScore = 0;

let passedGoalThreshold = false;
let goalScored = false;
let goalTimer = 0;
let goalDelay;

let playerSideStart = true;

let wallSmacked = false;
let wallCollisionTimer = 0;
let shakeTime;

let sparks = [];

let aiPos, aiPrevPos;
let aiRadius;

const AI_STATE   = { DEFEND: 0, ATTACK: 1, POSSESSION: 2 };
let aiState = AI_STATE.DEFEND;

let MAX_AI_SPD, stallFrames = 0, stallDuration;

let aiDefendNoiseOffset = 0; // for smooth randomness

let adaptiveMaxAISpd, adaptiveClampRange, adaptiveGoalKick;

let isMuted = false;

const WINNING_SCORE = 7;

let gameOver = false;


function setup() {
  calculateCanvasSize();
  createCanvas(canvasWidth, canvasHeight);

  // Responsive values based on canvas size
  arenaMargin = canvasHeight * 0.05;
  puckRadius = canvasWidth * 0.0375;
  paddleRadius = canvasWidth * 0.05;
  aiRadius = paddleRadius;
  cornerRadius = canvasWidth * 0.025;
  goalRadius = canvasWidth * 0.2;
  puckFriction = 0.985;
  puckMinSpeed = canvasWidth * 0.0008;
  puckMaxSpeed = canvasWidth * 0.055; 
  goalDelay = 1000;
  shakeTime = 500;

  MAX_AI_SPD = canvasHeight * 0.014;
  stallDuration = 25;

  adaptiveMaxAISpd = MAX_AI_SPD;
  adaptiveClampRange = goalRadius;
  adaptiveGoalKick = canvasWidth * 0.013;

  cornerCenters = [
    createVector(arenaMargin, arenaMargin), // Top left
    createVector(width - arenaMargin, arenaMargin), // Top right
    createVector(arenaMargin, height - arenaMargin), // Bottom left
    createVector(width - arenaMargin, height - arenaMargin) // Bottom right
  ];

  initGameObjects();
}

function calculateCanvasSize() {
  let minHeight = 320;
  canvasHeight = max(minHeight, windowHeight);
  canvasWidth = canvasHeight * 0.75;  // 3:4 aspect ratio
}

function initGameObjects() {
  if (playerSideStart) {
    puckPos = createVector(width / 2, height / 2 + height / 8);
  } else {
    puckPos = createVector(width / 2, height / 2 - height / 8);
  }
  puckVel = createVector(0, 0);

  paddlePos = createVector(mouseX, constrain(mouseY, height / 2, height)); // player is on bottom half
  prevPaddlePos = paddlePos.copy();

  aiPos = createVector(width / 2, arenaMargin + aiRadius * 2);
  aiPrevPos = aiPos.copy();
}

function draw() {
  if (gameOver) {
    drawGameOverScreen();
    // Rematch button
    let btnW = canvasWidth * 0.38;
    let btnH = canvasWidth * 0.12;
    let btnX = width / 2;
    let btnY = height - arenaMargin - btnH * 1.2;
    if (
      mouseX > btnX - btnW / 2 &&
      mouseX < btnX + btnW / 2 &&
      mouseY > btnY - btnH / 2 &&
      mouseY < btnY + btnH / 2
    ) {
      cursor('pointer');
    } else {
      cursor('default');
    }
    return;
  }

  if (goalScored) {
    let shake = width / 72;
    translate(random(-shake, shake), random(-shake, shake));
  }

  if (wallSmacked && millis() - wallCollisionTimer <= shakeTime) {
    let shake = map(pVelocity, 0, puckMaxSpeed, 0, width / 72);
    translate(random(-shake, shake), random(-shake, shake));
  }

  if (millis() - wallCollisionTimer > shakeTime) {
    wallSmacked = false;
  }

  background(30);
  drawArena();

  prevPaddlePos.set(paddlePos);

  let targetX, targetY;

  if (touches.length > 0) {
    targetX = touches[0].x;
    targetY = touches[0].y;
  } else {
    targetX = mouseX;
    targetY = mouseY;
  }

  paddlePos.set(
    constrain(targetX, arenaMargin + paddleRadius, width - arenaMargin - paddleRadius),
    constrain(targetY, height / 2 + paddleRadius, height - arenaMargin - paddleRadius)
  );

  aiPrevPos.set(aiPos);
  updateAI();

  updatePuck();
  checkWallCollision();
  checkCornerCollision();

  if (passedGoalThreshold) checkGoal();

  checkPaddleCollision(); // human paddle
  checkAIPaddleCollision(aiPos, aiPrevPos); // AI paddle

  drawPuck();
  drawPlayerPaddle(); 
  drawAIPaddle();

  for (let i = sparks.length - 1; i >= 0; i--) {
    sparks[i].update();
    sparks[i].show();
    if (sparks[i].isDead()) sparks.splice(i, 1);
  }

  drawTopAndBackWalls();
  drawScoreboard();

  // Mute button 
  let btnSize = canvasWidth * 0.08;
  let muteBtnX = width - btnSize * 0.7;
  let muteBtnY = btnSize * 0.7;
  if (
    mouseX > muteBtnX - btnSize / 2 &&
    mouseX < muteBtnX + btnSize / 2 &&
    mouseY > muteBtnY - btnSize / 2 &&
    mouseY < muteBtnY + btnSize / 2
  ) {
    cursor('pointer');
  } else {
    cursor('default');
  }
}

function drawArena() {
  noFill();
  stroke(100);
  strokeWeight(canvasWidth * 0.005);
  rect(arenaMargin, arenaMargin, width - arenaMargin * 2, height - arenaMargin * 2, cornerRadius * 2);

  drawGoalAreas();
  drawCenterLine();

  drawCenterFaceoffCircleWithCornerHashmarks();

  if (goalScored) {
    push();
    stroke(random(150, 255));
    strokeWeight(width * 0.025);
    playerSideStart ? fill(227, 66, 52) : fill(50, 150, 255);
    textSize(width * 0.25);
    textAlign(CENTER, CENTER);
    textStyle(BOLD);
    text("GOAL!", width / 2, height / 2)
    pop();
  }
}

// for puck sliding in the goal effect
function drawTopAndBackWalls() {
  fill(30, 30, 30);
  rect(0, 0, width, arenaMargin - 2);
  rect(0, height - arenaMargin + 2, width, height);
}

function drawScoreboard() {
  textAlign(CENTER, TOP);
  textSize(width * 0.05);
  fill(50, 150, 255);
  text(`${playerScore}`, width / 2 - 30, 10);
  fill(255);
  text(` - `, width / 2, 10);
  fill(227, 66, 52);
  text(`${opponentScore}`, width / 2 + 30, 10);

  // mute button
  let btnSize = canvasWidth * 0.08; // 8% of width
  let btnX = width - btnSize * 0.7;
  let btnY = btnSize * 0.7;
  let btnRadius = btnSize * 0.18;

  fill(40);
  stroke(80);
  strokeWeight(canvasWidth * 0.005);
  rectMode(CENTER);
  rect(btnX, btnY, btnSize, btnSize, btnRadius);

  // Emoji Icons
  noStroke();
  fill(230);
  textSize(btnSize * 0.5);
  textAlign(CENTER, CENTER);
  text(isMuted ? "🔇" : "🔊", btnX, btnY - btnSize * 0.18);

  // Clarifying text inside button
  textSize(btnSize * 0.23);
  fill(220);
  textAlign(CENTER, CENTER);
  text(isMuted ? "unmute" : "mute", btnX, btnY + btnSize * 0.22);

  rectMode(CORNER); // Restore default
}

function drawGoalAreas() {
  stroke(180); // light gray
  strokeWeight(canvasWidth * 0.0025);
  noFill();

  const goalWidth = goalRadius * 2;

  // Top Goal
  arc(width / 2, arenaMargin, goalWidth, goalWidth, 0, PI);

  // Bottom Goal
  arc(width / 2, height - arenaMargin, goalWidth, goalWidth, PI, TWO_PI);
}


function drawCenterLine() {
  stroke(180);
  strokeWeight(canvasWidth * 0.0025);

  const dashLength = width * 0.015;
  const gapLength = dashLength;

  const centerY = height / 2;
  const startX = arenaMargin;
  const endX = width - arenaMargin;

  for (let x = startX; x < endX; x += dashLength + gapLength) {
    line(x, centerY, x + dashLength, centerY);
  }
}


function drawCenterFaceoffCircleWithCornerHashmarks() {
  const centerX = width / 2;
  const centerY = height / 2;

  const baseSize = width;
  const circleRadius = baseSize * 0.15;
  const dotDiameter = circleRadius * 0.2;

  const hashOffset = circleRadius * 0.1;
  const hashLength = circleRadius * 0.1;

  let lineColor = color(180);
  stroke(lineColor);
  strokeWeight(canvasWidth * 0.0025);
  noFill();

  // Circle ring
  ellipse(centerX, centerY, circleRadius * 2);

  // Center dot
  noStroke();
  fill(lineColor);
  ellipse(centerX, centerY, dotDiameter);

  // Corner hashmark L shapes
  stroke(lineColor);
  strokeWeight(canvasWidth * 0.0025);

  const dirs = [
    { dx: -1, dy: -1 }, // top left
    { dx: 1, dy: -1 },  // top right
    { dx: -1, dy: 1 },  // bottom left
    { dx: 1, dy: 1 }    // bottom right
  ];

  for (let d of dirs) {
    const x = centerX + d.dx * hashOffset;
    const y = centerY + d.dy * hashOffset;

    // Vertical segment
    line(x, y, x, y + d.dy * hashLength);

    // Horizontal segment
    line(x, y, x + d.dx * hashLength, y);
  }
}

function drawPuck() {
  fill(255);
  noStroke();
  circle(puckPos.x, puckPos.y, puckRadius * 2);
}

function drawPlayerPaddle() { 
  drawPaddleBase(paddlePos, color(50, 150, 255)); 
}
function drawAIPaddle() { 
  drawPaddleBase(aiPos, color(227, 66, 52));          
}

function drawPaddleBase(pos, col) {
  fill(col);
  circle(pos.x, pos.y, paddleRadius * 2);
  fill(30);
  noStroke();
  circle(pos.x, pos.y, paddleRadius);
  fill(col);
  noStroke();
  circle(pos.x, pos.y, paddleRadius * 0.75);
}


function updatePuck() {
  puckPos.add(puckVel);
  puckVel.mult(puckFriction);

  if (puckVel.mag() < puckMinSpeed) {
    puckVel.set(0, 0);
  }

  if (puckVel.mag() > puckMaxSpeed) {
    puckVel.setMag(puckMaxSpeed);
  }
}

function resetPuck() {
  if (playerSideStart) {
    puckPos.set(width / 2, height / 2 + height / 8);
  } else {
    puckPos.set(width / 2, height / 2 - height / 8);
  }
  puckVel.set(0, 0);
}

function checkGoal() {
  if (!goalScored) {
    puckVel.y *= 5;
    if (puckPos.y + puckRadius > height - arenaMargin) {
      goalScored = true;
      opponentScore++;
      playerSideStart = true;
      goalTimer = millis();
      playGoalSound();
    } else if (puckPos.y - puckRadius < arenaMargin) {
      goalScored = true;
      playerScore++;
      playerSideStart = false;
      goalTimer = millis();
      playGoalSound();
    }
  }

  // Check for game over
  if (playerScore >= WINNING_SCORE || opponentScore >= WINNING_SCORE) {
    gameOver = true;
    return;
  }

  if (goalScored && millis() - goalTimer > goalDelay) {
    resetPuck();
    goalScored = false;
    passedGoalThreshold = false;
  }
}

function isInsideGoalRange() {
  const goalCenter = width / 2;
  if (puckPos.x >= goalCenter - goalRadius && puckPos.x <= goalCenter + goalRadius) {
    return true
  }
}


function checkWallCollision() {
  // Check Goal threshold
  const isInsideGoalXRange = isInsideGoalRange();
  if (isInsideGoalXRange || passedGoalThreshold) {
    if (puckPos.y > height - arenaMargin || puckPos.y < arenaMargin) {
      passedGoalThreshold = true
    }
  } else {
    // Left wall
    if (puckPos.x - puckRadius < arenaMargin) {
      let normal = createVector(1, 0);
      let penetration = arenaMargin + puckRadius - puckPos.x;
      puckPos.x += penetration + 0.5;
      wallSmacked = true;
      wallCollisionTimer = millis();
      pVelocity = puckVel.mag();
      playWallSound(puckVel.mag());
      createSparks(puckPos, normal);
      applyWallGlide(normal);
    }

    // Right wall
    if (puckPos.x + puckRadius > width - arenaMargin) {
      let normal = createVector(-1, 0);
      let penetration = puckPos.x + puckRadius - (width - arenaMargin);
      puckPos.x -= penetration + 0.5;
      wallSmacked = true;
      wallCollisionTimer = millis();
      pVelocity = puckVel.mag();
      playWallSound(puckVel.mag());
      createSparks(puckPos, normal);
      applyWallGlide(normal);
    }

    // Top wall
    if (puckPos.y - puckRadius < arenaMargin) {
      let normal = createVector(0, 1);
      let penetration = arenaMargin + puckRadius - puckPos.y;
      puckPos.y += penetration + 0.5;
      wallSmacked = true;
      wallCollisionTimer = millis();
      pVelocity = puckVel.mag();
      playWallSound(puckVel.mag());
      createSparks(puckPos, normal);
      applyWallGlide(normal);
    }

    // Bottom wall
    if (puckPos.y + puckRadius > height - arenaMargin) {
      let normal = createVector(0, -1);
      let penetration = puckPos.y + puckRadius - (height - arenaMargin);
      puckPos.y -= penetration + 0.5;
      wallSmacked = true;
      wallCollisionTimer = millis();
      pVelocity = puckVel.mag();
      playWallSound(puckVel.mag());
      createSparks(puckPos, normal);
      applyWallGlide(normal);
    }
  }
}

function applyWallGlide(normal) {
  let vDotN = puckVel.dot(normal);

  if (vDotN < 0) {
    // Tangent direction = velocity - normal component
    let normalComponent = normal.copy().mult(vDotN);
    let tangentComponent = puckVel.copy().sub(normalComponent);

    let approachAngle = abs(vDotN) / puckVel.mag(); // 0 = grazing, 1 = head-on

    // Blend between gliding and bounce
    let glideRatio = constrain(map(approachAngle, 0, 0.5, 1, 0), 0, 1);
    let bounceRatio = 1 - glideRatio;

    let reflected = tangentComponent.copy().add(normal.copy().mult(-vDotN * bounceRatio));

    puckVel.set(reflected);
    puckVel.mult(0.98);
  }
}

function checkCornerCollision() {
  for (let corner of cornerCenters) {
    let toPuck = p5.Vector.sub(puckPos, corner);
    let dist = toPuck.mag();
    let allowedDist = cornerRadius - 2 + puckRadius;

    if (dist < allowedDist && dist > 0.01) {
      let normal = toPuck.copy().normalize();
      let overlap = allowedDist - dist;

      // Push puck out of the wall
      puckPos.add(normal.copy().mult(overlap + 0.5));

      let vDotN = puckVel.dot(normal);

      if (vDotN < 0) {
        // Project velocity onto tangent
        let tangent = createVector(-normal.y, normal.x); // Perpendicular to normal
        let tangentSpeed = puckVel.dot(tangent);

        // Preserve full tangent speed, discard only the wall-piercing part
        puckVel = tangent.copy().mult(tangentSpeed);

        // Optional: very small speed loss to simulate real contact
        puckVel.mult(0.999); 
      }
    }
  }
}

function checkPaddleCollision() {
  let toPuck = p5.Vector.sub(puckPos, paddlePos);
  let dist = toPuck.mag();
  let minDist = puckRadius + paddleRadius;

  if (dist < minDist && dist > 0.01) {
    let collisionNormal = toPuck.copy().normalize();

    let paddleVel = p5.Vector.sub(paddlePos, prevPaddlePos);
    let relativeVel = p5.Vector.sub(puckVel, paddleVel);
    let speedTowardEachOther = relativeVel.dot(collisionNormal);

    // Only apply if actually moving toward each other
    if (speedTowardEachOther < 0) {
      // Scale impulse based on approach speed
      let impulseStrength = constrain(-speedTowardEachOther, 0, 10);
      let impulse = collisionNormal.copy().mult(impulseStrength * 1.2);
      puckVel.add(impulse);
      playHitSound(impulseStrength);
      createSparks(puckPos, collisionNormal);
      
      if (puckVel.mag() > puckMaxSpeed) {
        puckVel.setMag(puckMaxSpeed);
      }

      // Resolve overlap, but prevent pushing puck into walls
      let overlap = minDist - dist;
      let correction = collisionNormal.copy().mult(overlap + 0.5);

      let newPos = p5.Vector.add(puckPos, correction);

      // Clamp to stay inside arena after correction
      newPos.x = constrain(newPos.x, arenaMargin + puckRadius, width - arenaMargin - puckRadius);
      newPos.y = constrain(newPos.y, arenaMargin + puckRadius, height - arenaMargin - puckRadius);

      puckPos.set(newPos);
    }
  }
}

function checkAIPaddleCollision(pos, prev) {
  let toPuck = p5.Vector.sub(puckPos, pos);
  let dist = toPuck.mag();
  let minDist = puckRadius + paddleRadius;

  if (dist < minDist && dist > 0.01) {
    let collisionNormal = toPuck.copy().normalize();

    let paddleVel   = p5.Vector.sub(pos, prev);
    let relativeVel = p5.Vector.sub(puckVel, paddleVel);
    let speedToward = relativeVel.dot(collisionNormal);

    if (speedToward < 0) {
      let impulseStrength = constrain(-speedToward, 0, 20);
      let impulse = collisionNormal.copy().mult(impulseStrength * 1.2);

      const goalKick  = adaptiveGoalKick; // adaptive shot strength
      const goalDir   = dirToPlayerGoal(puckPos);
      impulse.add(goalDir.mult(goalKick));
      puckVel.add(impulse);
      playHitSound(impulseStrength);
      createSparks(puckPos, collisionNormal);

      if (puckVel.mag() > puckMaxSpeed) puckVel.setMag(puckMaxSpeed);

      let overlap = minDist - dist;
      let correction = collisionNormal.copy().mult(overlap + 0.5);
      let newPos = p5.Vector.add(puckPos, correction);
      newPos.x = constrain(newPos.x, arenaMargin + puckRadius, width - arenaMargin - puckRadius);
      newPos.y = constrain(newPos.y, arenaMargin + paddleRadius, height - arenaMargin - paddleRadius);
      puckPos.set(newPos);
    }
  }
}

function dirToPlayerGoal(fromVec) {
  const goalCenter = createVector(width / 2, height - arenaMargin);
  return p5.Vector.sub(goalCenter, fromVec).normalize();   // unit vector
}

function updateAI() {
  // Adaptive Difficulty
  let scoreDiff = playerScore - opponentScore;
  adaptiveMaxAISpd = map(constrain(scoreDiff, -5, 5), -5, 5, canvasHeight * 0.008, canvasHeight * 0.025); // speed
  adaptiveClampRange = map(constrain(scoreDiff, -5, 5), -5, 5, goalRadius * 1.5, goalRadius * 0.5); // defense
  adaptiveGoalKick = map(constrain(scoreDiff, -5, 5), -5, 5, canvasWidth * 0.009, canvasWidth * 0.025); // shot

  let puckOnAIHalf = puckPos.y < height / 2;
  // Responsive stall threshold
  if (puckOnAIHalf && puckVel.mag() < puckMinSpeed * 9)  stallFrames++; else stallFrames = 0;

  if (stallFrames > stallDuration) aiState = AI_STATE.POSSESSION;
  else if (puckOnAIHalf && puckVel.y < 0) aiState = AI_STATE.ATTACK;
  else aiState = AI_STATE.DEFEND;

  // choose target
  let target;
  switch (aiState) {
    case AI_STATE.DEFEND: {
      aiDefendNoiseOffset += 0.01;
      let noiseVal = noise(aiDefendNoiseOffset);
      let maxOffset = canvasWidth * 0.07;
      let offset = map(noiseVal, 0, 1, -maxOffset, maxOffset);

      let goalCenterX = width / 2;
      let defendX = constrain(
        puckPos.x + offset,
        goalCenterX - adaptiveClampRange - offset,
        goalCenterX + adaptiveClampRange + offset
      );

      target = createVector(
        defendX,
        arenaMargin + aiRadius + 10
      );
      break;
    }

    case AI_STATE.ATTACK:
        const lookAhead = 20; // frames
        let anticipated = p5.Vector.add(
            puckPos,
            p5.Vector.mult(puckVel, lookAhead / 60)
        );
        anticipated.y = constrain(anticipated.y,
                                    arenaMargin + aiRadius,
                                    height / 2 - aiRadius * 1.2);
        anticipated.x = constrain(anticipated.x,
                                    arenaMargin + aiRadius,
                                    width - arenaMargin - aiRadius);
        target = anticipated;
        break;

    case AI_STATE.POSSESSION: // retrieve stalled puck
      target = puckPos.copy();
      if (puckPos.dist(aiPos) < aiRadius * 0.6) {
        aiState = AI_STATE.ATTACK;
      }
      break;
  }

  // move towards target
  let desire = p5.Vector.sub(target, aiPos);
  if (desire.mag() > adaptiveMaxAISpd) desire.setMag(adaptiveMaxAISpd);
  aiPos.add(desire);

  let maxY = (aiState === AI_STATE.POSSESSION) ? height / 2 + aiRadius * 0.4
                                               : height / 2 - aiRadius;
  aiPos.y = constrain(aiPos.y, arenaMargin + aiRadius, maxY);
  aiPos.x = constrain(aiPos.x, arenaMargin + aiRadius, width - arenaMargin - aiRadius);
}

function playHitSound(force = 1) {
  if (isMuted) return;
  // Map force to volume
  let vol = map(force, 0, 20, 0.1, 0.5);

  let noise = new p5.Noise('white');
  let env = new p5.Envelope();
  env.setADSR(0.001, 0.05, 0, 0.05);
  env.setRange(vol, 0);
  noise.amp(env);
  noise.start();
  env.play(noise, 0, 0.03);
  noise.stop(0.08);

  let osc = new p5.Oscillator('sine');
  let env2 = new p5.Envelope();
  env2.setADSR(0.005, 0.08, 0, 0.08);
  env2.setRange(vol * 0.7, 0);
  osc.freq(180 + random(-30, 30));
  osc.amp(env2);
  osc.start();
  env2.play(osc, 0, 0.04);
  osc.stop(0.12);
}

function playWallSound(force = 1) {
  if (isMuted) return;
  let vol = map(force, 0, puckMaxSpeed, 0.1, 0.4);

  let noise = new p5.Noise('white');
  let env = new p5.Envelope();
  env.setADSR(0.001, 0.07, 0, 0.07);
  env.setRange(vol, 0);
  noise.amp(env);
  noise.start();
  env.play(noise, 0, 0.04);
  noise.stop(0.12);

  let osc = new p5.Oscillator('sine');
  let env2 = new p5.Envelope();
  env2.setADSR(0.002, 0.06, 0, 0.06);
  env2.setRange(vol * 0.7, 0);
  osc.freq(320 + random(-40, 40));
  osc.amp(env2);
  osc.start();
  env2.play(osc, 0, 0.03);
  osc.stop(0.10);
}

function playGoalSound() {
  if (isMuted) return;
  let osc = new p5.Oscillator('triangle');
  let env = new p5.Envelope();
  env.setADSR(0.01, 0.2, 0.2, 0.2);
  env.setRange(0.4, 0);
  osc.freq(220);
  osc.amp(env);
  osc.start();
  env.play(osc, 0, 0.1);
  osc.freq(440, 0.1);
  osc.stop(0.3);
}

class Spark {
  constructor(position, direction) {
    this.pos = position.copy();
    this.vel = direction.copy().mult(random(2, 5)).rotate(random(-PI / 6, PI / 6));
    this.life = 255;
    this.size = random(canvasWidth * 0.0035, canvasWidth * 0.008);
    this.color = color(255, 200 + random(55), 0); // fiery orange
  }

  update() {
    this.pos.add(this.vel);
    this.vel.mult(0.95);
    this.life -= 10;
  }

  isDead() {
    return this.life <= 0;
  }

  show() {
    noStroke();
    fill(red(this.color), green(this.color), blue(this.color), this.life);
    circle(this.pos.x, this.pos.y, this.size);
  }
}

function createSparks(position, direction, count = 7) {
  for (let i = 0; i < count; i++) {
    sparks.push(new Spark(position, direction));
  }
}

function windowResized() {
  calculateCanvasSize();
  resizeCanvas(canvasWidth, canvasHeight);

  // Update all responsive values based on new canvas size
  arenaMargin = canvasHeight * 0.05;
  puckRadius = canvasWidth * 0.0375;
  paddleRadius = canvasWidth * 0.05;
  aiRadius = paddleRadius;
  cornerRadius = canvasWidth * 0.025;
  goalRadius = canvasWidth * 0.2;
  puckFriction = 0.985;
  puckMinSpeed = canvasWidth * 0.0008;
  puckMaxSpeed = canvasWidth * 0.055;
  goalDelay = 1000;
  shakeTime = 500;

  MAX_AI_SPD = canvasHeight * 0.014;
  stallDuration = 25;

  adaptiveMaxAISpd = MAX_AI_SPD;
  adaptiveClampRange = goalRadius;
  adaptiveGoalKick = canvasWidth * 0.013;

  cornerCenters = [
    createVector(arenaMargin, arenaMargin),
    createVector(width - arenaMargin, arenaMargin),
    createVector(arenaMargin, height - arenaMargin),
    createVector(width - arenaMargin, height - arenaMargin)
  ];

  // Re-initialize paddle and puck positions to fit new canvas
  initGameObjects();
}

function mousePressed() {
  if (gameOver) {
    let btnW = canvasWidth * 0.38;
    let btnH = canvasWidth * 0.12;
    let btnX = width / 2;
    let btnY = height - arenaMargin - btnH * 1.2;
    if (
      mouseX > btnX - btnW / 2 &&
      mouseX < btnX + btnW / 2 &&
      mouseY > btnY - btnH / 2 &&
      mouseY < btnY + btnH / 2
    ) {
      // Reset game
      playerScore = 0;
      opponentScore = 0;
      gameOver = false;
      goalScored = false;
      passedGoalThreshold = false;
      resetPuck();
    }
    return;
  }

  // Mute button
  let btnSize = canvasWidth * 0.08;
  let btnX = width - btnSize * 0.7;
  let btnY = btnSize * 0.7;
  if (
    mouseX > btnX - btnSize / 2 &&
    mouseX < btnX + btnSize / 2 &&
    mouseY > btnY - btnSize / 2 &&
    mouseY < btnY + btnSize / 2
  ) {
    isMuted = !isMuted;
  }
}

function drawGameOverScreen() {
  background(30, 220); // semi-transparent overlay

  let winnerY = height * 0.22;
  let rematchY = height - arenaMargin - canvasWidth * 0.12 * 1.2;
  let scoreY = (winnerY + rematchY) / 2;

  let winner, winnerColor;
  if (playerScore >= WINNING_SCORE) {
    winner = "You Win!";
    winnerColor = color(50, 150, 255); // blue
  } else {
    winner = "Computer Wins!";
    winnerColor = color(227, 66, 52); // red
  }

  textAlign(CENTER, CENTER);
  textSize(canvasWidth * 0.12);
  fill(winnerColor);
  textStyle(BOLD);
  text(winner, width / 2, winnerY);

  // Final score
  textSize(canvasWidth * 0.16);
  fill(50, 150, 255);
  text(`${playerScore}`, width / 2 - canvasWidth * 0.13, scoreY);
  fill(255);
  text(` - `, width / 2, scoreY);
  fill(227, 66, 52);
  text(`${opponentScore}`, width / 2 + canvasWidth * 0.13, scoreY);

  // Rematch button
  let btnW = canvasWidth * 0.38;
  let btnH = canvasWidth * 0.12;
  let btnX = width / 2;
  let btnY = rematchY;
  let btnRadius = btnH * 0.35;

  fill(40);
  stroke(80);
  strokeWeight(canvasWidth * 0.005);
  rectMode(CENTER);
  rect(btnX, btnY, btnW, btnH, btnRadius);

  noStroke();
  fill(230);
  textSize(btnH * 0.5);
  textAlign(CENTER, CENTER);
  text("Rematch", btnX, btnY);

  rectMode(CORNER); // Restore default
}
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js
  2. https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/addons/p5.sound.min.js