๐ŸŽฎArcadeLab

Weenie's Great Feast!

by GlitchGecko62
857 lines40.2 KB
โ–ถ Play
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Weenie's Great Feast!</title>
<style>
  * { box-sizing: border-box; margin: 0; padding: 0; }
  body {
    background: #0a0500;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    font-family: 'Courier New', monospace;
  }
  #wrap {
    position: relative;
    width: 680px;
  }
  canvas {
    display: block;
    width: 680px;
    image-rendering: pixelated;
    cursor: pointer;
  }
  #hint {
    color: #5a3a00;
    font-size: 11px;
    text-align: center;
    padding: 4px 0;
    background: #1a0f00;
    letter-spacing: 0.5px;
    border-top: 1px solid #2d1b00;
  }
</style>
</head>
<body>
<div id="wrap">
  <canvas id="c" width="680" height="324"></canvas>
  <div id="hint">SPACE or click to jump ยท double jump allowed ยท hit a rock = drop your food</div>
</div>

<script>
/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   WEENIE'S GREAT FEAST โ€” Final Version
   All coordinates in pixels. No grid/pixel mixing.
   Layout: HUD 0-34 | PLAY 34-234 (GND=234) | METERS 238-320
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
const cv = document.getElementById('c');
const cx = cv.getContext('2d');
const W = 680, H = 324;
const HUD2 = 34, GND = 234, METY = 238, METH = 86, SKY_TOP = 56;
const GRAV = 0.45, J1 = -11.2, J2 = -9.4, PS = 3;

/* Food IDs */
const INGR_IDS = ['acorn','berry','carrot','mushroom','honey','bread'];
const OBS_IDS  = ['rock','log'];

/* Per-friend goals: name, ingredient, count needed */
const FDEF = [
  { name:'Bunny', want:'carrot',   need:3 },
  { name:'Pig',   want:'honey',    need:3 },
  { name:'Fox',   want:'mushroom', need:2, want2:'bread', need2:1 },
];

/* Height bands for food โ€” minimum is BANDS[1], ground only occasionally */
const BANDS_JUMP  = [GND-20, GND-40, GND-60, GND-78]; /* must jump */
const BAND_GROUND = GND+2;                              /* occasional ground */

const WORDS = ['DELICIOUS!','GREAT CATCH!','YUMMY!','NICE ONE!','YEAH!','SO FRESH!'];

/* โ”€โ”€ STATE โ”€โ”€ */
let G = {}, raf = null, winRaf = null, pmsg = null, _ls = '';
let winPhase = 0, wSeqT = 0;
let groundFoodCount = 0; /* track consecutive ground-level food */
let lastObsX = -999;    /* track last obstacle spawn x to enforce gap */
const MIN_OBS_GAP = 280; /* minimum pixel gap between obstacles */

function mkFriends() {
  return FDEF.map(f => ({
    name:  f.name,
    want:  f.want,
    need:  f.need,
    want2: f.want2 || null,
    need2: f.need2 || 0,
    got:   0,
    got2:  0,
    happy: false,
    hunger:0,
  }));
}

