Csdl / index.html
gaur3009's picture
Update index.html
04e2228 verified
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Sling Humans β€” Prototype</title>
<style>
html,body{height:100%;margin:0;background:linear-gradient(#bfe9ff,#eaf7ff);font-family:Inter,system-ui,Segoe UI,Arial}
#game {width:100%;height:100vh;overflow:hidden}
.ui {position:absolute;left:14px;top:14px;background:rgba(255,255,255,0.9);padding:10px;border-radius:10px;box-shadow:0 6px 18px rgba(13,38,76,0.08);z-index:10}
.ui button{background:#1976d2;color:white;border:none;padding:8px 12px;border-radius:8px;cursor:pointer}
.ui .small{font-size:13px;color:#0b2540;margin-top:6px}
.credits{position:absolute;right:14px;top:14px;background:rgba(255,255,255,0.9);padding:8px 10px;border-radius:8px;font-size:13px;z-index:10}
</style>
</head>
<body>
<div id="game"></div>
<div class="ui">
<div style="font-weight:700">Sling Humans β€” Prototype</div>
<div style="margin-top:8px;display:flex;gap:8px">
<button id="restart">Restart</button>
<button id="next">Next Level</button>
<button id="powerup">Mega Launch</button>
</div>
<div class="small">Score: <span id="score">0</span> β€’ Ammo: <span id="ammo">5</span> β€’ Stars: <span id="stars">0</span></div>
<div style="margin-top:8px;font-size:13px;color:#444">Human: <select id="hType">
<option value="regular">Regular</option>
<option value="heavy">Heavyweight</option>
<option value="jumper">Jumper</option>
<option value="boomer">Boomer</option>
</select></div>
</div>
<div class="credits">Cartoon style β€” non graphic</div>
<!-- Phaser 3 CDN -->
<script src="https://cdn.jsdelivr.net/npm/phaser@3.55.2/dist/phaser.min.js"></script>
<script>
/*
Sling Humans β€” Prototype
- Single-file Phaser 3 game
- Uses emoji + basic shapes
- Extendable: add assets, audio, more levels
*/
const WIDTH = Math.min(window.innerWidth, 1200);
const HEIGHT = Math.min(window.innerHeight, 800);
const config = {
type: Phaser.AUTO,
parent: 'game',
width: WIDTH,
height: HEIGHT,
backgroundColor: 0xe8f7ff,
physics: { default: 'matter', matter: { gravity: { y: 1.0 }, debug: false } },
scene: { preload, create, update }
};
const game = new Phaser.Game(config);
let slingPos = { x: 160, y: HEIGHT - 180 };
let currentHuman = null, isDragging=false;
let dottedLines = [];
let score = 0, ammo = 5, level = 0, stars = 0;
let blocksGroup = [], targetsGroup = [];
let megaLaunchActive = false;
function preload() {
// no external assets; can load sprites/sounds here later
}
function create() {
const scene = this;
// ground
const ground = scene.matter.add.rectangle(WIDTH/2, HEIGHT-24, WIDTH, 48, { isStatic: true, label: 'ground' });
// catapult base visuals
scene.add.circle(slingPos.x, slingPos.y, 20, 0x6d4c41).setDepth(2);
scene.add.rectangle(slingPos.x+28, slingPos.y+12, 10, 40, 0x4e342e).setOrigin(0.5).setDepth(2);
// UI hooks
document.getElementById('restart').onclick = () => resetLevel(scene);
document.getElementById('next').onclick = () => nextLevel(scene);
document.getElementById('powerup').onclick = () => { megaLaunchActive = true; document.getElementById('powerup').innerText='Mega! (On)'; setTimeout(()=>{megaLaunchActive=false; document.getElementById('powerup').innerText='Mega Launch';}, 8000); };
// level build
buildLevel(scene, level);
// spawn human
spawnHuman(scene);
// input handlers
scene.input.on('pointerdown', (pointer) => {
if (!currentHuman) return;
const bodies = Phaser.Physics.Matter.Matter.Query.point([currentHuman.body], pointer);
if (bodies.length) {
isDragging = true;
scene.matter.body.setStatic(currentHuman.body, true); // hold it while dragging
}
});
scene.input.on('pointermove', (pointer) => {
if (!isDragging || !currentHuman) return;
// clamp drag radius
const maxDrag = 250;
const dx = pointer.x - slingPos.x;
const dy = pointer.y - slingPos.y;
const dist = Math.min(Math.hypot(dx,dy), maxDrag);
const angle = Math.atan2(dy,dx);
const px = slingPos.x + Math.cos(angle)*dist;
const py = slingPos.y + Math.sin(angle)*dist;
currentHuman.setPosition(px, py);
drawTrajectory(scene, currentHuman.x, currentHuman.y, slingPos.x - px, slingPos.y - py);
});
scene.input.on('pointerup', (pointer) => {
if (!isDragging || !currentHuman) return;
isDragging = false;
clearTrajectory();
// compute launch velocity
const dx = slingPos.x - currentHuman.x;
const dy = slingPos.y - currentHuman.y;
let power = Math.sqrt(dx*dx + dy*dy);
const angle = Math.atan2(dy, dx);
let velocityFactor = 0.08 * (megaLaunchActive ? 2.2 : 1.0); // mega launch stronger
const vx = Math.cos(angle) * power * velocityFactor;
const vy = Math.sin(angle) * power * velocityFactor;
scene.matter.body.setStatic(currentHuman.body, false);
scene.matter.body.setVelocity(currentHuman.body, { x: vx, y: vy });
// small spin
scene.matter.body.setAngularVelocity(currentHuman.body, (Math.random()*6-3));
// track with camera
scene.cameras.main.startFollow(currentHuman, true, 0.06, 0.06);
// reduce ammo and update UI
ammo = Math.max(0, ammo-1);
updateUi();
// after a delay spawn next if ammo left
scene.time.delayedCall(800, ()=>{ if (ammo>0) spawnHuman(scene); });
});
// collision detection with blocks/targets
scene.matter.world.on('collisionstart', (event) => {
event.pairs.forEach(pair => {
const { bodyA, bodyB } = pair;
[bodyA, bodyB].forEach(b => {
if (!b.gameObject) return;
const go = b.gameObject;
// if boomer explosion type and collision strong, explode
if (go.humanType === 'boomer') {
const speed = pair.collision.penetration && Math.hypot(pair.collision.penetration.x, pair.collision.penetration.y) || 0;
if (speed > 1.2 && !go.exploded) {
go.exploded = true;
explodeAt(scene, go.x, go.y);
scene.matter.world.remove(go.body);
go.destroy();
}
}
});
// scoring: check if block knocked far or high force
const bodies = [bodyA.gameObject, bodyB.gameObject];
bodies.forEach(obj => {
if (obj && obj.isBlock && !obj.scored) {
// if block speed large or rotated a lot -> scored
const speed = obj.body.speed || 0;
const angle = Math.abs(obj.rotation || 0);
if (speed > 3 || angle > 0.6) {
obj.scored = true;
score += (obj.blockType==='glass' ? 30 : obj.blockType==='wood' ? 15 : 8);
updateUi();
// small tint animation
scene.tweens.add({ targets: obj, alpha: 0.3, duration: 200, yoyo: true });
}
}
});
});
});
// camera bounds
scene.cameras.main.setBounds(0,0, WIDTH*1.5, HEIGHT);
scene.cameras.main.setZoom(1.0);
}
function update() {
// nothing heavy here; could check win/lose
const scene = this;
// check win: all targets destroyed
if (targetsGroup.every(t=>t.destroyed)) {
// level clear
if (!this._cleared) {
this._cleared = true;
scene.cameras.main.fadeOut(600, 255,255,255);
scene.time.delayedCall(700, ()=> {
// award stars
const blocksLeft = blocksGroup.filter(b=>!b.scored).length;
stars = Math.min(3, Math.max(1, Math.floor(score/100)+1));
document.getElementById('stars').innerText = stars;
alert('Level Cleared! Score: '+score+' Stars: '+stars);
nextLevel(scene);
});
}
}
// if out of ammo and no current human, show message
if (ammo<=0 && !currentHuman) {
// optional: show a small hint
}
}
/* ---------- Helpers ---------- */
function spawnHuman(scene) {
const type = document.getElementById('hType').value;
const emoji = 'πŸ§‘';
const container = scene.add.container(slingPos.x, slingPos.y);
const circle = scene.add.circle(0,0,22, 0xffcc80).setStrokeStyle(2,0xdb8b3b);
const txt = scene.add.text(-10,-16, emoji, { fontSize: '28px' });
container.add([circle, txt]);
scene.matter.add.gameObject(container, { shape: { type: 'circle', radius: 22 }, label: 'human' });
container.setDepth(3);
container.isHuman = true;
container.humanType = type;
// set physics params by type
const body = container.body;
body.friction = 0.8;
body.restitution = (type==='jumper' ? 0.8 : 0.2);
body.density = (type==='heavy' ? 0.009 : type==='boomer' ? 0.002 : 0.004);
// special behaviours
if (type==='jumper') {
container.onGroundBounce = true;
}
if (type==='boomer') {
// boomer will explode on high impact (handled in collision)
}
// mark human as current
currentHuman = container;
// small bobbing animation
scene.tweens.add({ targets: container, y: container.y-6, duration: 600, yoyo:true, repeat:-1 });
// ensure it collides with world and blocks
scene.matter.world.setCollisionCategory(0);
// collisions with blocks
blocksGroup.forEach(b => scene.matter.add.collider(container, b));
// collisions with targets
targetsGroup.forEach(t => scene.matter.add.collider(container, t));
// update UI
updateUi();
}
function buildLevel(scene, lvl) {
// clear old
blocksGroup.forEach(b=>{ try{ b.destroy(); }catch(e){} });
targetsGroup.forEach(t=>{ try{ t.destroy(); }catch(e){} });
blocksGroup = []; targetsGroup = [];
// create a simple structure of blocks depending on level
const baseX = WIDTH - 300;
const baseY = HEIGHT - 120;
const pattern = 2 + (lvl % 3); // complexity
for (let s=0; s<pattern; s++) {
const cols = 2 + s;
const rows = 2 + Math.floor(s/1);
const startX = baseX + s*70;
for (let i=0;i<cols;i++){
for (let j=0;j<rows;j++){
const x = startX + i*48;
const y = baseY - j*42;
const type = (Math.random()<0.15 ? 'metal' : Math.random()<0.4 ? 'glass' : 'wood');
const color = type==='metal' ? 0x90a4ae : type==='glass' ? 0x81d4fa : 0xffcc80;
const rect = scene.add.rectangle(x,y,44,38,color).setStrokeStyle(2,0x333333);
scene.matter.add.gameObject(rect, { shape: { type:'rectangle', width:44, height:38 }});
rect.isBlock = true; rect.blockType = type; rect.scored = false;
blocksGroup.push(rect);
}
}
}
// add a couple of "targets" (robots)
for (let k=0;k<Math.min(3, 1+lvl); k++) {
const tx = baseX + 40 + k*70;
const ty = baseY - (2+k%2)*42 - 20;
const container = scene.add.container(tx, ty);
const body = scene.add.circle(0,0,18,0xa5d6a7).setStrokeStyle(2,0x2e7d32);
const smile = scene.add.text(-9,-12, 'πŸ€–', {fontSize:'26px'});
container.add([body, smile]);
scene.matter.add.gameObject(container, { shape: { type: 'circle', radius: 18 }});
container.isTarget = true; container.destroyed = false;
targetsGroup.push(container);
// collision behavior: if high impact -> destroyed
scene.matter.world.on('collisionactive', (ev) => {
ev.pairs.forEach(pair => {
if ((pair.bodyA === container.body || pair.bodyB === container.body)) {
// check relative speed
const speed = pair.collision.penetration && Math.hypot(pair.collision.penetration.x, pair.collision.penetration.y) || 0;
if (speed > 1.1 && !container.destroyed) {
container.destroyed = true;
// small explosion
explodeAt(scene, container.x, container.y);
try { scene.matter.world.remove(container.body); } catch(e){}
container.destroy();
score += 80;
updateUi();
}
}
});
});
}
// camera reset
scene.cameras.main.setScroll(0,0);
scene.cameras.main.stopFollow();
}
function drawTrajectory(scene, x0, y0, vx, vy) {
// clear old
clearTrajectory();
// simulate points using simple physics approx (not matter sim)
const g = 1.0 * 60; // pixel/s^2 scale approx
const steps = 25;
const dt = 1/12;
for (let i=0;i<steps;i++){
const t = i*dt;
const px = x0 + vx * 60 * t; // 60 to scale to pixels
const py = y0 + vy * 60 * t + 0.5 * g * t * t;
const dot = scene.add.circle(px, py, 3, 0x0d47a1).setAlpha(0.6).setDepth(1);
dottedLines.push(dot);
}
}
function clearTrajectory() {
dottedLines.forEach(d=>d.destroy());
dottedLines = [];
}
function explodeAt(scene, x, y) {
// particle-ish effect using small circles
for (let i=0;i<18;i++){
const p = scene.add.circle(x, y, 4, 0xff8a80);
scene.tweens.add({ targets: p, x: x + Phaser.Math.Between(-120,120), y: y + Phaser.Math.Between(-120,120), alpha: 0, duration: 500 + Math.random()*400, onComplete: ()=>p.destroy() });
}
}
function updateUi() {
document.getElementById('score').innerText = score;
document.getElementById('ammo').innerText = ammo;
document.getElementById('stars').innerText = stars;
}
function resetLevel(scene) {
score = 0; ammo = 5; stars = 0;
currentHuman = null; isDragging=false; megaLaunchActive=false;
updateUi();
// remove all matter children and rebuild scene - easiest: reload page
window.location.reload();
}
function nextLevel(scene) {
level += 1; ammo = 5 + Math.min(level, 3);
score = score + 50; // small bonus
currentHuman = null;
buildLevel(scene, level);
spawnHuman(scene);
updateUi();
}
</script>
</body>
</html>