danielbegemkulov2-sudo opened a new issue, #132:
URL: https://github.com/apache/cordova-fetch/issues/132

   # Feature Request
   
   ## Motivation Behind Feature
   <!-- Why should this feature be implemented? What problem does it solve? -->
   
   
   
   ## Feature Description
   <!-- 
   Describe your feature request in detail
   Please provide any code examples or screenshots of what this feature would 
look like
   Are there any drawbacks? Will this break anything for existing users? 
   -->
   
   
   
   ## Alternatives or Workarounds
   <!-- 
   Describe alternatives or workarounds you are currently using 
   Are there ways to do this with existing functionality?
   --><!doctype html>
   <html lang="ru">
   <head>
   <meta charset="utf-8" />
   <meta name="viewport" content="width=device-width,initial-scale=1" />
   <title>Прототип: Top-Down Shooter</title>
   <style>
     
html,body{height:100%;margin:0;background:#111;color:#eee;font-family:system-ui,Segoe
 UI,Roboto,Arial}
     #gameWrap{display:flex;gap:16px;align-items:flex-start;padding:12px}
     
canvas{background:linear-gradient(#0b1720,#061018);border-radius:8px;box-shadow:0
 6px 20px rgba(0,0,0,.6)}
     #ui{min-width:200px}
     button{display:inline-block;margin-top:8px;padding:10px 
14px;border-radius:8px;border:none;background:#2b7;background-color:#1565c0;color:#fff;cursor:pointer}
     .muted{opacity:.8;font-size:13px}
     #score{font-size:18px;margin-bottom:8px}
   </style>
   </head>
   <body>
   <div id="gameWrap">
     <canvas id="game" width="900" height="600"></canvas>
     <div id="ui">
       <div id="score">Счёт: 0</div>
       <div id="hp">HP: 100</div>
       <div id="wave">Волна: 0</div>
       <div class="muted">Управление: WASD — движение, мышь — прицел, ЛКМ — 
стрелять</div>
       <button id="startBtn">Начать / Перезапустить</button>
       <div style="margin-top:12px" class="muted">Прототип: 
однопользовательская аркада. Хочешь добавить: оружия, апгрейды, карты, 
мобилки?</div>
     </div>
   </div>
   
   <script>
   (() => {
     const canvas = document.getElementById('game');
     const ctx = canvas.getContext('2d', {alpha:false});
     let W = canvas.width, H = canvas.height;
   
     // UI
     const scoreEl = document.getElementById('score');
     const hpEl = document.getElementById('hp');
     const waveEl = document.getElementById('wave');
     const startBtn = document.getElementById('startBtn');
   
     // Game state
     let mouse = {x: W/2, y: H/2, down:false};
     let keys = {};
     let running = false;
     let player, bullets, enemies, spawnTimer, score, wave, gameOver;
   
     function reset() {
       player = {x: W/2, y: H/2, r:14, speed: 220, hp:100, fireRate: 0.18, 
lastShot:0};
       bullets = [];
       enemies = [];
       spawnTimer = 0;
       score = 0;
       wave = 0;
       gameOver = false;
       updateUI();
     }
   
     function updateUI(){
       scoreEl.textContent = 'Счёт: ' + Math.floor(score);
       hpEl.textContent = 'HP: ' + Math.max(0, Math.floor(player.hp));
       waveEl.textContent = 'Волна: ' + wave;
     }
   
     // Input
     window.addEventListener('mousemove', e => {
       const rect = canvas.getBoundingClientRect();
       mouse.x = (e.clientX - rect.left) * (canvas.width / rect.width);
       mouse.y = (e.clientY - rect.top) * (canvas.height / rect.height);
     });
     window.addEventListener('mousedown', () => mouse.down = true);
     window.addEventListener('mouseup', () => mouse.down = false);
     window.addEventListener('keydown', e => keys[e.key.toLowerCase()] = true);
     window.addEventListener('keyup', e => keys[e.key.toLowerCase()] = false);
   
     // Resize support (optional)
     // Keep canvas fixed size for simplicity.
   
     // Game logic
     function spawnWave() {
       wave++;
       const count = 4 + wave * 2;
       for (let i=0;i<count;i++){
         const side = Math.floor(Math.random()*4);
         let x,y;
         if (side===0){ x = Math.random()*W; y = -30; }
         else if (side===1){ x = Math.random()*W; y = H+30; }
         else if (side===2){ x = -30; y = Math.random()*H; }
         else { x = W+30; y = Math.random()*H; }
         const speed = 40 + Math.random()*40 + wave*4;
         enemies.push({x,y,r:14 + Math.random()*10, speed, hp: 10 + wave*2, 
hitFlash:0});
       }
     }
   
     function angleTo(a,b){ return Math.atan2(b.y-a.y, b.x-a.x); }
   
     function update(dt){
       if (!running) return;
   
       if (gameOver) return;
   
       // Player movement
       let vx=0, vy=0;
       if (keys['w']||keys['arrowup']) vy -= 1;
       if (keys['s']||keys['arrowdown']) vy += 1;
       if (keys['a']||keys['arrowleft']) vx -= 1;
       if (keys['d']||keys['arrowright']) vx += 1;
       const len = Math.hypot(vx,vy) || 1;
       player.x += (vx/len) * player.speed * dt;
       player.y += (vy/len) * player.speed * dt;
       // clamp
       player.x = Math.max(10, Math.min(W-10, player.x));
       player.y = Math.max(10, Math.min(H-10, player.y));
   
       // Shooting
       player.lastShot += dt;
       if (mouse.down && player.lastShot >= player.fireRate){
         player.lastShot = 0;
         const ang = Math.atan2(mouse.y - player.y, mouse.x - player.x);
         const speed = 520;
         bullets.push({x:player.x + Math.cos(ang)*(player.r+8), y:player.y + 
Math.sin(ang)*(player.r+8), vx:Math.cos(ang)*speed, vy:Math.sin(ang)*speed, 
r:3, life:1.8});
       }
   
       // Bullets
       for (let i=bullets.length-1;i>=0;i--){
         const b = bullets[i];
         b.x += b.vx * dt;
         b.y += b.vy * dt;
         b.life -= dt;
         if (b.life <= 0 || b.x < -50 || b.x > W+50 || b.y < -50 || b.y > H+50) 
bullets.splice(i,1);
       }
   
       // Enemies
       for (let i=enemies.length-1;i>=0;i--){
         const e = enemies[i];
         // move toward player
         const ang = Math.atan2(player.y - e.y, player.x - e.x);
         e.x += Math.cos(ang) * e.speed * dt;
         e.y += Math.sin(ang) * e.speed * dt;
   
         // collision with player
         const dx = e.x - player.x, dy = e.y - player.y;
         const dist = Math.hypot(dx,dy);
         if (dist < e.r + player.r){
           // damage
           player.hp -= 15 * dt; // continuous damage while touching
           e.hp -= 999; // enemy dies on contact (for fast gameplay)
           e.hitFlash = 0.2;
         }
   
         // hit by bullets
         for (let j=bullets.length-1;j>=0;j--){
           const b = bullets[j];
           const dx2 = e.x - b.x, dy2 = e.y - b.y;
           if (Math.hypot(dx2,dy2) < e.r + b.r){
             e.hp -= 10; // bullet damage
             bullets.splice(j,1);
             e.hitFlash = 0.12;
           }
         }
   
         if (e.hp <= 0){
           score += 10 + wave*2;
           enemies.splice(i,1);
         } else {
           e.hitFlash = Math.max(0, e.hitFlash - dt);
         }
       }
   
       // Spawn waves when all enemies cleared
       if (enemies.length === 0) {
         spawnTimer += dt;
         if (spawnTimer > 1.1) { spawnTimer = 0; spawnWave(); }
       }
   
       // Check player death
       if (player.hp <= 0){
         player.hp = 0;
         gameOver = true;
       }
   
       updateUI();
     }
   
     // Render
     function draw(){
       // background
       ctx.clearRect(0,0,W,H);
       // subtle vignette-ish
       ctx.fillStyle = '#071018';
       ctx.fillRect(0,0,W,H);
   
       // draw bullets
       for (const b of bullets){
         ctx.beginPath();
         ctx.arc(b.x,b.y,b.r,0,Math.PI*2);
         ctx.fillStyle = '#ffd';
         ctx.fill();
       }
   
       // draw enemies
       for (const e of enemies){
         ctx.save();
         ctx.translate(e.x,e.y);
         // body
         ctx.beginPath();
         ctx.arc(0,0,e.r,0,Math.PI*2);
         ctx.fillStyle = e.hitFlash > 0 ? '#ff9' : '#ff5252';
         ctx.fill();
         // simple eyes
         ctx.fillStyle = '#330000';
         ctx.fillRect(-e.r/3, -e.r/6, e.r/3, e.r/6);
         ctx.fillRect(e.r/6, -e.r/6, e.r/3, e.r/6);
         ctx.restore();
       }
   
       // player
       // draw direction aim
       const ang = Math.atan2(mouse.y - player.y, mouse.x - player.x);
       ctx.save();
       ctx.translate(player.x, player.y);
       ctx.rotate(ang);
       // body
       ctx.beginPath();
       ctx.arc(0,0,player.r,0,Math.PI*2);
       ctx.fillStyle = '#6cf';
       ctx.fill();
       // gun
       ctx.fillStyle = '#0b3';
       ctx.fillRect(6, -4, player.r+8, 8);
       ctx.restore();
   
       // HUD overlay
       if (gameOver){
         ctx.fillStyle = 'rgba(0,0,0,0.6)';
         ctx.fillRect(0,0,W,H);
         ctx.fillStyle = '#fff';
         ctx.textAlign = 'center';
         ctx.font = '40px system-ui,Segoe UI,Roboto';
         ctx.fillText('Игра окончена', W/2, H/2 - 20);
         ctx.font = '20px system-ui,Segoe UI,Roboto';
         ctx.fillText('Нажми "Начать / Перезапустить"', W/2, H/2 + 18);
       }
     }
   
     // Main loop
     let last = performance.now();
     function loop(ts){
       const dt = Math.min(0.04, (ts - last)/1000); // clamp dt
       last = ts;
       update(dt);
       draw();
       requestAnimationFrame(loop);
     }
   
     // Start game
     startBtn.addEventListener('click', () => {
       reset();
       running = true;
       player.lastShot = 0.2;
       last = performance.now();
     });
   
     // init
     reset();
     running = true;
     spawnWave();
     requestAnimationFrame(loop);
   
     // small touch support: start shooting on touch
     canvas.addEventListener('touchstart', (e) => {
       e.preventDefault();
       mouse.down = true;
       const rect = canvas.getBoundingClientRect();
       const t = e.touches[0];
       mouse.x = (t.clientX - rect.left) * (canvas.width / rect.width);
       mouse.y = (t.clientY - rect.top) * (canvas.height / rect.height);
     }, {passive:false});
     canvas.addEventListener('touchmove', (e) => {
       e.preventDefault();
       const rect = canvas.getBoundingClientRect();
       const t = e.touches[0];
       mouse.x = (t.clientX - rect.left) * (canvas.width / rect.width);
       mouse.y = (t.clientY - rect.top) * (canvas.height / rect.height);
     }, {passive:false});
     canvas.addEventListener('touchend', (e) => { mouse.down = false; }, 
{passive:false});
   
   })();
   </script>
   </body>
   </html><!doctype html>
   <html lang="ru">
   <head>
   <meta charset="utf-8" />
   <meta name="viewport" content="width=device-width,initial-scale=1" />
   <title>Прототип: Top-Down Shooter</title>
   <style>
     
html,body{height:100%;margin:0;background:#111;color:#eee;font-family:system-ui,Segoe
 UI,Roboto,Arial}
     #gameWrap{display:flex;gap:16px;align-items:flex-start;padding:12px}
     
canvas{background:linear-gradient(#0b1720,#061018);border-radius:8px;box-shadow:0
 6px 20px rgba(0,0,0,.6)}
     #ui{min-width:200px}
     button{display:inline-block;margin-top:8px;padding:10px 
14px;border-radius:8px;border:none;background:#2b7;background-color:#1565c0;color:#fff;cursor:pointer}
     .muted{opacity:.8;font-size:13px}
     #score{font-size:18px;margin-bottom:8px}
   </style>
   </head>
   <body>
   <div id="gameWrap">
     <canvas id="game" width="900" height="600"></canvas>
     <div id="ui">
       <div id="score">Счёт: 0</div>
       <div id="hp">HP: 100</div>
       <div id="wave">Волна: 0</div>
       <div class="muted">Управление: WASD — движение, мышь — прицел, ЛКМ — 
стрелять</div>
       <button id="startBtn">Начать / Перезапустить</button>
       <div style="margin-top:12px" class="muted">Прототип: 
однопользовательская аркада. Хочешь добавить: оружия, апгрейды, карты, 
мобилки?</div>
     </div>
   </div>
   
   <script>
   (() => {
     const canvas = document.getElementById('game');
     const ctx = canvas.getContext('2d', {alpha:false});
     let W = canvas.width, H = canvas.height;
   
     // UI
     const scoreEl = document.getElementById('score');
     const hpEl = document.getElementById('hp');
     const waveEl = document.getElementById('wave');
     const startBtn = document.getElementById('startBtn');
   
     // Game state
     let mouse = {x: W/2, y: H/2, down:false};
     let keys = {};
     let running = false;
     let player, bullets, enemies, spawnTimer, score, wave, gameOver;
   
     function reset() {
       player = {x: W/2, y: H/2, r:14, speed: 220, hp:100, fireRate: 0.18, 
lastShot:0};
       bullets = [];
       enemies = [];
       spawnTimer = 0;
       score = 0;
       wave = 0;
       gameOver = false;
       updateUI();
     }
   
     function updateUI(){
       scoreEl.textContent = 'Счёт: ' + Math.floor(score);
       hpEl.textContent = 'HP: ' + Math.max(0, Math.floor(player.hp));
       waveEl.textContent = 'Волна: ' + wave;
     }
   
     // Input
     window.addEventListener('mousemove', e => {
       const rect = canvas.getBoundingClientRect();
       mouse.x = (e.clientX - rect.left) * (canvas.width / rect.width);
       mouse.y = (e.clientY - rect.top) * (canvas.height / rect.height);
     });
     window.addEventListener('mousedown', () => mouse.down = true);
     window.addEventListener('mouseup', () => mouse.down = false);
     window.addEventListener('keydown', e => keys[e.key.toLowerCase()] = true);
     window.addEventListener('keyup', e => keys[e.key.toLowerCase()] = false);
   
     // Resize support (optional)
     // Keep canvas fixed size for simplicity.
   
     // Game logic
     function spawnWave() {
       wave++;
       const count = 4 + wave * 2;
       for (let i=0;i<count;i++){
         const side = Math.floor(Math.random()*4);
         let x,y;
         if (side===0){ x = Math.random()*W; y = -30; }
         else if (side===1){ x = Math.random()*W; y = H+30; }
         else if (side===2){ x = -30; y = Math.random()*H; }
         else { x = W+30; y = Math.random()*H; }
         const speed = 40 + Math.random()*40 + wave*4;
         enemies.push({x,y,r:14 + Math.random()*10, speed, hp: 10 + wave*2, 
hitFlash:0});
       }
     }
   
     function angleTo(a,b){ return Math.atan2(b.y-a.y, b.x-a.x); }
   
     function update(dt){
       if (!running) return;
   
       if (gameOver) return;
   
       // Player movement
       let vx=0, vy=0;
       if (keys['w']||keys['arrowup']) vy -= 1;
       if (keys['s']||keys['arrowdown']) vy += 1;
       if (keys['a']||keys['arrowleft']) vx -= 1;
       if (keys['d']||keys['arrowright']) vx += 1;
       const len = Math.hypot(vx,vy) || 1;
       player.x += (vx/len) * player.speed * dt;
       player.y += (vy/len) * player.speed * dt;
       // clamp
       player.x = Math.max(10, Math.min(W-10, player.x));
       player.y = Math.max(10, Math.min(H-10, player.y));
   
       // Shooting
       player.lastShot += dt;
       if (mouse.down && player.lastShot >= player.fireRate){
         player.lastShot = 0;
         const ang = Math.atan2(mouse.y - player.y, mouse.x - player.x);
         const speed = 520;
         bullets.push({x:player.x + Math.cos(ang)*(player.r+8), y:player.y + 
Math.sin(ang)*(player.r+8), vx:Math.cos(ang)*speed, vy:Math.sin(ang)*speed, 
r:3, life:1.8});
       }
   
       // Bullets
       for (let i=bullets.length-1;i>=0;i--){
         const b = bullets[i];
         b.x += b.vx * dt;
         b.y += b.vy * dt;
         b.life -= dt;
         if (b.life <= 0 || b.x < -50 || b.x > W+50 || b.y < -50 || b.y > H+50) 
bullets.splice(i,1);
       }
   
       // Enemies
       for (let i=enemies.length-1;i>=0;i--){
         const e = enemies[i];
         // move toward player
         const ang = Math.atan2(player.y - e.y, player.x - e.x);
         e.x += Math.cos(ang) * e.speed * dt;
         e.y += Math.sin(ang) * e.speed * dt;
   
         // collision with player
         const dx = e.x - player.x, dy = e.y - player.y;
         const dist = Math.hypot(dx,dy);
         if (dist < e.r + player.r){
           // damage
           player.hp -= 15 * dt; // continuous damage while touching
           e.hp -= 999; // enemy dies on contact (for fast gameplay)
           e.hitFlash = 0.2;
         }
   
         // hit by bullets
         for (let j=bullets.length-1;j>=0;j--){
           const b = bullets[j];
           const dx2 = e.x - b.x, dy2 = e.y - b.y;
           if (Math.hypot(dx2,dy2) < e.r + b.r){
             e.hp -= 10; // bullet damage
             bullets.splice(j,1);
             e.hitFlash = 0.12;
           }
         }
   
         if (e.hp <= 0){
           score += 10 + wave*2;
           enemies.splice(i,1);
         } else {
           e.hitFlash = Math.max(0, e.hitFlash - dt);
         }
       }
   
       // Spawn waves when all enemies cleared
       if (enemies.length === 0) {
         spawnTimer += dt;
         if (spawnTimer > 1.1) { spawnTimer = 0; spawnWave(); }
       }
   
       // Check player death
       if (player.hp <= 0){
         player.hp = 0;
         gameOver = true;
       }
   
       updateUI();
     }
   
     // Render
     function draw(){
       // background
       ctx.clearRect(0,0,W,H);
       // subtle vignette-ish
       ctx.fillStyle = '#071018';
       ctx.fillRect(0,0,W,H);
   
       // draw bullets
       for (const b of bullets){
         ctx.beginPath();
         ctx.arc(b.x,b.y,b.r,0,Math.PI*2);
         ctx.fillStyle = '#ffd';
         ctx.fill();
       }
   
       // draw enemies
       for (const e of enemies){
         ctx.save();
         ctx.translate(e.x,e.y);
         // body
         ctx.beginPath();
         ctx.arc(0,0,e.r,0,Math.PI*2);
         ctx.fillStyle = e.hitFlash > 0 ? '#ff9' : '#ff5252';
         ctx.fill();
         // simple eyes
         ctx.fillStyle = '#330000';
         ctx.fillRect(-e.r/3, -e.r/6, e.r/3, e.r/6);
         ctx.fillRect(e.r/6, -e.r/6, e.r/3, e.r/6);
         ctx.restore();
       }
   
       // player
       // draw direction aim
       const ang = Math.atan2(mouse.y - player.y, mouse.x - player.x);
       ctx.save();
       ctx.translate(player.x, player.y);
       ctx.rotate(ang);
       // body
       ctx.beginPath();
       ctx.arc(0,0,player.r,0,Math.PI*2);
       ctx.fillStyle = '#6cf';
       ctx.fill();
       // gun
       ctx.fillStyle = '#0b3';
       ctx.fillRect(6, -4, player.r+8, 8);
       ctx.restore();
   
       // HUD overlay
       if (gameOver){
         ctx.fillStyle = 'rgba(0,0,0,0.6)';
         ctx.fillRect(0,0,W,H);
         ctx.fillStyle = '#fff';
         ctx.textAlign = 'center';
         ctx.font = '40px system-ui,Segoe UI,Roboto';
         ctx.fillText('Игра окончена', W/2, H/2 - 20);
         ctx.font = '20px system-ui,Segoe UI,Roboto';
         ctx.fillText('Нажми "Начать / Перезапустить"', W/2, H/2 + 18);
       }
     }
   
     // Main loop
     let last = performance.now();
     function loop(ts){
       const dt = Math.min(0.04, (ts - last)/1000); // clamp dt
       last = ts;
       update(dt);
       draw();
       requestAnimationFrame(loop);
     }
   
     // Start game
     startBtn.addEventListener('click', () => {
       reset();
       running = true;
       player.lastShot = 0.2;
       last = performance.now();
     });
   
     // init
     reset();
     running = true;
     spawnWave();
     requestAnimationFrame(loop);
   
     // small touch support: start shooting on touch
     canvas.addEventListener('touchstart', (e) => {
       e.preventDefault();
       mouse.down = true;
       const rect = canvas.getBoundingClientRect();
       const t = e.touches[0];
       mouse.x = (t.clientX - rect.left) * (canvas.width / rect.width);
       mouse.y = (t.clientY - rect.top) * (canvas.height / rect.height);
     }, {passive:false});
     canvas.addEventListener('touchmove', (e) => {
       e.preventDefault();
       const rect = canvas.getBoundingClientRect();
       const t = e.touches[0];
       mouse.x = (t.clientX - rect.left) * (canvas.width / rect.width);
       mouse.y = (t.clientY - rect.top) * (canvas.height / rect.height);
     }, {passive:false});
     canvas.addEventListener('touchend', (e) => { mouse.down = false; }, 
{passive:false});
   
   })();
   </script>
   </body>
   </html><!doctype html>
   <html lang="ru">
   <head>
   <meta charset="utf-8" />
   <meta name="viewport" content="width=device-width,initial-scale=1" />
   <title>Прототип: Top-Down Shooter</title>
   <style>
     
html,body{height:100%;margin:0;background:#111;color:#eee;font-family:system-ui,Segoe
 UI,Roboto,Arial}
     #gameWrap{display:flex;gap:16px;align-items:flex-start;padding:12px}
     
canvas{background:linear-gradient(#0b1720,#061018);border-radius:8px;box-shadow:0
 6px 20px rgba(0,0,0,.6)}
     #ui{min-width:200px}
     button{display:inline-block;margin-top:8px;padding:10px 
14px;border-radius:8px;border:none;background:#2b7;background-color:#1565c0;color:#fff;cursor:pointer}
     .muted{opacity:.8;font-size:13px}
     #score{font-size:18px;margin-bottom:8px}
   </style>
   </head>
   <body>
   <div id="gameWrap">
     <canvas id="game" width="900" height="600"></canvas>
     <div id="ui">
       <div id="score">Счёт: 0</div>
       <div id="hp">HP: 100</div>
       <div id="wave">Волна: 0</div>
       <div class="muted">Управление: WASD — движение, мышь — прицел, ЛКМ — 
стрелять</div>
       <button id="startBtn">Начать / Перезапустить</button>
       <div style="margin-top:12px" class="muted">Прототип: 
однопользовательская аркада. Хочешь добавить: оружия, апгрейды, карты, 
мобилки?</div>
     </div>
   </div>
   
   <script>
   (() => {
     const canvas = document.getElementById('game');
     const ctx = canvas.getContext('2d', {alpha:false});
     let W = canvas.width, H = canvas.height;
   
     // UI
     const scoreEl = document.getElementById('score');
     const hpEl = document.getElementById('hp');
     const waveEl = document.getElementById('wave');
     const startBtn = document.getElementById('startBtn');
   
     // Game state
     let mouse = {x: W/2, y: H/2, down:false};
     let keys = {};
     let running = false;
     let player, bullets, enemies, spawnTimer, score, wave, gameOver;
   
     function reset() {
       player = {x: W/2, y: H/2, r:14, speed: 220, hp:100, fireRate: 0.18, 
lastShot:0};
       bullets = [];
       enemies = [];
       spawnTimer = 0;
       score = 0;
       wave = 0;
       gameOver = false;
       updateUI();
     }
   
     function updateUI(){
       scoreEl.textContent = 'Счёт: ' + Math.floor(score);
       hpEl.textContent = 'HP: ' + Math.max(0, Math.floor(player.hp));
       waveEl.textContent = 'Волна: ' + wave;
     }
   
     // Input
     window.addEventListener('mousemove', e => {
       const rect = canvas.getBoundingClientRect();
       mouse.x = (e.clientX - rect.left) * (canvas.width / rect.width);
       mouse.y = (e.clientY - rect.top) * (canvas.height / rect.height);
     });
     window.addEventListener('mousedown', () => mouse.down = true);
     window.addEventListener('mouseup', () => mouse.down = false);
     window.addEventListener('keydown', e => keys[e.key.toLowerCase()] = true);
     window.addEventListener('keyup', e => keys[e.key.toLowerCase()] = false);
   
     // Resize support (optional)
     // Keep canvas fixed size for simplicity.
   
     // Game logic
     function spawnWave() {
       wave++;
       const count = 4 + wave * 2;
       for (let i=0;i<count;i++){
         const side = Math.floor(Math.random()*4);
         let x,y;
         if (side===0){ x = Math.random()*W; y = -30; }
         else if (side===1){ x = Math.random()*W; y = H+30; }
         else if (side===2){ x = -30; y = Math.random()*H; }
         else { x = W+30; y = Math.random()*H; }
         const speed = 40 + Math.random()*40 + wave*4;
         enemies.push({x,y,r:14 + Math.random()*10, speed, hp: 10 + wave*2, 
hitFlash:0});
       }
     }
   
     function angleTo(a,b){ return Math.atan2(b.y-a.y, b.x-a.x); }
   
     function update(dt){
       if (!running) return;
   
       if (gameOver) return;
   
       // Player movement
       let vx=0, vy=0;
       if (keys['w']||keys['arrowup']) vy -= 1;
       if (keys['s']||keys['arrowdown']) vy += 1;
       if (keys['a']||keys['arrowleft']) vx -= 1;
       if (keys['d']||keys['arrowright']) vx += 1;
       const len = Math.hypot(vx,vy) || 1;
       player.x += (vx/len) * player.speed * dt;
       player.y += (vy/len) * player.speed * dt;
       // clamp
       player.x = Math.max(10, Math.min(W-10, player.x));
       player.y = Math.max(10, Math.min(H-10, player.y));
   
       // Shooting
       player.lastShot += dt;
       if (mouse.down && player.lastShot >= player.fireRate){
         player.lastShot = 0;
         const ang = Math.atan2(mouse.y - player.y, mouse.x - player.x);
         const speed = 520;
         bullets.push({x:player.x + Math.cos(ang)*(player.r+8), y:player.y + 
Math.sin(ang)*(player.r+8), vx:Math.cos(ang)*speed, vy:Math.sin(ang)*speed, 
r:3, life:1.8});
       }
   
       // Bullets
       for (let i=bullets.length-1;i>=0;i--){
         const b = bullets[i];
         b.x += b.vx * dt;
         b.y += b.vy * dt;
         b.life -= dt;
         if (b.life <= 0 || b.x < -50 || b.x > W+50 || b.y < -50 || b.y > H+50) 
bullets.splice(i,1);
       }
   
       // Enemies
       for (let i=enemies.length-1;i>=0;i--){
         const e = enemies[i];
         // move toward player
         const ang = Math.atan2(player.y - e.y, player.x - e.x);
         e.x += Math.cos(ang) * e.speed * dt;
         e.y += Math.sin(ang) * e.speed * dt;
   
         // collision with player
         const dx = e.x - player.x, dy = e.y - player.y;
         const dist = Math.hypot(dx,dy);
         if (dist < e.r + player.r){
           // damage
           player.hp -= 15 * dt; // continuous damage while touching
           e.hp -= 999; // enemy dies on contact (for fast gameplay)
           e.hitFlash = 0.2;
         }
   
         // hit by bullets
         for (let j=bullets.length-1;j>=0;j--){
           const b = bullets[j];
           const dx2 = e.x - b.x, dy2 = e.y - b.y;
           if (Math.hypot(dx2,dy2) < e.r + b.r){
             e.hp -= 10; // bullet damage
             bullets.splice(j,1);
             e.hitFlash = 0.12;
           }
         }
   
         if (e.hp <= 0){
           score += 10 + wave*2;
           enemies.splice(i,1);
         } else {
           e.hitFlash = Math.max(0, e.hitFlash - dt);
         }
       }
   
       // Spawn waves when all enemies cleared
       if (enemies.length === 0) {
         spawnTimer += dt;
         if (spawnTimer > 1.1) { spawnTimer = 0; spawnWave(); }
       }
   
       // Check player death
       if (player.hp <= 0){
         player.hp = 0;
         gameOver = true;
       }
   
       updateUI();
     }
   
     // Render
     function draw(){
       // background
       ctx.clearRect(0,0,W,H);
       // subtle vignette-ish
       ctx.fillStyle = '#071018';
       ctx.fillRect(0,0,W,H);
   
       // draw bullets
       for (const b of bullets){
         ctx.beginPath();
         ctx.arc(b.x,b.y,b.r,0,Math.PI*2);
         ctx.fillStyle = '#ffd';
         ctx.fill();
       }
   
       // draw enemies
       for (const e of enemies){
         ctx.save();
         ctx.translate(e.x,e.y);
         // body
         ctx.beginPath();
         ctx.arc(0,0,e.r,0,Math.PI*2);
         ctx.fillStyle = e.hitFlash > 0 ? '#ff9' : '#ff5252';
         ctx.fill();
         // simple eyes
         ctx.fillStyle = '#330000';
         ctx.fillRect(-e.r/3, -e.r/6, e.r/3, e.r/6);
         ctx.fillRect(e.r/6, -e.r/6, e.r/3, e.r/6);
         ctx.restore();
       }
   
       // player
       // draw direction aim
       const ang = Math.atan2(mouse.y - player.y, mouse.x - player.x);
       ctx.save();
       ctx.translate(player.x, player.y);
       ctx.rotate(ang);
       // body
       ctx.beginPath();
       ctx.arc(0,0,player.r,0,Math.PI*2);
       ctx.fillStyle = '#6cf';
       ctx.fill();
       // gun
       ctx.fillStyle = '#0b3';
       ctx.fillRect(6, -4, player.r+8, 8);
       ctx.restore();
   
       // HUD overlay
       if (gameOver){
         ctx.fillStyle = 'rgba(0,0,0,0.6)';
         ctx.fillRect(0,0,W,H);
         ctx.fillStyle = '#fff';
         ctx.textAlign = 'center';
         ctx.font = '40px system-ui,Segoe UI,Roboto';
         ctx.fillText('Игра окончена', W/2, H/2 - 20);
         ctx.font = '20px system-ui,Segoe UI,Roboto';
         ctx.fillText('Нажми "Начать / Перезапустить"', W/2, H/2 + 18);
       }
     }
   
     // Main loop
     let last = performance.now();
     function loop(ts){
       const dt = Math.min(0.04, (ts - last)/1000); // clamp dt
       last = ts;
       update(dt);
       draw();
       requestAnimationFrame(loop);
     }
   
     // Start game
     startBtn.addEventListener('click', () => {
       reset();
       running = true;
       player.lastShot = 0.2;
       last = performance.now();
     });
   
     // init
     reset();
     running = true;
     spawnWave();
     requestAnimationFrame(loop);
   
     // small touch support: start shooting on touch
     canvas.addEventListener('touchstart', (e) => {
       e.preventDefault();
       mouse.down = true;
       const rect = canvas.getBoundingClientRect();
       const t = e.touches[0];
       mouse.x = (t.clientX - rect.left) * (canvas.width / rect.width);
       mouse.y = (t.clientY - rect.top) * (canvas.height / rect.height);
     }, {passive:false});
     canvas.addEventListener('touchmove', (e) => {
       e.preventDefault();
       const rect = canvas.getBoundingClientRect();
       const t = e.touches[0];
       mouse.x = (t.clientX - rect.left) * (canvas.width / rect.width);
       mouse.y = (t.clientY - rect.top) * (canvas.height / rect.height);
     }, {passive:false});
     canvas.addEventListener('touchend', (e) => { mouse.down = false; }, 
{passive:false});
   
   })();
   </script>
   </body>
   </html>
   
   
   ### 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscr...@cordova.apache.org.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscr...@cordova.apache.org
For additional commands, e-mail: issues-h...@cordova.apache.org

Reply via email to