function resetG() {
  if (raf)    { cancelAnimationFrame(raf);    raf    = null; }
  if (winRaf) { cancelAnimationFrame(winRaf); winRaf = null; }
  pmsg = null; _ls = ''; winPhase = 0; wSeqT = 0; groundFoodCount = 0;
  G = {
    state:'start', fc:0, spd:2.4, phase:0,
    basket:[], hits:0, earlyRock:false,
    friends: mkFriends(),
    wx:80, wy:GND, wvy:0, wjumps:0, wfr:0, wflash:0, wbounce:0,
    items:[], sparks:[], popups:[], fly:[],
    clouds:[{x:80,y:58},{x:240,y:50},{x:420,y:64},{x:590,y:52}],
    trees:[{x:60},{x:220},{x:430},{x:610}],
    gOff:0, endT:0, btn1:null, btn2:null,
  };
  groundFoodCount = 0;
  lastObsX = -999;
}
resetG();

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   PIXEL SPRITE RENDERER
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function spr(ox, oy, sc, d) {
  for (let i = 0; i < d.length; i++) {
    if (!d[i][3]) continue;
    cx.fillStyle = d[i][0];
    cx.fillRect(ox + d[i][1]*sc, oy + d[i][2]*sc, d[i][3]*sc, d[i][4]*sc);
  }
}

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   FOOD SPRITES  (10ร—10 grid, rendered at PS=3)
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
const FOOD = {
  acorn:[['#3a2000',1,0,4,1],['#3a2000',0,1,6,2],['#3a2000',0,3,1,5],['#3a2000',5,3,1,5],['#3a2000',1,8,4,1],['#7a5020',1,1,4,1],['#6a4010',1,2,4,1],['#2a1400',2,0,2,1],['#c8841a',1,4,4,4],['#e8a030',2,4,2,2],['#8a5010',1,7,4,1]],
  berry:[['#1a0800',2,1,3,1],['#1a0800',1,2,5,1],['#1a0800',0,3,6,1],['#1a0800',0,4,1,3],['#1a0800',6,4,1,3],['#1a0800',0,7,6,1],['#1a0800',1,8,4,1],['#cc2244',1,3,4,1],['#dd3355',1,4,4,3],['#bb1133',1,7,4,1],['#ff6688',2,3,2,1],['#2a6010',2,0,2,2],['#1a4008',3,0,1,2]],
  carrot:[['#3a8010',1,0,2,3],['#2a6008',3,0,2,3],['#4a9818',0,0,2,2],['#1a0800',3,2,2,1],['#1a0800',2,3,4,1],['#1a0800',1,4,5,1],['#1a0800',1,5,5,1],['#1a0800',2,6,4,1],['#1a0800',3,7,3,1],['#1a0800',4,8,2,1],['#e8621a',3,3,2,1],['#e8621a',2,4,4,1],['#e8621a',2,5,4,1],['#e8621a',3,6,3,1],['#e8621a',4,7,2,1],['#ff8840',3,4,1,1],['#cc4410',4,6,1,1]],
  mushroom:[['#1a0800',2,0,5,1],['#1a0800',1,1,7,1],['#1a0800',0,2,9,1],['#1a0800',0,3,9,1],['#1a0800',0,4,3,1],['#1a0800',6,4,3,1],['#1a0800',0,2,1,2],['#1a0800',8,2,1,2],['#cc2200',2,1,5,1],['#cc2200',1,2,7,1],['#cc2200',1,3,3,1],['#cc2200',5,3,3,1],['#ee3300',2,2,5,1],['#ff6644',3,2,3,1],['#f0e0c0',3,4,3,2],['#1a0800',2,4,5,2],['#1a0800',2,6,5,1],['#fff',2,2,1,1],['#fff',5,3,1,1]],
  honey:[['#1a0800',2,0,5,1],['#1a0800',1,1,7,1],['#1a0800',0,2,1,6],['#1a0800',8,2,1,6],['#1a0800',0,8,9,1],['#c07010',2,0,5,1],['#d4881a',1,1,7,1],['#e8a020',1,2,7,5],['#ffc030',2,2,5,3],['#e89010',1,7,7,1],['#fff8e0',2,2,2,1],['#ffd050',3,4,2,1]],
  bread:[['#1a0800',2,0,6,1],['#1a0800',1,1,8,1],['#1a0800',0,2,10,1],['#1a0800',0,3,10,3],['#1a0800',0,6,10,1],['#1a0800',1,7,8,1],['#1a0800',2,8,6,1],['#c87820',2,1,6,1],['#d98a28',1,2,8,1],['#e8a030',1,3,8,3],['#f0b040',2,3,6,2],['#c87820',1,6,8,1],['#b06818',2,7,6,1],['#ffc840',3,2,4,1],['#fff8e0',3,3,2,1],['#a05810',2,2,6,1],['#f0d880',3,4,1,1],['#f0d880',6,3,1,1],['#f0d880',5,5,1,1]],
  rock:[['#1a0800',1,0,9,1],['#1a0800',0,1,11,1],['#1a0800',0,2,11,5],['#1a0800',0,7,11,1],['#1a0800',1,8,9,1],['#888880',1,1,9,1],['#aaaaaa',1,2,9,2],['#cccccc',2,2,7,1],['#bbbbaa',1,3,5,1],['#aaaaaa',8,2,2,2],['#666660',0,6,11,1],['#555550',1,7,9,1]],
  log:[['#1a0800',0,1,13,1],['#1a0800',0,5,13,1],['#1a0800',0,1,1,4],['#1a0800',12,1,1,4],['#8b5a28',1,2,11,3],['#a06830',2,2,9,1],['#c8843a',2,2,7,1],['#7a4a18',1,3,11,1],['#6a3a10',1,4,11,1],['#1a0800',0,0,4,7],['#8b5a28',1,1,2,5],['#a06830',1,2,2,3],['#c8843a',1,3,2,1],['#7a4818',5,2,2,1],['#7a4818',9,3,2,1]],
};
function drawFood(id, ox, oy, sc) { const d = FOOD[id]; if (d) spr(ox, oy, sc, d); }

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   WEENIE โ€” improved: taller hat, rounder head, bigger eyes
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function drawWeenie(ox, oy, fr, fl, bounce) {
  const bob = bounce > 0 ? -Math.round(Math.abs(Math.sin(bounce*0.35))*2) : 0;
  oy += bob;
  const bl = fl > 0 && (Math.floor(fl/4) & 1) === 0;
  const B  = bl ? '#ff3311' : '#c87028';
  const D  = bl ? '#cc2200' : '#8a4810';
  const T  = bl ? '#ff5522' : '#d88030';
  const K  = '#1a0800', W2 = '#ffeedd';
  const st = Math.round(Math.sin(fr*0.5)*1.5);
  spr(ox, oy, PS, [
    /* TALLER hat with wide brim */
    ['#fff', 1,-6, 8,1], ['#fff', 3,-13,4,8], ['#eee', 3,-13,4,1], ['#ddd', 4,-7, 2,1],
    [K, 1,-7, 8,1], [K, 3,-14,4,1], [K, 3,-7, 1,8], [K, 6,-7, 1,8],
    /* tail */
    [K,10,1,1,5],[K,13,0,1,6],[K,10,6,4,1],[T,11,1,2,5],[T,12,0,2,2],[D,11,4,2,2],
    /* body */
    [K,0,5,1,5],[K,9,5,1,4],[K,0,9,10,1],[K,1,5,9,1],[B,1,6,8,3],[W2,2,6,5,3],
    /* ROUNDER head โ€” chamfered corners */
    [K,2,-1,7,1],[K,1,0,1,1],[K,8,0,1,1],[K,0,1,1,4],[K,9,1,1,4],[K,1,5,1,1],[K,8,5,1,1],[K,2,6,7,1],
    [B,2,0,7,6],[B,1,1,8,4],
    /* BIGGER 2ร—2 eyes with shine */
    [K,2,1,3,3],['#fff',2,1,3,3],[K,2,2,2,2],['#fff',3,1,1,1],
    [K,6,1,3,3],['#fff',6,1,3,3],[K,6,2,2,2],['#fff',7,1,1,1],
    /* rosy cheeks */
    ['#c87040',0,3,2,2],['#c87040',8,3,2,2],
    /* nose + smile */
    [D,4,5,3,1],
    [K,2,6,2,1],[K,7,6,2,1],[K,3,7,5,1],[D,3,7,5,1],
    /* ears */
    [K,2,-2,2,3],[K,6,-2,2,3],[B,2,-1,1,2],[B,6,-1,1,2],['#e09050',2,-1,1,1],['#e09050',6,-1,1,1],
    /* legs */
    [K,2,9,2,2+st],[K,5,9,2,2-st],[B,2,9,2,1+st],[B,5,9,2,1-st],[D,2,10+st,2,1],[D,5,9-st,2,1],
  ]);
}

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   ANIMAL SPRITES
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function drawBunny(ox, oy, happy, sc) {
  sc = sc || PS;
  const K='#1a0800',BW='#e8e4d8',PK='#f0a8c0',EY=happy?'#ff3366':'#993344';
  spr(ox,oy,sc,[
    [K,2,-9,3,10],[BW,2,-8,3,9],[PK,3,-7,1,7],
    [K,6,-9,3,10],[BW,6,-8,3,9],[PK,7,-7,1,7],
    [K,0,0,10,1],[K,0,1,1,5],[K,9,1,1,5],[K,0,6,10,1],[BW,1,1,8,5],
    [K,2,2,2,2],[K,6,2,2,2],[EY,2,2,2,2],[EY,6,2,2,2],['#fff',2,2,1,1],['#fff',6,2,1,1],
    [PK,4,4,2,1],
    ...(happy?[[K,3,5,1,1],[K,6,5,1,1],[K,4,6,2,1],[PK,0,3,2,2],[PK,8,3,2,2]]:[[K,3,5,4,1]]),
    [K,1,6,8,1],[K,0,7,1,6],[K,9,7,1,6],[K,1,13,8,1],[BW,1,7,8,6],['#f8f4ea',3,8,4,3],
    [K,-1,8,2,3],[K,9,8,2,3],[BW,-1,8,1,3],[BW,9,8,1,3],
    ...(happy?[[BW,-1,7,1,1],[BW,9,7,1,1]]:[[BW,0,0,0,0]]),
    [K,1,13,3,2],[K,6,13,3,2],[BW,1,13,3,1],[BW,6,13,2,1],[K,9,9,2,3],[BW,9,10,2,1],
  ]);
}

