Weenie's Great Feast!
by GlitchGecko62857 lines40.2 KB
<!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.