זירת החיסולים
by MysticFlare491165 lines44.3 KB
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>זירת החיסולים</title>
<style>
:root{
--bg:#10131f; --panel:#1b2032; --panel2:#242b47; --line:#323a5e;
--gold:#ffc93c; --gold-d:#c69413;
--green:#35d07f; --green-d:#1e8f57;
--red:#ff5b5b; --cyan:#3ec7f0; --violet:#a78bfa;
--text:#f4f6ff; --dim:#9aa3c7;
}
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent;
user-select:none;-webkit-user-select:none;-webkit-touch-callout:none;}
html,body{height:100%;overflow:hidden;overscroll-behavior:none;}
body{position:fixed;inset:0;width:100%;background:var(--bg);color:var(--text);
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Heebo",Arial,sans-serif;
touch-action:none;}
button{font-family:inherit;cursor:pointer;color:var(--text);}
input{user-select:text;-webkit-user-select:text;}
/* ---------- screens ---------- */
.screen{position:absolute;inset:0;display:none;flex-direction:column;}
.screen.active{display:flex;}
.hidden{display:none !important;}
/* ---------- home ---------- */
#home{padding:calc(14px + env(safe-area-inset-top)) 18px calc(20px + env(safe-area-inset-bottom));align-items:center;}
.top-row{width:100%;display:flex;justify-content:center;position:relative;}
#soundBtn{position:absolute;left:0;top:0;}
.icon-btn{width:44px;height:44px;border-radius:14px;background:var(--panel);
border:2px solid var(--line);font-size:1.2rem;display:flex;align-items:center;justify-content:center;}
#trophyPill{display:flex;align-items:center;gap:10px;background:var(--panel);
border:2px solid var(--line);border-radius:999px;padding:10px 20px;
font-size:1.05rem;font-weight:800;}
#trophyPill:active{transform:scale(.97);}
.tp-cup{font-size:1.2rem;}
#trophyCount{color:var(--gold);}
.tp-div{width:2px;height:18px;background:var(--line);border-radius:2px;}
.tp-name{color:var(--dim);max-width:34vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.home-mid{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;text-align:center;width:100%;}
.title-emojis{font-size:2.3rem;display:flex;gap:16px;}
.title-emojis span{display:inline-block;animation:bob 2.4s ease-in-out infinite;}
.title-emojis span:nth-child(2){animation-delay:.35s;}
.title-emojis span:nth-child(3){animation-delay:.7s;}
@keyframes bob{50%{transform:translateY(-9px);}}
.game-title{font-size:clamp(2.7rem,14vw,4.4rem);font-weight:900;line-height:1.02;
letter-spacing:-1px;color:var(--gold);
text-shadow:0 5px 0 rgba(0,0,0,.55),0 14px 40px rgba(255,201,60,.16);}
.subtitle{color:var(--dim);font-weight:700;}
.selected-info{background:var(--panel);border:2px solid var(--line);padding:9px 18px;border-radius:14px;font-weight:700;}
.selected-info b{color:var(--gold);}
.next-unlock{color:var(--gold);font-size:.92rem;font-weight:800;min-height:1.2em;}
.home-bottom{width:100%;display:flex;gap:12px;}
.btn{border:none;border-radius:18px;font-weight:900;transition:transform .06s;}
.btn:active{transform:translateY(4px);border-bottom-width:2px !important;}
.btn-green{background:var(--green);border-bottom:6px solid var(--green-d);color:#06281a;
font-size:1.5rem;padding:18px 10px;flex:2;}
.btn-panel{background:var(--panel2);border-bottom:6px solid #10141f;color:var(--text);
font-size:1.1rem;padding:18px 10px;flex:1;}
/* ---------- characters ---------- */
#chars{padding:calc(12px + env(safe-area-inset-top)) 16px 0;}
.chars-head{display:flex;align-items:center;gap:12px;padding-bottom:12px;}
.chars-head h2{flex:1;font-size:1.5rem;font-weight:900;}
.chars-trophy{font-weight:800;color:var(--gold);background:var(--panel);
border:2px solid var(--line);border-radius:999px;padding:8px 14px;}
.char-list{flex:1;overflow-y:auto;touch-action:pan-y;display:flex;flex-direction:column;gap:12px;
padding-bottom:calc(20px + env(safe-area-inset-bottom));}
.char-card{display:flex;gap:14px;background:var(--panel);border:2.5px solid var(--line);
border-radius:20px;padding:14px;text-align:right;width:100%;align-items:center;}
.char-card.selected{border-color:var(--gold);box-shadow:0 0 0 3px rgba(255,201,60,.16);}
.char-card.locked{opacity:.8;}
.char-card.locked .cc-emoji{filter:grayscale(.7);}
.cc-emoji{font-size:2.8rem;width:76px;height:76px;display:flex;align-items:center;justify-content:center;
background:#141a2e;border-radius:18px;border:2.5px solid var(--cc,#555);flex:0 0 auto;}
.cc-body{flex:1;min-width:0;}
.cc-name{font-size:1.25rem;font-weight:900;color:var(--cc,#fff);display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.cc-badge{font-size:.68rem;background:var(--gold);color:#221a00;padding:3px 9px;border-radius:99px;font-weight:900;}
.cc-line{color:var(--dim);font-size:.85rem;margin-top:3px;font-weight:600;}
.cc-lock{margin-top:9px;color:var(--gold);font-weight:800;font-size:.9rem;}
.cc-prog{height:9px;background:#000;border-radius:5px;margin-top:6px;overflow:hidden;}
.cc-prog div{height:100%;background:var(--gold);border-radius:5px;}
.cc-progtxt{font-size:.75rem;color:var(--dim);margin-top:4px;font-weight:700;}
.shake{animation:shk .35s;}
@keyframes shk{25%{transform:translateX(-6px);}50%{transform:translateX(6px);}75%{transform:translateX(-4px);}}
/* ---------- game ---------- */
#game{background:#141827;}
#cv{position:absolute;inset:0;}
#hud{position:absolute;top:calc(8px + env(safe-area-inset-top));left:0;right:0;
display:flex;justify-content:center;gap:7px;pointer-events:none;z-index:5;padding:0 54px;}
.chip{background:rgba(18,22,40,.88);border:1.5px solid var(--line);border-radius:12px;
padding:5px 9px;display:flex;align-items:center;gap:6px;}
.chip.me{border-color:var(--gold);}
.chip.dead{opacity:.45;filter:grayscale(1);}
.chip-e{font-size:1rem;}
.chip-n{font-size:.72rem;font-weight:800;max-width:60px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.chip-bar{width:42px;height:6px;background:#000;border-radius:3px;overflow:hidden;}
.chip-bar div{height:100%;width:100%;background:var(--green);border-radius:3px;}
#exitBtn{position:absolute;top:calc(8px + env(safe-area-inset-top));left:10px;
width:38px;height:38px;border-radius:12px;background:rgba(18,22,40,.88);
border:1.5px solid var(--line);color:var(--dim);font-size:1rem;font-weight:900;z-index:6;}
#joyZone{position:absolute;left:0;bottom:0;width:56%;height:72%;touch-action:none;z-index:4;}
#joyBase{position:absolute;width:118px;height:118px;border-radius:50%;
background:rgba(255,255,255,.05);border:2px solid rgba(255,255,255,.13);
left:26px;bottom:calc(40px + env(safe-area-inset-bottom));pointer-events:none;}
#joyBase.on{background:rgba(255,255,255,.09);}
#joyKnob{position:absolute;width:56px;height:56px;border-radius:50%;
background:rgba(255,255,255,.26);border:2px solid rgba(255,255,255,.4);
left:50%;top:50%;transform:translate(-50%,-50%);}
#fireBtn{position:absolute;right:20px;bottom:calc(32px + env(safe-area-inset-bottom));
width:94px;height:94px;border-radius:50%;font-size:2.4rem;z-index:4;
background:linear-gradient(180deg,#ff6b5e,#e04338);border:none;
border-bottom:6px solid #a52c24;box-shadow:0 8px 20px rgba(0,0,0,.45);touch-action:none;}
#fireBtn.press{transform:scale(.92);border-bottom-width:3px;}
#powerBtn{position:absolute;right:34px;bottom:calc(148px + env(safe-area-inset-bottom));
width:74px;height:74px;border-radius:50%;padding:6px;border:none;z-index:4;touch-action:none;
background:conic-gradient(var(--gold) var(--pct,0deg), #262c49 var(--pct,0deg));}
#powerBtn .pin{width:100%;height:100%;border-radius:50%;background:#141a2e;
display:flex;align-items:center;justify-content:center;font-size:1.7rem;opacity:.5;}
#powerBtn.ready .pin{opacity:1;}
#powerBtn.ready{animation:ppulse 1s infinite;box-shadow:0 0 24px rgba(255,201,60,.6);}
@keyframes ppulse{50%{transform:scale(1.09);}}
#countWrap{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;
justify-content:center;gap:14px;pointer-events:none;z-index:8;}
#cdNum{font-size:min(28vw,130px);font-weight:900;color:var(--gold);
text-shadow:0 6px 0 rgba(0,0,0,.5);line-height:1;}
#cdNum.pop{animation:cdpop .5s;}
@keyframes cdpop{0%{transform:scale(1.7);opacity:.2;}100%{transform:scale(1);opacity:1;}}
#cdHint{background:rgba(18,22,40,.88);border:1.5px solid var(--line);border-radius:14px;
padding:9px 16px;font-weight:800;color:var(--dim);font-size:.95rem;}
/* ---------- overlay / modal ---------- */
.overlay{position:absolute;inset:0;background:rgba(8,10,20,.74);display:flex;
align-items:center;justify-content:center;z-index:20;padding:20px;}
.overlay-card{background:var(--panel);border:2.5px solid var(--line);border-radius:24px;
padding:26px 22px;width:min(370px,92vw);display:flex;flex-direction:column;gap:13px;
text-align:center;position:relative;z-index:2;}
.overlay-card .btn{padding:15px 10px;font-size:1.2rem;}
.ov-emoji{font-size:4.2rem;line-height:1;animation:cdpop .5s;}
#ovTitle{font-size:1.65rem;font-weight:900;color:var(--gold);}
#ovSub{color:var(--text);font-weight:600;line-height:1.55;}
.ov-total{color:var(--dim);font-size:.9rem;}
.ov-unlock{background:rgba(255,201,60,.12);border:1.5px solid var(--gold);
border-radius:14px;padding:11px;color:var(--gold);font-weight:800;line-height:1.5;}
#confetti{position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:1;}
#confetti span{position:absolute;top:-36px;animation:fall linear forwards;}
@keyframes fall{to{transform:translateY(112vh) rotate(560deg);opacity:.75;}}
#nameInput{font-size:1.2rem;font-weight:800;padding:14px;border-radius:14px;
border:2px solid var(--line);background:#12172a;color:var(--text);text-align:center;outline:none;width:100%;}
#nameInput:focus{border-color:var(--gold);}
</style>
</head>
<body>
<!-- ============ HOME ============ -->
<div id="home" class="screen active">
<div class="top-row">
<button id="soundBtn" class="icon-btn">🔊</button>
<button id="trophyPill">
<span class="tp-cup">🏆</span><span id="trophyCount">0</span>
<span class="tp-div"></span>
<span id="playerNameLabel" class="tp-name">בחרו שם ✏️</span>
</button>
</div>
<div class="home-mid">
<div class="title-emojis"><span>🥷</span><span>🤖</span><span>👺</span></div>
<h1 class="game-title">זירת<br>החיסולים</h1>
<p class="subtitle">3 לוחמים. זירה אחת. רק אחד שורד.</p>
<div id="selectedInfo" class="selected-info"></div>
<div id="nextUnlock" class="next-unlock"></div>
</div>
<div class="home-bottom">
<button id="startBtn" class="btn btn-green">⚔️ התחלה</button>
<button id="charsBtn" class="btn btn-panel">🎭 דמויות</button>
</div>
</div>
<!-- ============ CHARACTERS ============ -->
<div id="chars" class="screen">
<div class="chars-head">
<button id="backBtn" class="icon-btn">→</button>
<h2>הדמויות</h2>
<div class="chars-trophy">🏆 <span id="trophyCount2">0</span></div>
</div>
<div id="charList" class="char-list"></div>
</div>
<!-- ============ GAME ============ -->
<div id="game" class="screen">
<canvas id="cv"></canvas>
<div id="hud"></div>
<button id="exitBtn">✕</button>
<div id="joyZone">
<div id="joyBase"><div id="joyKnob"></div></div>
</div>
<button id="powerBtn"><span class="pin"><span id="powerIcon">💥</span></span></button>
<button id="fireBtn">👊</button>
<div id="countWrap" class="hidden">
<div id="cdNum"></div>
<div id="cdHint"></div>
</div>
<div id="overlay" class="overlay hidden">
<div id="confetti"></div>
<div class="overlay-card">
<div id="ovEmoji" class="ov-emoji">🏆</div>
<h2 id="ovTitle"></h2>
<div id="ovSub"></div>
<div id="ovUnlock" class="ov-unlock hidden"></div>
<button id="againBtn" class="btn btn-green">⚔️ עוד קרב!</button>
<button id="homeBtn" class="btn btn-panel">🏠 דף הבית</button>
</div>
</div>
</div>
<!-- ============ NAME MODAL ============ -->
<div id="nameModal" class="overlay hidden">
<div class="overlay-card">
<h2 style="font-weight:900;">✏️ מה השם שלכם?</h2>
<input id="nameInput" maxlength="14" placeholder="השם שלכם...">
<button id="saveNameBtn" class="btn btn-green">שמירה ✓</button>
<button id="cancelNameBtn" class="btn btn-panel">ביטול</button>
</div>
</div>
<script>
'use strict';
/* ================= helpers ================= */
const $=id=>document.getElementById(id);
const clamp=(v,a,b)=>v<a?a:(v>b?b:v);
const rand=(a,b)=>a+Math.random()*(b-a);
const fmt=n=>n.toLocaleString();
const distF=(a,b)=>Math.hypot(b.x-a.x,b.y-a.y);
const angTo=(a,b)=>Math.atan2(b.y-a.y,b.x-a.x);
function shuffle(arr){for(let i=arr.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[arr[i],arr[j]]=[arr[j],arr[i]];}return arr;}
function esc(s){return String(s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));}
/* ================= save ================= */
const SAVE_KEY='zirat-hahisulim-save';
const save={name:'',trophies:0,selected:'thief',sound:true};
async function loadSave(){
if(!window.storage)return;
try{
const r=await window.storage.get(SAVE_KEY);
if(r&&r.value){const d=JSON.parse(r.value);
if(typeof d.name==='string')save.name=d.name;
if(typeof d.trophies==='number')save.trophies=d.trophies;
if(typeof d.selected==='string')save.selected=d.selected;
if(typeof d.sound==='boolean')save.sound=d.sound;
}
}catch(e){}
}
let saveTimer=null;
function persist(){
if(!window.storage)return;
clearTimeout(saveTimer);
saveTimer=setTimeout(async()=>{
try{await window.storage.set(SAVE_KEY,JSON.stringify(save));}catch(e){}
},250);
}
/* ================= characters ================= */
const CHARS=[
{id:'thief',emoji:'🥷',name:'הגנב',cost:0,color:'#a78bfa',type:'melee',
dmg:200,rate:3.5,spd:1,atkEmoji:'👊',
power:{emoji:'🦵',name:'בעיטת־על',charge:10},
lines:['👊 בוקס מקרוב — 200 נזק','🦵 מכת כוח: בעיטה — 750 נזק','⏱️ נטענת ב־10 שניות']},
{id:'robot',emoji:'🤖',name:'הרובוט',cost:1000,color:'#3ec7f0',type:'shoot',
dmg:100,rate:7,spd:1,atkEmoji:'🔩',projEmoji:'🔩',
power:{emoji:'⚡',name:'לייזר־ענק',charge:14},
lines:['🔩 יורה ברגים — 100 נזק','⚡ מכת כוח: לייזר על כל המגרש — 500 לכל היריבים','⏱️ נטענת ב־14 שניות']},
{id:'samurai',emoji:'👺',name:'הסמוראי',cost:2000,color:'#ff5b5b',type:'shoot',
dmg:100,rate:7,spd:1.3,atkEmoji:'🔪',projEmoji:'🔪',
power:{emoji:'⚔️',name:'חרב־החיסול',charge:20},
lines:['🔪 יורה סכינים — 100 נזק','💨 הכי מהיר בזירה!','⚔️ מכת כוח: חרב — חיסול מיידי מקרוב','⏱️ נטענת ב־20 שניות']},
];
const charById=id=>CHARS.find(c=>c.id===id)||CHARS[0];
const unlocked=c=>save.trophies>=c.cost;
const BOT_NAMES=['צל','רעם','עקרב','ברזל','קוברה','פנתר','טורנדו','ניצוץ'];
/* ================= sound ================= */
let actx=null;
document.addEventListener('pointerdown',()=>{
if(!actx){try{actx=new (window.AudioContext||window.webkitAudioContext)();}catch(e){}}
else if(actx.state==='suspended'){actx.resume().catch(()=>{});}
},true);
function tone(freq,dur,type,vol,slideTo){
if(!save.sound||!actx)return;
try{
const o=actx.createOscillator(),g=actx.createGain(),t=actx.currentTime;
o.type=type||'square';
o.frequency.setValueAtTime(freq,t);
if(slideTo)o.frequency.exponentialRampToValueAtTime(Math.max(20,slideTo),t+dur);
g.gain.setValueAtTime(vol||.12,t);
g.gain.exponentialRampToValueAtTime(.0001,t+dur);
o.connect(g);g.connect(actx.destination);
o.start(t);o.stop(t+dur+.03);
}catch(e){}
}
function sfx(n){
switch(n){
case 'shoot':tone(640,.05,'square',.04,220);break;
case 'shoot2':tone(480,.05,'square',.025,180);break;
case 'punch':tone(150,.08,'triangle',.2,70);break;
case 'whiff':tone(320,.05,'sine',.035,140);break;
case 'hit':tone(230,.06,'square',.1,100);break;
case 'hurt':tone(170,.11,'sawtooth',.13,70);break;
case 'boom':tone(120,.32,'sawtooth',.22,35);tone(65,.4,'square',.16,28);break;
case 'kick':tone(95,.26,'sawtooth',.28,32);break;
case 'sword':tone(950,.14,'square',.16,110);break;
case 'laser':tone(310,.6,'sawtooth',.2,55);break;
case 'ready':tone(880,.09,'sine',.14);setTimeout(()=>tone(1175,.13,'sine',.14),95);break;
case 'select':tone(660,.08,'sine',.12,760);break;
case 'deny':tone(190,.12,'square',.1,120);break;
case 'count':tone(440,.09,'sine',.12);break;
case 'go':tone(523,.12,'square',.14);setTimeout(()=>tone(784,.2,'square',.14),120);break;
case 'win':[523,659,784,1046].forEach((f,i)=>setTimeout(()=>tone(f,.18,'square',.14),i*130));break;
case 'lose':[392,311,262].forEach((f,i)=>setTimeout(()=>tone(f,.22,'sine',.13),i*160));break;
}
}
/* ================= screens / home UI ================= */
function show(id){
document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active'));
$(id).classList.add('active');
}
function refreshHome(){
$('trophyCount').textContent=fmt(save.trophies);
$('trophyCount2').textContent=fmt(save.trophies);
$('playerNameLabel').textContent=save.name?save.name:'בחרו שם ✏️';
const c=charById(save.selected);
$('selectedInfo').innerHTML='משחקים בתור: '+c.emoji+' <b>'+esc(c.name)+'</b>';
const next=CHARS.find(x=>!unlocked(x));
$('nextUnlock').textContent=next
?('עוד '+fmt(next.cost-save.trophies)+' גביעים ו'+next.name+' שלכם! '+next.emoji)
:'כל הדמויות פתוחות! 🎉';
$('soundBtn').textContent=save.sound?'🔊':'🔇';
}
function renderChars(){
const list=$('charList');list.innerHTML='';
CHARS.forEach(c=>{
const un=unlocked(c);
const card=document.createElement('button');
card.className='char-card'+(un?'':' locked')+(save.selected===c.id?' selected':'');
card.style.setProperty('--cc',c.color);
let html='<div class="cc-emoji">'+(un?c.emoji:'🔒')+'</div><div class="cc-body">';
html+='<div class="cc-name">'+esc(c.name)+(save.selected===c.id?' <span class="cc-badge">נבחר ✓</span>':'')+'</div>';
html+=c.lines.map(l=>'<div class="cc-line">'+l+'</div>').join('');
if(!un){
const pct=Math.min(100,Math.round(save.trophies/c.cost*100));
html+='<div class="cc-lock">🔒 נפתח ב־'+fmt(c.cost)+' גביעים'
+'<div class="cc-prog"><div style="width:'+pct+'%"></div></div>'
+'<div class="cc-progtxt">'+fmt(save.trophies)+' / '+fmt(c.cost)+'</div></div>';
}
html+='</div>';
card.innerHTML=html;
card.addEventListener('click',()=>{
if(un){save.selected=c.id;persist();renderChars();refreshHome();sfx('select');}
else{card.classList.remove('shake');void card.offsetWidth;card.classList.add('shake');sfx('deny');}
});
list.appendChild(card);
});
}
$('startBtn').addEventListener('click',()=>startMatch());
$('charsBtn').addEventListener('click',()=>{renderChars();show('chars');});
$('backBtn').addEventListener('click',()=>{refreshHome();show('home');});
$('soundBtn').addEventListener('click',()=>{save.sound=!save.sound;persist();refreshHome();if(save.sound)sfx('select');});
/* name modal */
$('trophyPill').addEventListener('click',()=>{
$('nameInput').value=save.name;
$('nameModal').classList.remove('hidden');
setTimeout(()=>$('nameInput').focus(),60);
});
function saveName(){
save.name=$('nameInput').value.trim().slice(0,14);
persist();refreshHome();
$('nameModal').classList.add('hidden');
sfx('select');
}
$('saveNameBtn').addEventListener('click',saveName);
$('nameInput').addEventListener('keydown',e=>{if(e.key==='Enter')saveName();});
$('cancelNameBtn').addEventListener('click',()=>$('nameModal').classList.add('hidden'));
/* ================= canvas / arena ================= */
const cv=$('cv'),ctx=cv.getContext('2d');
let W=0,H=0,S=1,dpr=1;
const HUD_SAFE=58;
let walls=[];
const WALL_DEFS=[
{fx:.50,fy:.40,len:.36,horiz:true},
{fx:.50,fy:.66,len:.20,horiz:true},
{fx:.16,fy:.55,len:.20,horiz:false},
{fx:.84,fy:.55,len:.20,horiz:false},
{fx:.25,fy:.24,len:.16,horiz:true},
{fx:.75,fy:.24,len:.16,horiz:true},
];
function buildWalls(){
const th=26*S;
walls=WALL_DEFS.map(d=>{
const L=d.horiz?d.len*W:d.len*H;
const w=d.horiz?L:th, h=d.horiz?th:L;
return {x:d.fx*W-w/2,y:d.fy*H-h/2,w:w,h:h};
});
}
function resizeCanvas(){
dpr=Math.min(window.devicePixelRatio||1,2);
const w=window.innerWidth,h=window.innerHeight;
if(G.running&&W>0&&H>0){
const rx=w/W,ry=h/H;
G.fighters.forEach(f=>{f.x*=rx;f.y*=ry;});
G.projs.forEach(p=>{p.x*=rx;p.y*=ry;});
}
W=w;H=h;
cv.width=Math.round(W*dpr);cv.height=Math.round(H*dpr);
cv.style.width=W+'px';cv.style.height=H+'px';
ctx.setTransform(dpr,0,0,dpr,0,0);
S=clamp(Math.min(W,H)/480,0.8,1.7);
buildWalls();
}
window.addEventListener('resize',resizeCanvas);
/* geometry */
function hitWall(x,y,r){
for(const w of walls){
const cx=clamp(x,w.x,w.x+w.w),cy=clamp(y,w.y,w.y+w.h);
const dx=x-cx,dy=y-cy;
if(dx*dx+dy*dy<r*r)return true;
}
return false;
}
function seg(ax,ay,bx,by,cx,cy,dx,dy){
const d1=(dx-cx)*(ay-cy)-(dy-cy)*(ax-cx);
const d2=(dx-cx)*(by-cy)-(dy-cy)*(bx-cx);
const d3=(bx-ax)*(cy-ay)-(by-ay)*(cx-ax);
const d4=(bx-ax)*(dy-ay)-(by-ay)*(dx-ax);
return ((d1>0&&d2<0)||(d1<0&&d2>0))&&((d3>0&&d4<0)||(d3<0&&d4>0));
}
function losClear(x1,y1,x2,y2){
for(const w of walls){
const x=w.x,y=w.y,X=w.x+w.w,Y=w.y+w.h;
if(seg(x1,y1,x2,y2,x,y,X,y)||seg(x1,y1,x2,y2,X,y,X,Y)||
seg(x1,y1,x2,y2,X,Y,x,Y)||seg(x1,y1,x2,y2,x,Y,x,y))return false;
}
return true;
}
/* ================= game state ================= */
const G={running:false,state:'idle',fighters:[],projs:[],fx:[],time:0,
countdown:0,cdLabel:'',shake:0,laser:0,laserOwner:null,endTimer:0,place:0,newUnlocks:[]};
let firing=false,fireJust=false;
let joyVec={x:0,y:0};
const keys={};
function makeFighter(def,x,y,isPlayer,name){
return {def:def,x:x,y:y,r:22*S,hp:1000,maxHp:1000,alive:true,isPlayer:isPlayer,
name:name,kx:0,ky:0,aim:isPlayer?-Math.PI/2:Math.PI/2,
charge:0,atkCd:0,flash:0,dash:null,readySfx:false,chipDead:false,
ai:isPlayer?null:{target:null,retarget:0,strafe:Math.random()<.5?1:-1,
strafeT:rand(.7,1.5),ideal:rand(170,290),powerT:rand(.3,1.2)}};
}
function enemiesAlive(f){return G.fighters.filter(e=>e!==f&&e.alive);}
function clampF(f){f.x=clamp(f.x,f.r+4,W-f.r-4);f.y=clamp(f.y,f.r+HUD_SAFE,H-f.r-4);}
function ejectFromWalls(f){
let tries=0;
while(hitWall(f.x,f.y,f.r+2)&&tries++<60){
const a=Math.atan2(H/2-f.y,W/2-f.x);
f.x+=Math.cos(a)*10;f.y+=Math.sin(a)*10;
}
clampF(f);
}
function startMatch(){
if(!unlocked(charById(save.selected)))save.selected='thief';
resizeCanvas();
const pdef=charById(save.selected);
const pool=shuffle(CHARS.slice());
const names=shuffle(BOT_NAMES.slice());
G.fighters=[
makeFighter(pdef,W*.5,H*.78,true,save.name||'אני'),
makeFighter(pool[0],W*.18,H*.15,false,names[0]),
makeFighter(pool[1],W*.82,H*.15,false,names[1]),
];
G.fighters.forEach(ejectFromWalls);
G.projs=[];G.fx=[];G.time=0;G.shake=0;G.laser=0;G.laserOwner=null;
G.state='count';G.countdown=2.85;G.cdLabel='';G.endTimer=0;G.place=0;G.newUnlocks=[];
G.running=true;firing=false;fireJust=false;joyVec={x:0,y:0};
setupHUD();
$('fireBtn').textContent=pdef.atkEmoji;
$('powerIcon').textContent=pdef.power.emoji;
$('cdHint').textContent='🕹️ תזוזה • '+pdef.atkEmoji+' '+(pdef.type==='melee'?'מכות':'ירי')+' • '+pdef.power.emoji+' מכת כוח';
$('countWrap').classList.remove('hidden');
$('overlay').classList.add('hidden');
$('powerBtn').classList.remove('ready');
$('powerBtn').style.setProperty('--pct','0deg');
updateHUD();
show('game');
}
function setupHUD(){
const hud=$('hud');hud.innerHTML='';
G.fighters.forEach(f=>{
const chip=document.createElement('div');
chip.className='chip'+(f.isPlayer?' me':'');
chip.innerHTML='<span class="chip-e">'+f.def.emoji+'</span>'
+'<span class="chip-n">'+esc(f.name)+'</span>'
+'<div class="chip-bar"><div></div></div>';
f.chip=chip;f.chipBar=chip.querySelector('.chip-bar div');
hud.appendChild(chip);
});
}
function updateHUD(){
for(const f of G.fighters){
if(!f.chipBar)continue;
const pct=clamp(f.hp/f.maxHp,0,1);
f.chipBar.style.width=(pct*100)+'%';
f.chipBar.style.background=pct>.5?'#35d07f':(pct>.25?'#ffc93c':'#ff5b5b');
if(!f.alive&&!f.chipDead){
f.chipDead=true;f.chip.classList.add('dead');
f.chip.querySelector('.chip-e').textContent='💀';
}
}
}
function updatePowerBtn(){
const f=G.fighters[0];if(!f)return;
const pct=clamp(f.charge/f.def.power.charge,0,1);
$('powerBtn').style.setProperty('--pct',(pct*360)+'deg');
const ready=pct>=1&&f.alive&&G.state==='play';
$('powerBtn').classList.toggle('ready',ready);
if(!ready)f.readySfx=false;
else if(!f.readySfx){f.readySfx=true;sfx('ready');}
}
/* ================= input ================= */
const joyZone=$('joyZone'),joyBase=$('joyBase'),joyKnob=$('joyKnob');
let joyId=null,joyOrigin={x:0,y:0};
const JOY_R=52;
function moveKnob(dx,dy){joyKnob.style.transform='translate(calc(-50% + '+dx+'px), calc(-50% + '+dy+'px))';}
function resetJoyPos(){
joyBase.style.left='26px';joyBase.style.top='auto';
joyBase.style.bottom='calc(40px + env(safe-area-inset-bottom))';
}
joyZone.addEventListener('pointerdown',e=>{
if(joyId!==null)return;
joyId=e.pointerId;
try{joyZone.setPointerCapture(joyId);}catch(err){}
joyOrigin={x:e.clientX,y:e.clientY};
const zr=joyZone.getBoundingClientRect();
joyBase.style.left=(e.clientX-zr.left-59)+'px';
joyBase.style.bottom='auto';
joyBase.style.top=(e.clientY-zr.top-59)+'px';
joyBase.classList.add('on');
joyVec={x:0,y:0};moveKnob(0,0);
e.preventDefault();
});
joyZone.addEventListener('pointermove',e=>{
if(e.pointerId!==joyId)return;
let dx=e.clientX-joyOrigin.x,dy=e.clientY-joyOrigin.y;
const d=Math.hypot(dx,dy);
if(d>JOY_R){dx*=JOY_R/d;dy*=JOY_R/d;}
joyVec={x:dx/JOY_R,y:dy/JOY_R};
moveKnob(dx,dy);
});
function endJoy(e){
if(e.pointerId!==joyId)return;
joyId=null;joyVec={x:0,y:0};moveKnob(0,0);
joyBase.classList.remove('on');resetJoyPos();
}
joyZone.addEventListener('pointerup',endJoy);
joyZone.addEventListener('pointercancel',endJoy);
const fireBtn=$('fireBtn');
fireBtn.addEventListener('pointerdown',e=>{
try{fireBtn.setPointerCapture(e.pointerId);}catch(err){}
firing=true;fireJust=true;
fireBtn.classList.add('press');
e.preventDefault();
});
function endFire(){firing=false;fireBtn.classList.remove('press');}
fireBtn.addEventListener('pointerup',endFire);
fireBtn.addEventListener('pointercancel',endFire);
$('powerBtn').addEventListener('pointerdown',e=>{tryPlayerPower();e.preventDefault();});
$('exitBtn').addEventListener('click',()=>{
G.running=false;G.state='idle';
$('countWrap').classList.add('hidden');
$('overlay').classList.add('hidden');
refreshHome();show('home');
});
window.addEventListener('keydown',e=>{
keys[e.key.toLowerCase()]=true;
if(e.key===' '){keys.space=true;fireJust=true;e.preventDefault();}
if((e.key==='e'||e.key==='E'||e.key==='Enter')&&G.state==='play')tryPlayerPower();
});
window.addEventListener('keyup',e=>{
keys[e.key.toLowerCase()]=false;
if(e.key===' ')keys.space=false;
});
document.addEventListener('contextmenu',e=>e.preventDefault());
document.addEventListener('touchmove',e=>{
if(e.target&&e.target.closest&&e.target.closest('#game'))e.preventDefault();
},{passive:false});
/* ================= combat ================= */
const BASE_SPEED=195;
function moveFighter(f,dx,dy,dt){
const sp=BASE_SPEED*S*f.def.spd;
const nx=f.x+(dx*sp+f.kx)*dt;
const ny=f.y+(dy*sp+f.ky)*dt;
const dec=Math.pow(0.002,dt);
f.kx*=dec;f.ky*=dec;
if(!hitWall(nx,f.y,f.r))f.x=nx;
if(!hitWall(f.x,ny,f.r))f.y=ny;
clampF(f);
}
function knock(t,from,power){
const a=angTo(from,t);
t.kx+=Math.cos(a)*power;t.ky+=Math.sin(a)*power;
}
function autoTarget(f){
let best=null,bd=1e9,anyBest=null,anyD=1e9;
for(const e of enemiesAlive(f)){
const d=distF(f,e);
if(d<anyD){anyD=d;anyBest=e;}
if(d<bd&&losClear(f.x,f.y,e.x,e.y)){bd=d;best=e;}
}
return best||anyBest;
}
function shootProj(f,angle){
const sp=470*S;
G.projs.push({x:f.x+Math.cos(angle)*(f.r+10),y:f.y+Math.sin(angle)*(f.r+10),
vx:Math.cos(angle)*sp,vy:Math.sin(angle)*sp,r:8*S,
dmg:f.def.dmg,owner:f,emoji:f.def.projEmoji,
rot:Math.random()*6.28,spin:(Math.random()<.5?-1:1)*rand(9,14)});
sfx(f.isPlayer?'shoot':'shoot2');
}
function meleeSwing(f){
let target=null,bd=1e9;
for(const e of enemiesAlive(f)){
const d=distF(f,e);
if(d<f.r+e.r+34*S&&d<bd&&losClear(f.x,f.y,e.x,e.y)){target=e;bd=d;}
}
if(target){
f.aim=angTo(f,target);
fxEmoji(target.x,target.y,'👊',30*S,.28);
fxRing(target.x,target.y,f.def.color,8,26*S,.25);
knock(target,f,260*S);
sfx('punch');
hurt(target,f.def.dmg,f);
}else{
const wx=f.x+Math.cos(f.aim)*(f.r+22*S),wy=f.y+Math.sin(f.aim)*(f.r+22*S);
fxEmoji(wx,wy,'💨',20*S,.2);
if(f.isPlayer)sfx('whiff');
}
}
function damageNoEnd(f,dmg,src,silent){
if(!f.alive||G.state!=='play')return false;
f.hp-=dmg;f.flash=.16;
const kill=f.hp<=0;
if(!silent)fxDmg(f.x,f.y-f.r-30*S,String(dmg),
kill?'#ffd75e':(f.isPlayer?'#ff9b9b':'#ffe9a8'),dmg>=500);
if(f.isPlayer)sfx('hurt');else if(src&&src.isPlayer)sfx('hit');
if(kill){
f.hp=0;f.alive=false;f.dash=null;
fxEmoji(f.x,f.y,'💥',54*S,.5);
fxRing(f.x,f.y,'#ff8a5c',10,70*S,.45);
fxSpark(f.x,f.y,'#ffce7a');
addShake(9);sfx('boom');
if(src&&src.isPlayer){try{if(navigator.vibrate)navigator.vibrate(35);}catch(e){}}
}
return kill;
}
function hurt(f,dmg,src){damageNoEnd(f,dmg,src,false);checkEnd();}
function checkEnd(){
if(G.state!=='play')return;
const alive=G.fighters.filter(x=>x.alive);
const p=G.fighters[0];
if(!p.alive){G.place=alive.length+1;beginEnd();}
else if(alive.length===1){G.place=1;beginEnd();}
}
function beginEnd(){
G.state='ending';
G.endTimer=G.place===1?0.8:1.1;
firing=false;
G.newUnlocks=[];
if(G.place===1){
const before=save.trophies;
save.trophies+=10;persist();
G.newUnlocks=CHARS.filter(c=>before<c.cost&&save.trophies>=c.cost);
}
}
function addShake(m){G.shake=Math.max(G.shake,m);}
/* powers */
function usePower(f){
const id=f.def.id;
if(id==='robot'){
f.charge=0;G.laser=1.0;G.laserOwner=f;
addShake(13);sfx('laser');
fxMsg(f.x,f.y-f.r-40*S,'⚡ לייזר־ענק! ⚡');
for(const e of enemiesAlive(f))damageNoEnd(e,500,f,false);
checkEnd();
return true;
}
if(id==='thief'){
let t=null,bd=1e9;
for(const e of enemiesAlive(f)){
const d=distF(f,e);
if(d<f.r+e.r+60*S&&d<bd&&losClear(f.x,f.y,e.x,e.y)){t=e;bd=d;}
}
if(!t)return false;
f.charge=0;f.aim=angTo(f,t);
fxEmoji(t.x,t.y,'🦵',44*S,.4);
fxRing(t.x,t.y,'#ffc93c',10,60*S,.35);
addShake(9);sfx('kick');
knock(t,f,900*S);
hurt(t,750,f);
return true;
}
if(id==='samurai'){
let best=null,bd=1e9;
for(const e of enemiesAlive(f)){
const d=distF(f,e);
if(d<300*S&&d<bd&&losClear(f.x,f.y,e.x,e.y)){best=e;bd=d;}
}
if(!best)return false;
f.charge=0;
f.dash={a:angTo(f,best),t:0,dur:.22,killed:[]};
sfx('sword');
return true;
}
return false;
}
function tryPlayerPower(){
const f=G.fighters[0];
if(!f||G.state!=='play'||!f.alive||f.dash)return;
if(f.charge<f.def.power.charge){
const pb=$('powerBtn');
pb.classList.remove('shake');void pb.offsetWidth;pb.classList.add('shake');
sfx('deny');return;
}
const ok=usePower(f);
if(!ok){fxMsg(f.x,f.y-f.r-40*S,'אין יריב קרוב!');sfx('deny');}
}
function dashStep(f,dt){
const d=f.dash;d.t+=dt;
const sp=1500*S;
const nx=f.x+Math.cos(d.a)*sp*dt,ny=f.y+Math.sin(d.a)*sp*dt;
if(hitWall(nx,ny,f.r)||nx<f.r||nx>W-f.r||ny<f.r+HUD_SAFE||ny>H-f.r){
d.t=d.dur;
}else{f.x=nx;f.y=ny;}
fxGhost(f.x,f.y,f.r,f.def.color);
for(const e of enemiesAlive(f)){
if(d.killed.indexOf(e)>=0)continue;
if(distF(f,e)<f.r+e.r+10*S){
d.killed.push(e);
fxEmoji(e.x,e.y,'⚔️',46*S,.45);
fxMsg(e.x,e.y-44*S,'חיסול!');
fxRing(e.x,e.y,'#ffffff',6,70*S,.4);
addShake(10);
damageNoEnd(e,999999,f,true);
}
}
checkEnd();
if(f.dash&&d.t>=d.dur)f.dash=null;
}
/* player + bots */
function playerControl(f,dt){
let mx=joyVec.x,my=joyVec.y;
mx+=(keys['arrowright']||keys['d']?1:0)-(keys['arrowleft']||keys['a']?1:0);
my+=(keys['arrowdown']||keys['s']?1:0)-(keys['arrowup']||keys['w']?1:0);
const m=Math.hypot(mx,my);
if(m>1){mx/=m;my/=m;}
moveFighter(f,mx,my,dt);
const t=autoTarget(f);
if(t)f.aim=angTo(f,t);
else if(m>0.1)f.aim=Math.atan2(my,mx);
f.atkCd-=dt;
if(fireJust){f.atkCd=Math.min(f.atkCd,0);fireJust=false;}
if((firing||keys.space)&&f.atkCd<=0){
if(f.def.type==='shoot')shootProj(f,f.aim);
else meleeSwing(f);
f.atkCd=1/f.def.rate;
}
}
function botThink(f,dt){
const ai=f.ai;
ai.retarget-=dt;
if(ai.retarget<=0||!ai.target||!ai.target.alive){
ai.target=autoTarget(f);
ai.retarget=rand(.5,1.1);
}
const t=ai.target;
if(!t){moveFighter(f,0,0,dt);return;}
const dx=t.x-f.x,dy=t.y-f.y;
const d=Math.hypot(dx,dy)||1;
const ux=dx/d,uy=dy/d;
ai.strafeT-=dt;
if(ai.strafeT<=0){ai.strafe*=-1;ai.strafeT=rand(.7,1.5);}
let mx=0,my=0;
if(f.def.type==='melee'){
mx=ux;my=uy;
if(d<f.r+t.r+28*S){mx*=.15;my*=.15;}
mx+=-uy*ai.strafe*.25;my+=ux*ai.strafe*.25;
}else{
const ideal=ai.ideal*S;
if(d>ideal+70*S){mx=ux;my=uy;}
else if(d<ideal-70*S){mx=-ux;my=-uy;}
mx+=-uy*ai.strafe*.8;my+=ux*ai.strafe*.8;
}
const mm=Math.hypot(mx,my);
if(mm>1){mx/=mm;my/=mm;}
const px=f.x+mx*46*S,py=f.y+my*46*S;
if(hitWall(px,py,f.r)){
const a=Math.atan2(my,mx);
const offs=[1.2,-1.2,2.2,-2.2];
for(let i=0;i<offs.length;i++){
const nx2=f.x+Math.cos(a+offs[i])*46*S,ny2=f.y+Math.sin(a+offs[i])*46*S;
if(!hitWall(nx2,ny2,f.r)){mx=Math.cos(a+offs[i]);my=Math.sin(a+offs[i]);break;}
}
}
f.aim=Math.atan2(dy,dx);
moveFighter(f,mx,my,dt);
f.atkCd-=dt;
if(f.def.type==='shoot'){
if(f.atkCd<=0&&d<560*S&&losClear(f.x,f.y,t.x,t.y)){
shootProj(f,f.aim+rand(-.13,.13));
f.atkCd=rand(.32,.5);
}
}else{
if(f.atkCd<=0&&d<f.r+t.r+36*S){
meleeSwing(f);
f.atkCd=.5;
}
}
if(f.charge>=f.def.power.charge){
ai.powerT-=dt;
if(ai.powerT<=0){
const used=usePower(f);
ai.powerT=used?.6:.25;
}
}
}
function separate(){
const fs=G.fighters.filter(f=>f.alive);
for(let i=0;i<fs.length;i++)for(let j=i+1;j<fs.length;j++){
const a=fs[i],b=fs[j];
let dx=b.x-a.x,dy=b.y-a.y;
let d=Math.hypot(dx,dy);
const min=a.r+b.r;
if(d<min){
if(d<0.01){dx=1;dy=0;d=1;}
const push=(min-d)/2,ux=dx/d,uy=dy/d;
a.x-=ux*push;a.y-=uy*push;
b.x+=ux*push;b.y+=uy*push;
clampF(a);clampF(b);
if(hitWall(a.x,a.y,a.r))ejectFromWalls(a);
if(hitWall(b.x,b.y,b.r))ejectFromWalls(b);
}
}
}
function updateProjs(dt){
for(let i=G.projs.length-1;i>=0;i--){
const p=G.projs[i];
p.x+=p.vx*dt;p.y+=p.vy*dt;p.rot+=p.spin*dt;
if(p.x<-40||p.x>W+40||p.y<-40||p.y>H+40){G.projs.splice(i,1);continue;}
if(hitWall(p.x,p.y,p.r)){fxSpark(p.x,p.y,'#c9d2ff');G.projs.splice(i,1);continue;}
let hitF=false;
for(const f of G.fighters){
if(!f.alive||f===p.owner)continue;
const dx=f.x-p.x,dy=f.y-p.y;
const rr=(f.r+p.r);
if(dx*dx+dy*dy<rr*rr){
fxSpark(p.x,p.y,p.owner.def.color);
hurt(f,p.dmg,p.owner);
hitF=true;break;
}
}
if(hitF)G.projs.splice(i,1);
}
}
/* ================= fx ================= */
function addFx(o){o.t=0;G.fx.push(o);}
function fxDmg(x,y,text,color,big){addFx({type:'dmg',x:x,y:y,text:text,color:color,big:!!big,dur:.8});}
function fxMsg(x,y,text){addFx({type:'msg',x:x,y:y,text:text,dur:1.0});}
function fxEmoji(x,y,e,size,dur){addFx({type:'emoji',x:x,y:y,e:e,size:size,dur:dur||.35});}
function fxRing(x,y,color,r0,r1,dur){addFx({type:'ring',x:x,y:y,color:color,r0:r0,r1:r1,dur:dur||.3});}
function fxSpark(x,y,color){addFx({type:'spark',x:x,y:y,color:color,dur:.22});}
function fxGhost(x,y,r,color){addFx({type:'ghost',x:x,y:y,r:r,color:color,dur:.3});}
function updateFx(dt){
for(let i=G.fx.length-1;i>=0;i--){
G.fx[i].t+=dt;
if(G.fx[i].t>=G.fx[i].dur)G.fx.splice(i,1);
}
}
/* ================= update ================= */
function update(dt){
G.time+=dt;
if(G.shake>0)G.shake=Math.max(0,G.shake-34*dt);
if(G.laser>0)G.laser-=dt;
updateFx(dt);
if(G.state==='count'){
G.countdown-=dt;
const label=G.countdown>0.6?String(Math.ceil((G.countdown-0.6)/0.75)):'קרב!';
if(label!==G.cdLabel){
G.cdLabel=label;
const cd=$('cdNum');
cd.textContent=label;
cd.classList.remove('pop');void cd.offsetWidth;cd.classList.add('pop');
sfx(label==='קרב!'?'go':'count');
}
if(G.countdown<=0){
G.state='play';
$('countWrap').classList.add('hidden');
}
updateHUD();updatePowerBtn();
return;
}
if(G.state==='ending'||G.state==='over'){
updateProjs(dt);
updateHUD();updatePowerBtn();
if(G.state==='ending'){
G.endTimer-=dt;
if(G.endTimer<=0)showOverlay();
}
return;
}
if(G.state!=='play')return;
for(const f of G.fighters){
if(!f.alive)continue;
f.flash=Math.max(0,f.flash-dt);
if(f.dash){dashStep(f,dt);continue;}
f.charge=Math.min(f.def.power.charge,f.charge+dt);
if(f.isPlayer)playerControl(f,dt);
else botThink(f,dt);
}
separate();
updateProjs(dt);
updateHUD();
updatePowerBtn();
}
/* ================= render ================= */
function circleFill(x,y,r){ctx.beginPath();ctx.arc(x,y,r,0,6.2832);ctx.fill();}
function circleStroke(x,y,r){ctx.beginPath();ctx.arc(x,y,r,0,6.2832);ctx.stroke();}
function rr(x,y,w,h,r){
ctx.beginPath();
if(ctx.roundRect)ctx.roundRect(x,y,w,h,r);
else ctx.rect(x,y,w,h);
}
function drawEmoji(e,x,y,size){
ctx.font=size+'px "Apple Color Emoji","Segoe UI Emoji","Noto Color Emoji",sans-serif';
ctx.textAlign='center';ctx.textBaseline='middle';
ctx.fillText(e,x,y);
}
function render(){
ctx.clearRect(0,0,W,H);
ctx.save();
if(G.shake>0)ctx.translate(rand(-G.shake,G.shake),rand(-G.shake,G.shake));
ctx.fillStyle='#141827';
ctx.fillRect(-24,-24,W+48,H+48);
ctx.strokeStyle='rgba(85,100,170,.13)';ctx.lineWidth=1;
const g=46*S;
ctx.beginPath();
for(let x=g;x<W;x+=g){ctx.moveTo(x,0);ctx.lineTo(x,H);}
for(let y=g;y<H;y+=g){ctx.moveTo(0,y);ctx.lineTo(W,y);}
ctx.stroke();
for(const w of walls){
ctx.fillStyle='#39415f';rr(w.x,w.y,w.w,w.h,8);ctx.fill();
ctx.fillStyle='rgba(255,255,255,.10)';rr(w.x,w.y,w.w,4,3);ctx.fill();
ctx.strokeStyle='rgba(0,0,0,.35)';ctx.lineWidth=2;rr(w.x,w.y,w.w,w.h,8);ctx.stroke();
}
for(const f of G.fighters){
if(!f.alive){
ctx.globalAlpha=.5;
drawEmoji('💀',f.x,f.y,f.r*1.2);
ctx.globalAlpha=1;
}
}
for(const p of G.projs){
ctx.save();ctx.translate(p.x,p.y);ctx.rotate(p.rot);
drawEmoji(p.emoji,0,0,16*S);
ctx.restore();
}
for(const f of G.fighters){if(f.alive)drawFighter(f);}
drawFx();
if(G.laser>0)drawLaser();
ctx.restore();
}
function drawFighter(f){
const c=f.def.color;
if(f.flash>0){
ctx.globalAlpha=Math.min(1,f.flash*6);
ctx.fillStyle='#ffffff';circleFill(f.x,f.y,f.r+6);
ctx.globalAlpha=1;
}
if(f.isPlayer){
ctx.strokeStyle='rgba(255,215,94,'+(0.3+0.22*Math.sin(G.time*5)).toFixed(3)+')';
ctx.lineWidth=3;circleStroke(f.x,f.y,f.r+9);
}
ctx.fillStyle='#20263e';circleFill(f.x,f.y,f.r);
ctx.lineWidth=4;ctx.strokeStyle=f.isPlayer?'#ffd75e':c;circleStroke(f.x,f.y,f.r);
drawEmoji(f.def.emoji,f.x,f.y+1,f.r*1.35);
const ax=f.x+Math.cos(f.aim)*(f.r+7),ay=f.y+Math.sin(f.aim)*(f.r+7);
ctx.fillStyle=f.isPlayer?'#ffd75e':c;circleFill(ax,ay,3.5);
if(!f.isPlayer&&f.charge>=f.def.power.charge&&Math.sin(G.time*10)>0){
drawEmoji('💥',f.x+f.r*.9,f.y-f.r-9,14*S);
}
ctx.font='800 '+Math.round(12*S)+'px -apple-system,"Segoe UI",Roboto,sans-serif';
ctx.textAlign='center';ctx.textBaseline='middle';
ctx.fillStyle=f.isPlayer?'#ffd75e':'#cdd5f7';
ctx.fillText(f.name,f.x,f.y-f.r-25*S);
const bw=56*S,bh=7*S,bx=f.x-bw/2,by=f.y-f.r-17*S;
ctx.fillStyle='rgba(0,0,0,.55)';rr(bx-1,by-1,bw+2,bh+2,4);ctx.fill();
const pct=clamp(f.hp/f.maxHp,0,1);
if(pct>0){
ctx.fillStyle=pct>.5?'#35d07f':(pct>.25?'#ffc93c':'#ff5b5b');
rr(bx,by,bw*pct,bh,3);ctx.fill();
}
}
function drawFx(){
for(const e of G.fx){
const p=e.t/e.dur;
if(e.type==='dmg'){
ctx.globalAlpha=1-p;
ctx.font='900 '+Math.round((e.big?26:18)*S)+'px -apple-system,"Segoe UI",Roboto,sans-serif';
ctx.textAlign='center';ctx.textBaseline='middle';
const y=e.y-p*44*S;
ctx.lineWidth=3;ctx.strokeStyle='rgba(0,0,0,.6)';
ctx.strokeText(e.text,e.x,y);
ctx.fillStyle=e.color||'#ffe9a8';
ctx.fillText(e.text,e.x,y);
ctx.globalAlpha=1;
}else if(e.type==='msg'){
ctx.globalAlpha=1-p*p;
ctx.font='900 '+Math.round(21*S)+'px -apple-system,"Segoe UI",Roboto,sans-serif';
ctx.textAlign='center';ctx.textBaseline='middle';
const y=e.y-p*26*S;
ctx.lineWidth=4;ctx.strokeStyle='rgba(0,0,0,.65)';
ctx.strokeText(e.text,e.x,y);
ctx.fillStyle='#ffc93c';
ctx.fillText(e.text,e.x,y);
ctx.globalAlpha=1;
}else if(e.type==='emoji'){
ctx.globalAlpha=1-p*p;
const sc=.5+Math.min(1,p*3)*.7;
drawEmoji(e.e,e.x,e.y,e.size*sc);
ctx.globalAlpha=1;
}else if(e.type==='ring'){
ctx.globalAlpha=(1-p)*.85;
ctx.strokeStyle=e.color;ctx.lineWidth=Math.max(1,4*(1-p));
circleStroke(e.x,e.y,e.r0+(e.r1-e.r0)*p);
ctx.globalAlpha=1;
}else if(e.type==='spark'){
ctx.globalAlpha=1-p;
ctx.fillStyle=e.color||'#fff';
for(let k=0;k<6;k++){
const a=k*1.047,r=p*20*S;
circleFill(e.x+Math.cos(a)*r,e.y+Math.sin(a)*r,2.4*S*(1-p)+.6);
}
ctx.globalAlpha=1;
}else if(e.type==='ghost'){
ctx.globalAlpha=(1-p)*.3;
ctx.fillStyle=e.color;
circleFill(e.x,e.y,e.r*(1-p*.4));
ctx.globalAlpha=1;
}
}
}
function drawLaser(){
const col=G.laserOwner?G.laserOwner.def.color:'#3ec7f0';
ctx.globalAlpha=0.14+0.11*Math.abs(Math.sin(G.time*42));
ctx.fillStyle=col;
ctx.fillRect(0,0,W,H);
const n=7;
for(let i=0;i<n;i++){
const y=(((i/n)+(1-G.laser)*1.4)%1)*H;
ctx.globalAlpha=.32;ctx.fillStyle=col;ctx.fillRect(0,y-6,W,14);
ctx.globalAlpha=.55;ctx.fillStyle='#ffffff';ctx.fillRect(0,y,W,3);
}
ctx.globalAlpha=1;
}
/* ================= overlay ================= */
function showOverlay(){
G.state='over';
const place=G.place;
$('ovEmoji').textContent=place===1?'🏆':(place===2?'🥈':'🥉');
if(place===1){
$('ovTitle').textContent='מקום ראשון!';
$('ovSub').innerHTML='+10 גביעים! 🏆<br><span class="ov-total">סה״כ: '+fmt(save.trophies)+' 🏆</span>';
spawnConfetti();sfx('win');
}else{
$('ovTitle').textContent=place===2?'מקום שני — כמעט!':'מקום שלישי';
$('ovSub').innerHTML='רק המקום הראשון מקבל גביעים.<br>עוד ניסיון? 💪';
$('confetti').innerHTML='';
sfx('lose');
}
const ou=$('ovUnlock');
if(G.newUnlocks.length){
ou.classList.remove('hidden');
ou.innerHTML=G.newUnlocks.map(c=>'🎉 נפתחה דמות חדשה: '+c.emoji+' <b>'+esc(c.name)+'</b>!').join('<br>');
}else ou.classList.add('hidden');
$('overlay').classList.remove('hidden');
}
function spawnConfetti(){
const box=$('confetti');box.innerHTML='';
const em=['🎉','✨','⭐','🏆','💛'];
for(let i=0;i<26;i++){
const s=document.createElement('span');
s.textContent=em[i%em.length];
s.style.left=(Math.random()*100)+'%';
s.style.animationDelay=(Math.random()*.8)+'s';
s.style.animationDuration=(2+Math.random()*1.6)+'s';
s.style.fontSize=(15+Math.random()*20)+'px';
box.appendChild(s);
}
}
$('againBtn').addEventListener('click',()=>startMatch());
$('homeBtn').addEventListener('click',()=>{
G.running=false;G.state='idle';
$('overlay').classList.add('hidden');
refreshHome();show('home');
});
/* ================= loop / init ================= */
let last=0;
function loop(ts){
requestAnimationFrame(loop);
const dt=Math.min(0.05,(ts-last)/1000||0.016);
last=ts;
if(!G.running)return;
update(dt);
render();
}
(async function init(){
await loadSave();
refreshHome();
renderChars();
resizeCanvas();
resetJoyPos();
requestAnimationFrame(loop);
})();
</script>
</body>
</html>Game Source: זירת החיסולים
Creator: MysticFlare49
Libraries: none
Complexity: complex (1165 lines, 44.3 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: game-mysticflare49" to link back to the original. Then publish at arcadelab.ai/publish.