function drawPig(ox, oy, happy, sc) {
  sc = sc || PS;
  const K='#1a0800',PI='#f0a0b8',PD='#d07888',PN='#a04060',LT='#ffc8d8';
  spr(ox,oy,sc,[
    [K,1,-3,3,3],[K,7,-3,3,3],[PD,2,-2,1,1],[PD,8,-2,1,1],
    [K,0,0,12,1],[K,0,1,1,6],[K,11,1,1,6],[K,0,7,12,1],[PI,1,1,10,6],
    [K,3,5,6,3],[PD,4,6,4,1],[PN,4,6,1,1],[PN,7,6,1,1],['#ffaaaa',4,5,2,1],
    [K,2,2,2,2],[K,7,2,2,2],['#1a0800',2,2,2,2],['#1a0800',7,2,2,2],['#fff',2,2,1,1],['#fff',7,2,1,1],
    ...(happy?[[PN,-1,4,2,1],[PN,9,4,2,1],[K,4,8,1,1],[K,7,8,1,1],[K,5,9,2,1]]:[[PI,0,0,0,0]]),
    [K,0,7,12,1],[K,0,8,1,7],[K,11,8,1,7],[K,0,15,12,1],[PI,1,8,10,7],[LT,2,9,7,5],
    [K,11,9,2,2],[K,12,8,2,3],[PD,11,10,1,1],[PN,12,9,1,1],
    [K,-1,9,2,4],[K,11,9,2,4],[PI,-1,9,1,4],[PI,11,9,1,4],
    ...(happy?[[PI,-1,8,1,1],[PI,11,8,1,1]]:[[PI,0,0,0,0]]),
    [K,2,15,3,3],[K,7,15,3,3],[PI,2,15,3,2],[PI,7,15,3,2],[PD,2,16,3,1],[PD,7,16,3,1],
  ]);
}

function drawFox(ox, oy, happy, sc) {
  sc = sc || PS;
  const K='#1a0800',FO='#cc5818',FD='#903808',FW='#f5dca0',FL='#f0c070';
  spr(ox,oy,sc,[
    [K,1,-6,3,7],[K,6,-6,3,7],[FO,2,-5,1,5],[FO,7,-5,1,5],[FW,2,-4,1,3],[FW,7,-4,1,3],
    [K,0,0,10,1],[K,0,1,1,5],[K,9,1,1,5],[K,0,6,10,1],[FO,1,1,8,5],
    [K,2,3,6,4],[FW,3,4,4,2],
    [K,1,2,2,2],[K,6,2,2,2],['#1a0800',1,2,2,2],['#1a0800',6,2,2,2],['#fff',1,2,1,1],['#fff',6,2,1,1],
    [K,5,5,2,1],[FD,5,5,2,1],
    ...(happy?[[FW,0,4,2,1],[FW,8,4,2,1]]:[[FO,0,0,0,0]]),
    [K,0,6,10,1],[K,0,7,1,7],[K,9,7,1,7],[K,0,14,10,1],[FO,1,7,8,7],[FW,2,9,6,4],
    [K,9,5,5,1],[K,9,6,1,8],[K,13,5,1,8],[K,9,13,5,1],[FO,10,6,3,7],[FL,10,6,3,3],[FW,10,9,2,3],
    [K,-1,8,2,4],[K,9,8,2,4],[FO,-1,8,1,4],[FO,9,8,1,4],
    ...(happy?[[FO,-1,7,1,1],[FO,9,7,1,1]]:[[FO,0,0,0,0]]),
    [K,2,14,2,3],[K,6,14,2,3],[FO,2,14,2,2],[FO,6,14,2,2],[FD,2,15,2,1],[FD,6,15,2,1],
  ]);
}

function drawWeenieFeast(ox, oy) {
  const K='#1a0800',B='#c87028',W2='#ffeedd';
  spr(ox,oy,PS,[
    [K,2,-5,5,1],[K,3,-9,3,1],[K,2,-5,1,5],[K,6,-5,1,5],[K,3,-9,1,5],[K,5,-9,1,5],
    ['#fff',3,-8,2,4],['#eee',3,-8,2,1],
    [K,0,-1,8,1],[K,0,0,1,5],[K,7,0,1,5],[K,0,4,8,1],[B,1,0,6,4],[W2,2,1,4,2],
    [K,1,4,7,1],[K,0,5,1,6],[K,7,5,1,6],[K,1,10,7,1],[B,1,5,6,5],[W2,2,6,4,3],
    [K,2,1,2,2],[K,5,1,2,2],['#fff',2,1,2,2],['#fff',5,1,2,2],[K,2,1,1,1],[K,5,1,1,1],
    [K,3,3,2,1],[K,3,4,1,1],[K,5,4,1,1],[K,4,5,1,1],
    [B,-2,2,2,4],[B,8,2,2,4],[B,2,10,2,3],[B,5,10,2,3],[K,2,12,2,1],[K,5,12,2,1],
  ]);
}

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   PIXEL STAR helper
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function drawStar(x, y, col) {
  cx.fillStyle = col || '#ffe066';
  [[2,0,2,1],[1,1,4,1],[0,2,6,1],[1,3,4,1],[2,4,2,1],[0,1,1,1],[5,1,1,1],[0,3,1,1],[5,3,1,1]]
    .forEach(([dx,dy,w,h]) => cx.fillRect(x+dx*2, y+dy*2, w*2, h*2));
}

/* pixel sun */
function drawSun(x, y) {
  cx.fillStyle = '#ffe066';
  [[2,0,4,1],[1,1,6,1],[0,2,8,1],[0,3,8,1],[0,4,8,1],[0,5,8,1],[1,6,6,1],[2,7,4,1]].forEach(([dx,dy,w,h])=>cx.fillRect(x+dx*2,y+dy*2,w*2,h*2));
  cx.fillStyle = '#fff8c0';
  cx.fillRect(x+4*2,y+2*2,4*2,4*2);
}

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   BACKGROUND โ€” sky + sun + clouds + trees + ground
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function drawBG() {
  /* sky โ€” shifts slightly darker with phase */
  const skies = ['#5ba8d8','#4a90c0','#3a7ab0'];
  cx.fillStyle = skies[G.phase]; cx.fillRect(0, HUD2, W, GND-HUD2);

  /* pixel sun top-right โ€” subtle */
  drawSun(W-74, HUD2+8);

  /* chunky Mario-style clouds */
  G.clouds.forEach(cl => {
    cx.fillStyle = '#d6ecf8';
    cx.fillRect(cl.x,    cl.y+8,  60, 16);
    cx.fillRect(cl.x+8,  cl.y,    44, 10);
    cx.fillRect(cl.x+16, cl.y-6,  26,  8);
    cx.fillStyle = '#c0dff0'; cx.fillRect(cl.x, cl.y+20, 60, 4);
    cl.x -= G.spd*0.2; if (cl.x < -70) cl.x = W+40;
  });

  /* round pixel trees โ€” drawn in front of sky, behind ground */
  G.trees.forEach(tr => {
    const tx=tr.x, ty=GND-62;
    cx.fillStyle='#7a4a18'; cx.fillRect(tx+10, ty+46, 10, 20);
    cx.fillStyle='#3a8a18';
    cx.fillRect(tx+4, ty+30, 22, 18); cx.fillRect(tx, ty+36, 30, 14); cx.fillRect(tx+8, ty+22, 14, 12);
    cx.fillStyle='#4aaa28';
    cx.fillRect(tx+6, ty+28, 18, 14); cx.fillRect(tx+2, ty+34, 26, 12); cx.fillRect(tx+10, ty+22, 10, 10);
    cx.fillStyle='#5aca38'; cx.fillRect(tx+8, ty+24, 6, 6); cx.fillRect(tx+4, ty+34, 6, 4);
    tr.x -= G.spd*0.4; if (tr.x < -44) tr.x = W+44;
  });

  /* ground */
  cx.fillStyle = '#4a7a1a'; cx.fillRect(0, GND, W, H-GND);
  cx.fillStyle = '#5aaa28'; cx.fillRect(0, GND, W, 8);
  cx.fillStyle = '#6aca38'; cx.fillRect(0, GND, W, 4);
  /* scrolling grass dashes */
  G.gOff = (G.gOff + G.spd) % 40;
  cx.fillStyle = '#3a6a0a';
  for (let gx = -G.gOff; gx < W; gx += 40) cx.fillRect(gx, GND+2, 20, 4);
  /* soil layers */
  cx.fillStyle = '#8b5a28'; cx.fillRect(0, GND+8, W, 14);
  cx.fillStyle = '#7a4a18'; cx.fillRect(0, GND+20, W, H-(GND+20));
}

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   HUD
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function drawHUD() {
  cx.fillStyle = '#120800'; cx.fillRect(0, 0, W, HUD2);
  cx.fillStyle = '#5a3a00'; cx.fillRect(0, HUD2-2, W, 2);
  cx.fillStyle = '#ffe066'; cx.font = "bold 13px 'Courier New'";
  cx.textAlign = 'left'; cx.textBaseline = 'middle';
  cx.fillText("WEENIE'S RUN!", 10, HUD2/2);
  for (let i = 0; i < 3; i++) {
    cx.fillStyle = i < (3-G.hits) ? '#ff4444' : '#442222';
    cx.fillText('โ™ฅ', W-14-i*18, HUD2/2);
  }
  /* basket โ€” pixel food icons */
  const sw=24,sh=24,gap=3,tot=6*(sw+gap)-gap;
  const bx=Math.round((W-tot)/2), by=Math.round(HUD2/2-sh/2);
  for (let i=0; i<6; i++) {
    const sx = bx+i*(sw+gap);
    cx.fillStyle = G.basket[i] ? '#3d2800' : '#1a0f00'; cx.fillRect(sx,by,sw,sh);
    cx.strokeStyle = G.basket[i] ? '#ffb347' : '#4a2800'; cx.lineWidth=1.5; cx.strokeRect(sx,by,sw,sh);
    if (G.basket[i]) drawFood(G.basket[i], sx+2, by+2, 2);
  }
}

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   HUNGER METERS โ€” compact strip, characters at scale 2
   Characters anchored top-left at py+14 so full sprite visible
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function drawMeters() {
  cx.fillStyle = '#0a0500'; cx.fillRect(0, METY, W, METH);
  cx.fillStyle = '#3a2000'; cx.fillRect(0, METY, W, 2);
  const pw = Math.floor(W/3), ph = METH - 4;

  G.friends.forEach((f, i) => {
    const px = i*pw + 2, py = METY + 3;
    const crit = !f.happy && f.hunger > 0.72;

    /* panel bg + border */
    cx.fillStyle = f.happy ? '#0a2a0a' : crit ? '#260606' : '#130700';
    cx.fillRect(px, py, pw-4, ph);
    cx.strokeStyle = f.happy ? '#4caf50'
                   : crit    ? `rgba(255,60,30,${0.6+Math.sin(G.fc*0.15)*0.4})`
                   :           '#3a1800';
    cx.lineWidth = 1.5; cx.strokeRect(px, py, pw-4, ph);

    /* name */
    cx.fillStyle = f.happy ? '#4caf50' : crit ? '#ff7755' : '#ffb347';
    cx.font = "bold 9px 'Courier New'";
    cx.textAlign = 'left'; cx.textBaseline = 'top';
    cx.fillText(f.name.toUpperCase(), px + 8, py + 5);

    /* food icon row โ€” centred in panel */
    const allItems = f.want2
      ? [...Array(f.need).fill(f.want), ...Array(f.need2).fill(f.want2)]
      : Array(f.need).fill(f.want);
    const totalGot = f.got + f.got2;
    const iconSc = 2, iconW = 10*iconSc, iconGap = 5;
    const rowW = allItems.length*(iconW+iconGap) - iconGap;
    const startX = px + Math.floor((pw-4-rowW)/2);
    const iconY = py + 18;
    allItems.forEach((id, idx) => {
      const ix = startX + idx*(iconW+iconGap);
      const collected = idx < totalGot;
      cx.globalAlpha = collected ? 1.0 : 0.25;
      drawFood(id, ix, iconY, iconSc);
      cx.globalAlpha = 1;
      if (collected) {
        cx.fillStyle = '#4caf50'; cx.font = "bold 8px 'Courier New'";
        cx.textAlign = 'center'; cx.textBaseline = 'top';
        cx.fillText('\u2713', ix + iconW/2, iconY + iconW + 1);
      }
    });

    /* hunger bar */
    const bx2 = px + 8, by2 = py + ph - 12, bw = pw - 16, bh = 6;
    cx.fillStyle = '#0a0300'; cx.fillRect(bx2, by2, bw, bh);
    cx.strokeStyle = '#3a1800'; cx.lineWidth = 1; cx.strokeRect(bx2, by2, bw, bh);
    if (f.happy) {
      cx.fillStyle = '#4caf50'; cx.fillRect(bx2+1, by2+1, bw-2, bh-2);
    } else {
      const h = Math.max(0, Math.min(1, f.hunger));
      const fill = (bw-2)*h;
      const gEnd = Math.min(fill, (bw-2)*0.45);
      if (gEnd > 0) { cx.fillStyle='#44cc44'; cx.fillRect(bx2+1, by2+1, gEnd, bh-2); }
      const yS=(bw-2)*0.45, yE=Math.min(fill,(bw-2)*0.72);
      if (yE>yS) { cx.fillStyle='#ffcc00'; cx.fillRect(bx2+1+yS, by2+1, yE-yS, bh-2); }
      const rS=(bw-2)*0.72;
      if (fill>rS) { cx.fillStyle='#ff4422'; cx.fillRect(bx2+1+rS, by2+1, fill-rS, bh-2); }
    }
    cx.fillStyle = crit ? '#ff7755' : '#554433';
    cx.font = "6px 'Courier New'"; cx.textAlign='left'; cx.textBaseline='bottom';
    cx.fillText('HUNGER', bx2, by2-1);
  });
}

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   BUTTON (canvas-drawn โ€” no HTML, never hidden by CSS)
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function mkBtn(label, x, y, w, h, bg, hi, tag, sm) {
  cx.fillStyle = bg; cx.fillRect(x, y, w, h);
  cx.fillStyle = hi; cx.fillRect(x, y, w, 3);
  cx.fillStyle = 'rgba(0,0,0,0.4)'; cx.fillRect(x, y+h-3, w, 3);
  cx.fillStyle = '#fff'; cx.font = `bold ${sm?10:13}px 'Courier New'`;
  cx.textAlign='center'; cx.textBaseline='middle'; cx.fillText(label, x+w/2, y+h/2);
  return { x, y, w, h, tag: tag||'' };
}

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   INTRO / DEAD / FEAST SCREENS
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function drawIntroScreen() {
  /* solid dark overlay โ€” full play area, no background bleeding through */
  cx.fillStyle = '#0d0700';
  cx.fillRect(0, HUD2, W, GND - HUD2);

  const L = HUD2; /* top of play area */
  const lineH = 22;
  let y = L + 28;

  /* Title */
  cx.fillStyle = '#ffe066';
  cx.font = "bold 22px 'Courier New'";
  cx.textAlign = 'center';
  cx.textBaseline = 'top';
  cx.fillText("WEENIE'S GREAT FEAST!", W/2, y);
  y += 36;

  /* Divider */
  cx.fillStyle = '#3a2000';
  cx.fillRect(40, y, W-80, 1);
  y += 10;

  /* Who wants what */
  cx.fillStyle = '#ffb347';
  cx.font = "12px 'Courier New'";
  cx.fillText('Bunny needs: 3 carrots', W/2, y);  y += lineH;
  cx.fillText('Pig needs: 3 honey',     W/2, y);  y += lineH;
  cx.fillText('Fox needs: 2 mushrooms + 1 bread', W/2, y); y += lineH + 8;

  /* Divider */
  cx.fillStyle = '#3a2000';
  cx.fillRect(40, y, W-80, 1);
  y += 10;

  /* Rules */
  cx.fillStyle = '#886644';
  cx.font = "11px 'Courier New'";
  cx.fillText('Dodge rocks and logs', W/2, y); y += lineH - 4;
  cx.fillText('Get hit = drop all your food', W/2, y); y += lineH - 4;
  cx.fillText('3 hits = game over', W/2, y);

  /* Button โ€” anchored to bottom of play area with fixed gap */
  const bw = 220, bh = 36, bx = (W-bw)/2, by = GND - bh - 10;
  G.btn1 = mkBtn('โ–ถ  START RUNNING!', bx, by, bw, bh, '#e8623a', '#ff8855', 'start');
  G.btn2 = null;
}

function drawDeadScreen() {
  cx.fillStyle='rgba(10,5,0,0.91)'; cx.fillRect(0,HUD2,W,GND-HUD2);
  cx.fillStyle='#ff4422'; cx.font="bold 22px 'Courier New'";
  cx.textAlign='center'; cx.textBaseline='middle';
  cx.fillText('WEENIE TRIPPED!', W/2, HUD2+(GND-HUD2)*0.3);
  cx.fillStyle='#ffb347'; cx.font="13px 'Courier New'";
  cx.fillText("Her friends are still hungry...", W/2, HUD2+(GND-HUD2)*0.52);
  const bw=200,bh=34,bx=(W-bw)/2,by=HUD2+(GND-HUD2)*0.7;
  G.btn1 = mkBtn('โ–ถ  TRY AGAIN!', bx, by, bw, bh, '#e8623a','#ff8855','start');
  G.btn2 = null;
}

function drawFeastBG() {
  cx.fillStyle='#1c3010'; cx.fillRect(0,HUD2,W,GND-HUD2);
  cx.fillStyle='#2a5018'; cx.fillRect(0,GND-8,W,H-(GND-8));
  cx.fillStyle='#1a3010'; cx.fillRect(0,GND-8,W,4);
  /* pixel stars โ€” not text chars */
  const starPos = [[48,50],[130,42],[252,53],[392,40],[514,50],[622,44]];
  starPos.forEach(([x,y]) => drawStar(x, y));
  /* table */
  const tx=140,ty=GND-38,tw=400,th=14;
  cx.fillStyle='#8b5a2b'; cx.fillRect(tx,ty,tw,th);
  cx.fillStyle='#6b3a1b'; cx.fillRect(tx,ty,tw,4);
  cx.fillStyle='#7b4a22'; cx.fillRect(tx+14,ty+th,12,24); cx.fillRect(tx+tw-26,ty+th,12,24);
  INGR_IDS.slice(0,5).forEach((id,i) => drawFood(id, tx+18+i*72, ty-28, 4));
}

function drawFeastChars(t) {
  [{ fn:(x,y)=>drawBunny(x,y,true,PS), tx:44,  ty:GND-42 },
   { fn:(x,y)=>drawWeenieFeast(x,y),   tx:192, ty:GND-38 },
   { fn:(x,y)=>drawPig(x,y,true,PS),   tx:364, ty:GND-42 },
   { fn:(x,y)=>drawFox(x,y,true,PS),   tx:530, ty:GND-44 }]
  .forEach((ch,i) => {
    const p = Math.max(0, Math.min(1, t-i*0.8)); if (!p) return;
    const e = 1-Math.pow(1-p,3);
    ch.fn(Math.round(ch.tx+90*(1-e)), Math.round(ch.ty+(p<1?-Math.sin(p*Math.PI)*12:0)));
  });
}

function drawWonUI() {
  cx.fillStyle='rgba(10,5,0,0.82)'; cx.fillRect(0,HUD2,W,70);
  cx.fillStyle='#ffe066'; cx.font="bold 18px 'Courier New'";
  cx.textAlign='center'; cx.textBaseline='middle';
  cx.fillText('๐ŸŽ‰  WEENIE DID IT!  ๐ŸŽ‰', W/2, HUD2+18);
  cx.fillStyle='#ffd070'; cx.font="11px 'Courier New'";
  cx.fillText('Creative, caring, and a GREAT cook!', W/2, HUD2+36);
  const bw=290,bh=32,bx=(W-bw)/2,by=HUD2+52;
  G.btn1 = mkBtn('โญ  VOTE WEENIE FOR CLASS MASCOT!  โญ', bx, by, bw, bh, '#2a7a30','#4cba52','vote');
  const b2w=130,b2h=24,b2x=(W-b2w)/2,b2y=by+bh+5;
  cx.fillStyle='#1a0f00'; cx.fillRect(b2x,b2y,b2w,b2h);
  cx.strokeStyle='#6a4a20'; cx.lineWidth=1.5; cx.strokeRect(b2x,b2y,b2w,b2h);
  cx.fillStyle='#ffb347'; cx.font="bold 10px 'Courier New'";
  cx.textAlign='center'; cx.textBaseline='middle';
  cx.fillText('PLAY AGAIN', b2x+b2w/2, b2y+b2h/2);
  G.btn2 = { x:b2x, y:b2y, w:b2w, h:b2h, tag:'replay' };
}

function drawVotedUI() {
  cx.fillStyle='rgba(10,5,0,0.88)'; cx.fillRect(0,HUD2,W,88);
  cx.fillStyle='#a5d6a7'; cx.font="bold 20px 'Courier New'";
  cx.textAlign='center'; cx.textBaseline='middle';
  cx.fillText('๐Ÿ†  WEENIE WINS!  ๐Ÿ†', W/2, HUD2+20);
  cx.fillStyle='#c8e6c9'; cx.font="11px 'Courier New'";
  cx.fillText('Creative ยท Caring ยท A GREAT cook', W/2, HUD2+40);
  cx.fillStyle='#ffe066'; cx.font="bold 12px 'Courier New'";
  cx.fillText('VOTE WEENIE FOR CLASS MASCOT!', W/2, HUD2+58);
  const bw=130,bh=24,bx=(W-bw)/2,by=HUD2+72;
  cx.fillStyle='#1a0f00'; cx.fillRect(bx,by,bw,bh);
  cx.strokeStyle='#6a4a20'; cx.lineWidth=1.5; cx.strokeRect(bx,by,bw,bh);
  cx.fillStyle='#ffb347'; cx.font="bold 10px 'Courier New'";
  cx.textAlign='center'; cx.textBaseline='middle'; cx.fillText('PLAY AGAIN',bx+bw/2,by+bh/2);
  G.btn1 = {x:bx,y:by,w:bw,h:bh,tag:'replay'}; G.btn2=null;
}

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   EFFECTS
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function spawnSparks(px,py,good) {
  for(let i=0;i<8;i++) G.sparks.push({x:px,y:py,vx:(Math.random()-.5)*5,vy:-Math.random()*5-2,life:40,e:good?['โœจ','โญ','๐Ÿ’ซ'][i%3]:['๐Ÿ’ฅ','๐Ÿ’ข'][i%2]});
}
function spawnPop(t,x,y,c) { G.popups.push({t,x,y,life:55,maxLife:55,c:c||'#ffe066'}); }
function spawnFly(id,fx,fy) {
  const idx=G.basket.length-1, sw=24,gap=3,tot=6*(sw+gap)-gap, bx=Math.round((W-tot)/2);
  G.fly.push({id,x:fx,y:fy,tx:bx+idx*(sw+gap)+sw/2,ty:HUD2/2,life:22,max:22});
}
function drawSparks() {
  G.sparks = G.sparks.filter(p=>p.life>0);
  G.sparks.forEach(p=>{p.x+=p.vx;p.y+=p.vy;p.vy+=0.25;p.life--;cx.globalAlpha=p.life/40;cx.font='12px serif';cx.textAlign='left';cx.fillText(p.e,p.x,p.y);cx.globalAlpha=1;});
}
function drawPopups() {
  G.popups = G.popups.filter(p=>p.life>0);
  G.popups.forEach(p=>{
    const age=p.maxLife-p.life, a=age<8?age/8:p.life<12?p.life/12:1, rise=Math.min(age*0.55,28);
    cx.globalAlpha=a;
    cx.fillStyle='#2a1400'; cx.font="bold 15px 'Courier New'"; cx.textAlign='center'; cx.textBaseline='middle';
    cx.fillText(p.t,p.x+1,p.y-rise+1); cx.fillStyle=p.c; cx.fillText(p.t,p.x,p.y-rise);
    cx.globalAlpha=1; p.life--;
  });
}
function drawFly() {
  G.fly = G.fly.filter(f=>f.life>0);
  G.fly.forEach(f=>{
    const t=1-f.life/f.max, e=1-Math.pow(1-t,2);
    const cx2=f.x+(f.tx-f.x)*e, cy=f.y+(f.ty-f.y)*e-Math.sin(t*Math.PI)*28;
    cx.save(); cx.translate(cx2,cy); cx.scale(1-t*0.4,1-t*0.4); cx.globalAlpha=f.life/f.max;
    drawFood(f.id,-4,-4,2); cx.restore(); cx.globalAlpha=1; f.life--;
  });
}
function drawPhaseMsg() {
  if (!pmsg || pmsg.life<=0) return;
  const a=Math.min(1,pmsg.life/20), mid=HUD2+(GND-HUD2)/2;
  cx.globalAlpha=a; cx.fillStyle='#1a0f00'; cx.fillRect(W/2-165,mid-13,330,24);
  cx.strokeStyle='#ffe066'; cx.lineWidth=1.5; cx.strokeRect(W/2-165,mid-13,330,24);
  cx.fillStyle='#ffe066'; cx.font="bold 12px 'Courier New'";
  cx.textAlign='center'; cx.textBaseline='middle'; cx.fillText(pmsg.t,W/2,mid);
  cx.globalAlpha=1; pmsg.life--;
}

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   SPAWNING
   Food heights: mostly BANDS_JUMP (must jump), occasionally ground
   Never 3+ ground items in a row
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function pickBand() {
  /* 15% chance of ground-level food โ€” but not if 2 already in a row */
  if (groundFoodCount < 2 && Math.random() < 0.15) {
    groundFoodCount++;
    return BAND_GROUND;
  }
  groundFoodCount = 0;
  const r = Math.random();
  if (r < 0.25) return BANDS_JUMP[0]; /* low jump */
  if (r < 0.55) return BANDS_JUMP[1]; /* mid jump */
  if (r < 0.80) return BANDS_JUMP[2]; /* high jump */
  return BANDS_JUMP[3];               /* double jump */
}

function spawnItem(forceObs) {
  const obsChance = G.phase===0 ? 0.24 : G.phase===1 ? 0.34 : 0.44;
  const tooSoon = (W - lastObsX) < MIN_OBS_GAP;
  const isObs = !tooSoon && (forceObs===true || Math.random()<obsChance);
  let id;
  if (isObs) {
    id = OBS_IDS[Math.floor(Math.random()*2)];
    lastObsX = W + 30;
  } else {
    /* build pool of still-needed food only */
    const stillNeeded = [];
    G.friends.forEach(f => {
      if (f.happy) return;
      if (f.got  < f.need)  stillNeeded.push(f.want);
      if (f.want2 && f.got2 < f.need2) stillNeeded.push(f.want2);
    });
    /* phase 1+: bias 60% toward needed foods */
    if (stillNeeded.length && (G.phase >= 1 || Math.random() < 0.5)) {
      id = stillNeeded[Math.floor(Math.random()*stillNeeded.length)];
    }
    /* fallback: any ingredient */
    if (!id) {
      const pool = stillNeeded.length ? stillNeeded : INGR_IDS;
      id = pool[Math.floor(Math.random()*pool.length)];
    }
  }
  const isO = OBS_IDS.includes(id);
  G.items.push({ x:W+30, y:isO?GND+2:pickBand(), id, isObs:isO, bob:Math.random()*Math.PI*2, col:false });
}

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   CHECK WIN โ€” all friends fed
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function checkWin() {
  return G.friends.every(f => f.happy);
}

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   COLLECT FOOD โ€” update per-friend progress
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function collectFood(id) {
  /* only count this item if someone still needs it */
  let counted = false;
  G.friends.forEach(f => {
    if (f.happy) return;
    if (id === f.want && f.got < f.need) {
      f.got++; counted = true;
      if (f.got >= f.need && (!f.want2 || f.got2 >= f.need2)) {
        f.happy = true;
        spawnPop(f.name + ' is happy! \u{1F389}', W/2, GND-65, '#4caf50');
      }
    } else if (f.want2 && id === f.want2 && f.got2 < f.need2) {
      f.got2++; counted = true;
      if (f.got >= f.need && f.got2 >= f.need2) {
        f.happy = true;
        spawnPop(f.name + ' is happy! \u{1F389}', W/2, GND-65, '#4caf50');
      }
    }
  });
  /* only add to basket display if it counted */
  if (counted) G.basket.push(id);
  return counted;
}

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   WIN SEQUENCE โ€” 3 phases before feast screen
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function winSequenceLoop() {
  if (G.state !== 'winning') return;
  cx.clearRect(0,0,W,H); wSeqT++;
  if (winPhase === 0) {
    drawBG();
    if (G.wx < W/2-20) G.wx += 3;
    if (G.fc%6===0) G.wfr++;
    drawWeenie(G.wx-15, GND-44, G.wfr, 0, 0);
    drawMeters(); drawHUD();
    if (wSeqT===10)  spawnPop('AMAZING!',        W/2, GND-75, '#ffe066');
    if (wSeqT===24)  spawnPop('FEAST TIME!',      W/2, GND-55, '#ffd060');
    drawPopups(); drawSparks();
    if (wSeqT%8===0) for(let j=0;j<3;j++) spawnSparks(Math.random()*W, HUD2+Math.random()*(GND-HUD2), true);
    if (wSeqT > 60) { winPhase=1; wSeqT=0; }
  } else if (winPhase === 1) {
    drawBG(); drawWeenie(G.wx-15, GND-44, G.wfr, 0, 20); drawMeters(); drawHUD();
    const fa = wSeqT<14 ? wSeqT/14 : wSeqT<50 ? 1-(wSeqT-14)/36 : 0;
    cx.fillStyle=`rgba(255,248,200,${fa*0.85})`; cx.fillRect(0,HUD2,W,GND-HUD2);
    drawPopups();
    if (wSeqT > 60) { winPhase=2; wSeqT=0; }
  } else if (winPhase === 2) {
    drawBG(); drawWeenie(G.wx-15, GND-44, G.wfr, 0, 0);
    const a = Math.min(1, wSeqT/70);
    cx.globalAlpha=a; drawFeastBG(); cx.globalAlpha=1;
    drawMeters(); drawHUD();
    if (wSeqT > 78) { G.state='won'; G.endT=0; return; }
  }
  raf = requestAnimationFrame(winSequenceLoop);
}

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   WIN ANIMATION LOOP
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function winLoop() {
  if (G.state !== 'won') return;
  cx.clearRect(0,0,W,H); G.endT += 0.022;
  drawFeastBG(); drawFeastChars(G.endT); drawWonUI(); drawMeters(); drawHUD();
  if (G.endT < 4) winRaf = requestAnimationFrame(winLoop);
}

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   MAIN GAME LOOP
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function loop() {
  if (G.state !== 'playing') return;
  cx.clearRect(0,0,W,H); G.fc++;

  /* phase transitions */
  if (G.phase===0 && G.fc===220) { G.phase=1; pmsg={t:'FIND THEIR FAVOURITE FOOD!',life:80}; }
  if (G.phase===1 && G.fc===460) { G.phase=2; pmsg={t:'FASTER! ALMOST THERE!',life:80}; }

  /* hunger โ€” green for most of game, red only after ~36s */
  G.friends.forEach(f => { if (!f.happy) f.hunger = Math.min(1, G.fc/1440); });

  /* speed */
  const baseTgt = [2.4,3.0,3.8][G.phase];
  const hMax = Math.max(...G.friends.map(f=>f.happy?0:f.hunger));
  const hungerBoost = hMax>0.72 ? (hMax-0.72)/0.28*1.2 : 0;
  G.spd += (baseTgt+hungerBoost-G.spd)*0.01;

  drawBG();

  /* Weenie physics */
  G.wvy += GRAV; G.wy += G.wvy;
  if (G.wy >= GND) { G.wy=GND; G.wvy=0; G.wjumps=0; }
  if (G.wy < SKY_TOP) { G.wy=SKY_TOP; G.wvy=0; }
  if (G.fc%6===0) G.wfr++;
  if (G.wflash>0) G.wflash--;
  if (G.wbounce>0) G.wbounce--;

  /* early rock at ~1s */
  if (!G.earlyRock && G.fc===80) { G.earlyRock=true; spawnItem(true); }

  /* regular spawn */
  const si = Math.max(46, 88-G.fc*0.034) | 0;
  if (G.fc%si===0) spawnItem();

  /* items โ€” update lastObsX as obstacles scroll */
  const FS = PS*10;
  for (let i=G.items.length-1; i>=0; i--) {
    const it = G.items[i];
    it.x -= G.spd;
    if (it.isObs) lastObsX = Math.min(lastObsX, it.x); /* track leftmost obstacle */
    if (!it.isObs) it.y += Math.sin(G.fc*0.08+it.bob)*0.3;
    if (!it.col) drawFood(it.id, it.x, it.y-FS, PS);

    const wL=G.wx-10, wR=G.wx+10, wT=G.wy-44, wB=G.wy;
    const iL=it.x, iR=it.x+FS, iT=it.y-FS, iB=it.y+4;
    if (!it.col && wL<iR && wR>iL && wT<iB && wB>iT) {
      it.col = true;
      if (it.isObs) {
        G.hits++;
        if (G.basket.length) {
          spawnSparks(G.wx, G.wy-22, false);
          G.basket = [];
          G.friends.forEach(f => { if (!f.happy) { f.got=0; f.got2=0; } });
        }
        G.wflash=40; spawnPop('OUCH!', G.wx, G.wy-32, '#ff4422');
        G.items.splice(i,1);
        if (G.hits >= 3) { G.state='dead'; return; }
        continue;
      } else {
        if (G.basket.length < 9) { /* max 9 slots in display */
          const counted = collectFood(it.id);
          if (counted) {
            spawnFly(it.id, it.x+FS/2, it.y-FS/2);
            G.wbounce=18;
            spawnSparks(it.x+FS/2, it.y-FS/2, true);
            spawnPop(WORDS[Math.floor(Math.random()*WORDS.length)], it.x+FS/2, it.y-FS-8, '#ffe066');
          }
          G.items.splice(i,1);
          if (checkWin()) { G.state='winning'; winPhase=0; wSeqT=0; return; }
          continue;
        }
      }
    }
    if (it.x < -40) G.items.splice(i,1);
  }

  drawWeenie(G.wx-15, G.wy-44, G.wfr, G.wflash, G.wbounce);
  drawSparks(); drawFly(); drawPhaseMsg(); drawPopups();
  drawMeters(); drawHUD();
  raf = requestAnimationFrame(loop);
}

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   STATIC SCREEN RENDER
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function renderStatic() {
  cx.clearRect(0,0,W,H);
  if (G.state==='won')   { drawFeastBG(); drawFeastChars(4); drawWonUI();   drawMeters(); drawHUD(); return; }
  if (G.state==='voted') { drawFeastBG(); drawFeastChars(4); drawVotedUI(); drawMeters(); drawHUD(); return; }
  drawBG(); drawMeters(); drawHUD();
  if (G.state==='start') drawIntroScreen();
  if (G.state==='dead')  drawDeadScreen();
}

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
   INPUT
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
function jump() {
  if (G.state !== 'playing') return;
  if (G.wjumps < 2) { G.wvy = G.wjumps===0 ? J1 : J2; G.wjumps++; }
}

function handleClick(e) {
  const r=cv.getBoundingClientRect(), sc=W/r.width;
  const px2=(e.clientX-r.left)*sc, py=(e.clientY-r.top)*sc;
  if (G.state==='playing')  { jump(); return; }
  if (G.state==='winning')  { return; }
  const hit = b => b && px2>=b.x && px2<=b.x+b.w && py>=b.y && py<=b.y+b.h;
  if (hit(G.btn1)) {
    if (G.btn1.tag==='vote') { G.state='voted'; renderStatic(); }
    else { resetG(); startGame(); }
    return;
  }
  if (hit(G.btn2)) { resetG(); startGame(); }
}

function startGame() {
  if (raf)    { cancelAnimationFrame(raf);    raf=null; }
  if (winRaf) { cancelAnimationFrame(winRaf); winRaf=null; }
  G.state='playing'; loop();
}

cv.addEventListener('click', handleClick);
cv.addEventListener('touchstart', e=>{ e.preventDefault(); handleClick(e.touches[0]); }, {passive:false});
document.addEventListener('keydown', e => {
  if (e.code==='Space' || e.code==='ArrowUp') { e.preventDefault(); jump(); }
});

/* State change watcher */
let prevState = '';
setInterval(() => {
  const s = G.state;
  if (s !== prevState) {
    prevState = s;
    if (s==='winning') { if(raf)cancelAnimationFrame(raf); raf=null; wSeqT=0; winSequenceLoop(); }
    else if (s==='won') { if(winRaf)cancelAnimationFrame(winRaf); winRaf=null; winLoop(); }
    else if (s!=='playing') renderStatic();
  }
}, 50);

/* Initial render */
renderStatic();
</script>
</body>
</html>

Game Source: Weenie's Great Feast!

Creator: GlitchGecko62

Libraries: none

Complexity: complex (857 lines, 40.2 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: weenie-s-great-feast-glitchgecko62" to link back to the original. Then publish at arcadelab.ai/publish.