TP-Docs
HTML5 Icon HTML5 Icon HTML5 Icon
TP on Social Media

Recent

Welcome to TinyPortal. Please login or sign up.

Members
  • Total Members: 3,965
  • Latest: boruko
Stats
  • Total Posts: 195,976
  • Total Topics: 21,319
  • Online today: 602
  • Online ever: 8,223 (February 19, 2025, 04:35:35 AM)
Users Online
  • Users: 0
  • Guests: 245
  • Total: 245

Space invader game

Started by Kernal, March 07, 2026, 04:54:00 PM

Previous topic - Next topic

0 Members and 2 Guests are viewing this topic.

Kernal

Coded a simple space invader game that play`s inside a TP block just for fun Kernal coded 2026

<!--Space Invaders Kernal coded for fun 2026 -->
<div id="tp-spaceinvader" style="width:320px; height:400px; background:#000; position:relative; overflow:hidden; border:2px solid #ccc; border-radius:5px;">
  <canvas id="invaderCanvas" width="320" height="400"></canvas>
  <div id="scoreDisplay" style="position:absolute; top:5px; left:5px; color:#fff; font-family:monospace; font-size:14px;">Score: 0</div>
</div>

<script>
(function(){
  const canvas = document.getElementById('invaderCanvas');
  const ctx = canvas.getContext('2d');
  const scoreDisplay = document.getElementById('scoreDisplay');

  const WIDTH = canvas.width;
  const HEIGHT = canvas.height;

  // Player
  const player = {
    x: WIDTH/2 - 15,
    y: HEIGHT - 30,
    width: 30,
    height: 10,
    color: 'lime',
    speed: 5,
    dx: 0
  };

  // Bullets
  const bullets = [];

  // Invader settings
  const rows = 3;
  const cols = 6;
  const invaderWidth = 25;
  const invaderHeight = 15;
  const invaderSpacingX = 10;
  const invaderSpacingY = 20;
  let invaderDirection = 1; // 1 = right, -1 = left
  let invaderSpeed = 0.5;

  // Game state
  let invaders = [];
  let score = 0;
  let gameOver = false;
  let gameWon = false;

  function initInvaders(){
    invaders = [];
    for(let r=0;r<rows;r++){
      for(let c=0;c<cols;c++){
        invaders.push({
          x: c*(invaderWidth+invaderSpacingX)+20,
          y: r*(invaderHeight+invaderSpacingY)+20,
          width: invaderWidth,
          height: invaderHeight,
          alive: true,
          color: 'red'
        });
      }
    }
  }

  initInvaders();

  // Key controls
  document.addEventListener('keydown', e => {
    if(e.key === 'ArrowLeft') player.dx = -player.speed;
    if(e.key === 'ArrowRight') player.dx = player.speed;
    if((e.key === ' ' || e.key === 'ArrowUp') && !gameOver) shoot();
    if(e.key === 'r' && (gameOver || gameWon)) restart();
  });

  document.addEventListener('keyup', e => {
    if(e.key === 'ArrowLeft' || e.key === 'ArrowRight') player.dx = 0;
  });

  // Mobile touch controls
  canvas.addEventListener('touchstart', e => {
    const touch = e.touches[0];
    if(touch.clientX < WIDTH/2) player.dx = -player.speed;
    else player.dx = player.speed;
    shoot();
    e.preventDefault();
  });

  canvas.addEventListener('touchend', e => {
    player.dx = 0;
    e.preventDefault();
  });

  function shoot(){
    bullets.push({x: player.x + player.width/2 - 2, y: player.y, width: 4, height: 10, color:'yellow'});
  }

  function update(){
    if(gameOver || gameWon) return;

    // Move player
    player.x += player.dx;
    if(player.x < 0) player.x = 0;
    if(player.x + player.width > WIDTH) player.x = WIDTH - player.width;

    // Move bullets
    for(let i = bullets.length-1;i>=0;i--){
      bullets[i].y -= 6;
      if(bullets[i].y < 0) bullets.splice(i,1);
    }

    // Move invaders
    let moveDown = false;
    for(let inv of invaders){
      if(!inv.alive) continue;
      inv.x += invaderDirection * invaderSpeed;
      if(inv.x + inv.width >= WIDTH || inv.x <=0) moveDown = true;
    }
    if(moveDown){
      invaderDirection *= -1;
      for(let inv of invaders){
        inv.y += 10;
        if(inv.y + inv.height >= player.y) gameOver = true;
      }
    }

    // Collision detection
    for(let i = bullets.length-1;i>=0;i--){
      for(let j = 0;j<invaders.length;j++){
        let inv = invaders[j];
        if(inv.alive && bullets[i].x < inv.x+inv.width && bullets[i].x+bullets[i].width>inv.x && bullets[i].y<inv.y+inv.height && bullets[i].y+bullets[i].height>inv.y){
          inv.alive = false;
          bullets.splice(i,1);
          score += 10;
          break;
        }
      }
    }

    // Check victory
    if(invaders.every(inv=>!inv.alive)) gameWon = true;

    scoreDisplay.textContent = "Score: " + score;
  }

  function draw(){
    ctx.clearRect(0,0,WIDTH,HEIGHT);

    // Draw player
    ctx.fillStyle = player.color;
    ctx.fillRect(player.x, player.y, player.width, player.height);

    // Draw bullets
    for(let b of bullets){
      ctx.fillStyle = b.color;
      ctx.fillRect(b.x,b.y,b.width,b.height);
    }

    // Draw invaders
    for(let inv of invaders){
      if(inv.alive){
        ctx.fillStyle = inv.color;
        ctx.fillRect(inv.x,inv.y,inv.width,inv.height);
      }
    }

    // Game over / victory text
    if(gameOver || gameWon){
      ctx.fillStyle = 'white';
      ctx.font = '20px monospace';
      ctx.textAlign = 'center';
      if(gameOver) ctx.fillText("GAME OVER Sucker lol! Press R", WIDTH/2, HEIGHT/2);
      if(gameWon) ctx.fillText("YOU WIN Dam! Press R", WIDTH/2, HEIGHT/2);
    }
  }

  function loop(){
    update();
    draw();
    requestAnimationFrame(loop);
  }

  function restart(){
    bullets.length = 0;
    player.x = WIDTH/2 - player.width/2;
    player.dx = 0;
    score = 0;
    gameOver = false;
    gameWon = false;
    initInvaders();
  }

  loop();
})();
</script>


So the instructions for the game and do we read the instructions lol i never did i just pressed everything LMAO.

Anyway if you like these are the instructions below  :knuppel2:

Score tracking (10 points per invader)

Game Over if invaders reach the player

Victory message when all invaders are destroyed that sounds good don`t it lol.

Restart with R key that`s if your losing LMAO  :2funny:

Mobile/touch yup made it work on a mobile phone lol (tap left/right side to move + shoot)

Just copy the code into the block and hey oh invaders we go!

If needed i can upgrade this to include real space invaders  :knuppel2: 
░█░█░█▀▀░█▀▄░█▀█░█▀█░█░░
░█▀▄░█▀▀░█▀▄░█░█░█▀█░█░░
░▀░▀░▀▀▀░▀░▀░▀░▀░▀░▀░▀▀▀

Kernal

Ok well thanks for all the add the invader graphic`s to the game so hey oh i did it anyway as tonight i`ve got lots of feel time on my coding fingers.....


So

Stage two of the Invaders game TP-Invaders!

<div style="text-align:center;font-family:Arial;color:white;">
<h3>Space Invaders</h3>

<div id="highscore">High Score: 0 | Kernal Coded 2026 for TinyPortal</div>

<canvas id="game" width="320" height="400" style="background:black;border:3px solid #444;margin-top:10px;"></canvas>

<p>
⬅️ ➡️ Move &nbsp;&nbsp; SPACE = Fire &nbsp;&nbsp; R = Restart
</p>
</div>

<script>

const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");

let player = {x:150,y:360,width:20,height:10,speed:4};

let bullets = [];
let invaders = [];

let score = 0;
let highScore = localStorage.getItem("tpInvaderHighScore") || 0;

let gameOver = false;
let exploding = false;
let explosionFrame = 0;

let keys = {};

const invaderSprite = [
"00111100",
"11111111",
"11011011",
"11111111",
"00111100",
"01011010",
"10000001",
"01000010"
];

function updateHighScore(){

if(score > highScore){
highScore = score;
localStorage.setItem("tpInvaderHighScore", highScore);
}

document.getElementById("highscore").innerHTML =
"High Score: " + highScore + " | Kernal Coded 2026 for TinyPortal";

}

updateHighScore();

function drawSprite(sprite,x,y,size,color){

ctx.fillStyle=color;

for(let r=0;r<sprite.length;r++){
for(let c=0;c<sprite[r].length;c++){

if(sprite[r][c]=="1"){
ctx.fillRect(x+c*size,y+r*size,size,size);

}

}
}

}

function createInvaders(){

invaders=[];

for(let r=0;r<4;r++){
for(let c=0;c<8;c++){

invaders.push({
x:40+c*30,
y:40+r*25,
alive:true
});

}
}

}

createInvaders();

let direction = 1;

function movePlayer(){

if(keys["ArrowLeft"]){
player.x -= player.speed;
}

if(keys["ArrowRight"]){
player.x += player.speed;
}

if(player.x < 0) player.x = 0;
if(player.x > canvas.width - player.width) player.x = canvas.width - player.width;

}

function drawPlayer(){

if(exploding){
drawExplosion();
return;
}

ctx.fillStyle="lime";
ctx.fillRect(player.x,player.y,player.width,player.height);
ctx.fillRect(player.x+7,player.y-5,6,5);

}

function drawExplosion(){

ctx.fillStyle="orange";

for(let i=0;i<12;i++){

ctx.fillRect(
player.x + Math.random()*20,
player.y + Math.random()*15,
3,
3
);

}

explosionFrame++;

if(explosionFrame > 25){
gameOver = true;
}

}

function drawBullets(){

ctx.fillStyle="yellow";

bullets.forEach(b=>{
ctx.fillRect(b.x,b.y,3,10);
});

}

function drawInvaders(){

invaders.forEach(i=>{
if(i.alive){
drawSprite(invaderSprite,i.x,i.y,2,"white");
}
});

}

function moveInvaders(){

let hitEdge=false;

invaders.forEach(i=>{

if(!i.alive) return;

i.x += direction;

if(i.x < 10 || i.x > 280){
hitEdge = true;
}

});

if(hitEdge){

direction *= -1;

invaders.forEach(i=>{
i.y += 10;
});

}

}

function updateBullets(){

bullets.forEach(b=> b.y -= 5);

bullets.forEach(b=>{

invaders.forEach(i=>{

if(i.alive &&
b.x > i.x &&
b.x < i.x + 16 &&
b.y > i.y &&
b.y < i.y + 16){

i.alive = false;
b.y = -100;

score += 10;
updateHighScore();

}

});

});

bullets = bullets.filter(b=> b.y > 0);

}

function checkPlayerHit(){

invaders.forEach(i=>{

if(i.alive && i.y + 16 >= player.y){
exploding = true;
}

});

}

function drawScore(){

ctx.fillStyle="white";
ctx.fillText("Score: " + score,10,15);

}

function drawGameOver(){

ctx.fillStyle="red";
ctx.font="20px Arial";
ctx.fillText("GAME OVER",100,200);

ctx.fillStyle="white";
ctx.font="14px Arial";
ctx.fillText("Press R to Restart",95,230);

}

function gameLoop(){

ctx.clearRect(0,0,canvas.width,canvas.height);

movePlayer();

drawPlayer();
drawBullets();
drawInvaders();
drawScore();

if(!exploding){

updateBullets();
moveInvaders();
checkPlayerHit();

}

if(gameOver){

drawGameOver();
return;

}

requestAnimationFrame(gameLoop);

}

document.addEventListener("keydown",e=>{

keys[e.key] = true;

if(e.key === " "){

bullets.push({
x:player.x+10,
y:player.y
});

}

if(e.key==="r" || e.key==="R"){
restartGame();
}

});

document.addEventListener("keyup",e=>{
keys[e.key] = false;
});

function restartGame(){

score = 0;
bullets = [];

exploding = false;
explosionFrame = 0;
gameOver = false;

createInvaders();

gameLoop();

}

gameLoop();

</script>
░█░█░█▀▀░█▀▄░█▀█░█▀█░█░░
░█▀▄░█▀▀░█▀▄░█░█░█▀█░█░░
░▀░▀░▀▀▀░▀░▀░▀░▀░▀░▀░▀▀▀

Kernal

Ok fixed a few things on this code so this is a new update that works as a space invader game should do.


<div style="text-align:center;font-family:Arial;color:white;">
<h3>Space Invaders</h3>

<div id="highscore">High Score: 0 | Kernal Coded 2026 for TinyPortal</div>

<canvas id="game" width="320" height="400" style="background:black;border:3px solid #444;margin-top:10px;"></canvas>

<p>
⬅️ ➡️ Move &nbsp;&nbsp; SPACE = Fire &nbsp;&nbsp; R = Restart
</p>
</div>

<script>

const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");

let player = {x:150,y:360,width:20,height:10,speed:4};

let bullets = [];
let invaders = [];

let score = 0;
let level = 1;

let highScore = localStorage.getItem("tpInvaderHighScore") || 0;

let gameOver = false;
let exploding = false;
let explosionFrame = 0;

let keys = {};

let direction = 1;
let invaderSpeed = 1;

const invaderSprite = [
"00111100",
"11111111",
"11011011",
"11111111",
"00111100",
"01011010",
"10000001",
"01000010"
];

function updateHighScore(){

if(score > highScore){
highScore = score;
localStorage.setItem("tpInvaderHighScore", highScore);
}

document.getElementById("highscore").innerHTML =
"High Score: " + highScore + " | Kernal Coded 2026 for TinyPortal";

}

updateHighScore();

function drawSprite(sprite,x,y,size,color){

ctx.fillStyle=color;

for(let r=0;r<sprite.length;r++){
for(let c=0;c<sprite[r].length;c++){

if(sprite[r][c]=="1"){
ctx.fillRect(x+c*size,y+r*size,size,size);
}

}
}

}

function createInvaders(){

invaders=[];

for(let r=0;r<4;r++){
for(let c=0;c<8;c++){

invaders.push({
x:40+c*30,
y:40+r*25,
alive:true
});

}
}

}

createInvaders();

function movePlayer(){

if(keys["ArrowLeft"]) player.x -= player.speed;
if(keys["ArrowRight"]) player.x += player.speed;

if(player.x < 0) player.x = 0;
if(player.x > canvas.width-player.width)
player.x = canvas.width-player.width;

}

function drawPlayer(){

if(exploding){
drawExplosion();
return;
}

ctx.fillStyle="lime";
ctx.fillRect(player.x,player.y,player.width,player.height);
ctx.fillRect(player.x+7,player.y-5,6,5);

}

function drawExplosion(){

ctx.fillStyle="orange";

for(let i=0;i<12;i++){

ctx.fillRect(
player.x + Math.random()*20,
player.y + Math.random()*15,
3,
3
);

}

explosionFrame++;

if(explosionFrame > 25){
gameOver = true;
}

}

function drawBullets(){

ctx.fillStyle="yellow";

bullets.forEach(b=>{
ctx.fillRect(b.x,b.y,3,10);
});

}

function drawInvaders(){

invaders.forEach(i=>{
if(i.alive){
drawSprite(invaderSprite,i.x,i.y,2,"white");
}
});

}

function moveInvaders(){

let hitEdge=false;

invaders.forEach(i=>{

if(!i.alive) return;

i.x += direction * invaderSpeed;

if(i.x < 10 || i.x > 280){
hitEdge=true;
}

});

if(hitEdge){

direction *= -1;

invaders.forEach(i=>{
i.y += 10;
});

}

}

function updateBullets(){

bullets.forEach(b=> b.y -= 6);

bullets.forEach(b=>{

invaders.forEach(i=>{

if(i.alive &&
b.x > i.x &&
b.x < i.x + 16 &&
b.y > i.y &&
b.y < i.y + 16){

i.alive=false;
b.y=-100;

score+=10;
updateHighScore();

}

});

});

bullets = bullets.filter(b=> b.y > 0);

}

function checkPlayerHit(){

invaders.forEach(i=>{
if(i.alive && i.y + 16 >= player.y){
exploding=true;
}
});

}

function checkWaveComplete(){

let alive = invaders.some(i=>i.alive);

if(!alive){

level++;
invaderSpeed += 0.4;

createInvaders();

}

}

function drawScore(){

ctx.fillStyle="white";
ctx.fillText("Score: "+score,10,15);
ctx.fillText("Level: "+level,250,15);

}

function drawGameOver(){

ctx.fillStyle="red";
ctx.font="20px Arial";
ctx.fillText("GAME OVER",100,200);

ctx.fillStyle="white";
ctx.font="14px Arial";
ctx.fillText("Press R to Restart",95,230);

}

function gameLoop(){

ctx.clearRect(0,0,canvas.width,canvas.height);

movePlayer();

drawPlayer();
drawBullets();
drawInvaders();
drawScore();

if(!exploding){

updateBullets();
moveInvaders();
checkPlayerHit();
checkWaveComplete();

}

if(gameOver){

drawGameOver();
return;

}

requestAnimationFrame(gameLoop);

}

document.addEventListener("keydown",e=>{

keys[e.key] = true;

if(e.key === " "){

bullets.push({
x:player.x+10,
y:player.y
});

}

if(e.key==="r" || e.key==="R"){
restartGame();
}

});

document.addEventListener("keyup",e=>{
keys[e.key] = false;
});

function restartGame(){

score=0;
level=1;
invaderSpeed=1;

bullets=[];

exploding=false;
explosionFrame=0;
gameOver=false;

createInvaders();

gameLoop();

}

gameLoop();

</script>
░█░█░█▀▀░█▀▄░█▀█░█▀█░█░░
░█▀▄░█▀▀░█▀▄░█░█░█▀█░█░░
░▀░▀░▀▀▀░▀░▀░▀░▀░▀░▀░▀▀▀

Kernal

So as you can see on this space invader game that I'm developing on here we now have graphical invaders,...If you follow the code you`ll see that i coded the invader`s in binary as a sprite as they should be coded.

All the code here is Tiny Portal Block compatible and i think Lurk will be testing the code as i go to make sure it`s fine for your TP blocks....


So next step i think is adding barriers and a nice star field behind the invaders.

 ::)

░█░█░█▀▀░█▀▄░█▀█░█▀█░█░░
░█▀▄░█▀▀░█▀▄░█░█░█▀█░█░░
░▀░▀░▀▀▀░▀░▀░▀░▀░▀░▀░▀▀▀

Kernal

#4
If i was honest i coded this game day`s ago to the point it is here  O0

TP-Invaders

<div style="text-align:center;color:#00ff66;font-family:monospace">
<h3>SPACE INVADERS</h3>
<div id="highscore">High Score: 0 | Kernal Coded 2026 for TinyPortal</div>
<canvas id="game" width="360" height="440"
style="background:black;border:6px solid #00ff66;box-shadow:0 0 30px #00ff66 inset;"></canvas>
<p>⬅ ➡ Move | SPACE Fire | R Restart</p>
</div>

<script>
const canvas = document.getElementById("game")
const ctx = canvas.getContext("2d")

// Player
let player={x:170,y:400,w:24,h:12,speed:4}
// Bullets
let bullets=[], enemyBullets=[]
// Invaders
let invaders=[]
// Shields
let shields=[]
// UFO
let ufo=null
// Game state
let level=1, score=0, direction=1, speed=0.6
let keys={}, lastShot=0, fireDelay=300
let highScore=localStorage.getItem("tpInvaderHighScore")||0
let gameOver=false

document.getElementById("highscore").innerHTML=
"High Score: "+highScore+" | Kernal Coded 2026 for TinyPortal"

// Starfield background
const stars=[]
for(let i=0;i<80;i++) stars.push({x:Math.random()*360,y:Math.random()*440,r:Math.random()*2+1,speed:Math.random()*0.5+0.2})
function drawStars(){ ctx.fillStyle="white"; stars.forEach(s=>{ ctx.fillRect(s.x,s.y,s.r,s.r); s.y+=s.speed; if(s.y>440){s.y=0;s.x=Math.random()*360} }) }

// Sprites
const alienA1=["00111100","01111110","11011011","11111111","01111110","01000010"]
const alienA2=["00111100","01111110","11111111","11011011","11111111","00100100"]
let animFrame=0

function drawSprite(sprite,x,y,s,c){
  ctx.fillStyle=c
  for(let r=0;r<sprite.length;r++)
    for(let d=0;d<sprite[r].length;d++)
      if(sprite[r][d]=="1") ctx.fillRect(x+d*s,y+r*s,s,s)
}

// Sound helpers
const audioCtx = new (window.AudioContext || window.webkitAudioContext)()
function playTone(freq,duration=0.1,volume=0.2){
  let osc=audioCtx.createOscillator()
  let gain=audioCtx.createGain()
  osc.connect(gain); gain.connect(audioCtx.destination)
  osc.type="square"; osc.frequency.value=freq
  gain.gain.value=volume
  osc.start()
  osc.stop(audioCtx.currentTime+duration)
}

// Explosions
let explosions=[]
function explosion(x,y){
  for(let i=0;i<5;i++) explosions.push({x:x+Math.random()*16,y:y+Math.random()*16,r:Math.random()*2+1,l:10})
  playTone(100+Math.random()*300,0.1)
}

// Invaders
function createInvaders(){ invaders=[]; for(let r=0;r<5;r++) for(let c=0;c<10;c++) invaders.push({x:40+c*28,y:50+r*26,alive:true}) }
function drawInvaders(){ invaders.forEach(i=>{ if(!i.alive) return; let sprite=animFrame?alienA1:alienA2; drawSprite(sprite,i.x,i.y,2,"white") }) }
function moveInvaders(){ let edge=false; invaders.forEach(i=>{ if(!i.alive) return; i.x+=direction*speed; if(i.x<10||i.x>320) edge=true }); if(edge){ direction*=-1; invaders.forEach(i=>i.y+=10) } }
function alienShoot(){ let alive=invaders.filter(i=>i.alive); if(alive.length){ let s=alive[Math.floor(Math.random()*alive.length)]; enemyBullets.push({x:s.x+8,y:s.y+12}); playTone(400,0.05) } }

// Shields
function createShields(){ shields=[]; for(let i=0;i<4;i++) shields.push({x:50+i*70,y:340,hp:30}) }
function drawShields(){ ctx.fillStyle="#00ff66"; shields.forEach(s=>{for(let i=0;i<s.hp;i++) ctx.fillRect(s.x+(i%6)*5,s.y+Math.floor(i/6)*5,4,4)}) }

// Player
function movePlayer(){ if(keys["ArrowLeft"]) player.x-=player.speed; if(keys["ArrowRight"]) player.x+=player.speed; if(player.x<0) player.x=0; if(player.x>canvas.width-player.w) player.x=canvas.width-player.w }
function drawPlayer(){ ctx.fillStyle="#00ff66"; ctx.fillRect(player.x,player.y,player.w,player.h); ctx.fillRect(player.x+9,player.y-6,6,6) }

// Bullets
function drawBullets(){ ctx.fillStyle="yellow"; bullets.forEach(b=>ctx.fillRect(b.x,b.y,3,10)); ctx.fillStyle="red"; enemyBullets.forEach(b=>ctx.fillRect(b.x,b.y,3,10)) }
function updateBullets(){
  bullets.forEach(b=>b.y-=6)
  enemyBullets.forEach(b=>b.y+=4)
  // Player bullets hit invaders or shields
  bullets.forEach(b=>{
    invaders.forEach(i=>{ if(i.alive && b.x>i.x && b.x<i.x+16 && b.y>i.y && b.y<i.y+16){ i.alive=false; b.y=-100; score+=10; saveHigh(); explosion(i.x,i.y) } })
    shields.forEach(s=>{ if(s.hp>0 && b.x>s.x && b.x<s.x+30 && b.y>s.y && b.y<s.y+30){ s.hp-=1; b.y=-100 } })
  })
  // Enemy bullets hit player or shields
  enemyBullets.forEach(b=>{
    if(b.x>player.x && b.x<player.x+player.w && b.y>player.y){ explosion(player.x,player.y); gameOver=true; playTone(50,0.3) }
    shields.forEach(s=>{ if(s.hp>0 && b.x>s.x && b.x<s.x+30 && b.y>s.y && b.y<s.y+30){ s.hp-=1; b.y=-100 } })
  })
  bullets=bullets.filter(b=>b.y>0)
  enemyBullets=enemyBullets.filter(b=>b.y<440)
}

// UFO
function spawnUFO(){ if(!ufo && Math.random()<0.003) ufo={x:-40,y:25,s:2} }
function updateUFO(){ if(!ufo) return; ufo.x+=ufo.s; ctx.fillStyle="red"; ctx.fillRect(ufo.x,ufo.y,30,12); bullets.forEach(b=>{ if(b.x>ufo.x && b.x<ufo.x+30 && b.y>ufo.y){ score+=150; ufo=null; playTone(600,0.1) } }); if(ufo && ufo.x>380) ufo=null }

// Explosions
function drawExplosions(){ ctx.fillStyle="orange"; explosions.forEach(e=>ctx.fillRect(e.x,e.y,e.r,e.r)); explosions.forEach(e=>e.l--); explosions=explosions.filter(e=>e.l>0) }

// Level / HUD
function checkWave(){ if(!invaders.some(i=>i.alive)){ level++; speed+=0.25; createInvaders() } }
function hud(){ ctx.fillStyle="#00ff66"; ctx.fillText("Score "+score,10,20); ctx.fillText("Level "+level,290,20) }
function saveHigh(){ if(score>highScore){ highScore=score; localStorage.setItem("tpInvaderHighScore",highScore) }; document.getElementById("highscore").innerHTML="High Score: "+highScore+" | Kernal Coded 2026 for TinyPortal" }

// Game reset
function resetGame(){ score=0; level=1; speed=0.6; bullets=[]; enemyBullets=[]; createInvaders(); createShields(); ufo=null; gameOver=false }

// Game loop
function gameLoop(){
  ctx.clearRect(0,0,canvas.width,canvas.height)
  drawStars()
  if(gameOver) resetGame()
  animFrame^=1
  movePlayer()
  drawPlayer()
  drawInvaders()
  drawBullets()
  drawShields()
  drawExplosions()
  updateBullets()
  moveInvaders()
  spawnUFO()
  updateUFO()
  checkWave()
  if(Math.random()<0.02) alienShoot()
  hud()
  requestAnimationFrame(gameLoop)
}

// Controls
document.addEventListener("keydown",e=>{
  keys[e.key]=true
  if(e.key===" "){ let now=Date.now(); if(now-lastShot>fireDelay && bullets.length<1){ bullets.push({x:player.x+11,y:player.y}); lastShot=now; playTone(800,0.05) } }
  if(e.key==="r"||e.key==="R") resetGame()
})
document.addEventListener("keyup",e=>keys[e.key]=false)

// Init
createInvaders()
createShields()
gameLoop()
</script>
░█░█░█▀▀░█▀▄░█▀█░█▀█░█░░
░█▀▄░█▀▀░█▀▄░█░█░█▀█░█░░
░▀░▀░▀▀▀░▀░▀░▀░▀░▀░▀░▀▀▀

Kernal

So i`m thinking of adding rapid fire to the game and the space ship above well  :2funny: think it need`s a little graphics adding to it too... >:D

So this game can be added into your Tiny Portal block and it should work fine.

But still it`s work in progress any idea`s anyone that you would like added to the game,..

 :hmm:
░█░█░█▀▀░█▀▄░█▀█░█▀█░█░░
░█▀▄░█▀▀░█▀▄░█░█░█▀█░█░░
░▀░▀░▀▀▀░▀░▀░▀░▀░▀░▀░▀▀▀

Kernal

 :)

So i changed the character`s to black text and well if your all following this code then it`s code in progress be fun to follow with all the upgrades to the game.

 :D <div style="text-align:center;color:#000066;font-family:monospace">
<h3>SPACE INVADERS</h3>
<div id="highscore">High Score: 0 | Kernal Coded 2026 for TinyPortal</div>
<canvas id="game" width="360" height="440"
style="background:black;border:6px solid #00ff66;box-shadow:0 0 30px #00ff66 inset;"></canvas>

<div style="margin-top:5px;">
  <button id="leftBtn" style="width:60px;height:40px;margin:5px;">⬅</button>
  <button id="fireBtn" style="width:60px;height:40px;margin:5px;">🔥</button>
  <button id="rightBtn" style="width:60px;height:40px;margin:5px;">➡</button>
</div>
<p>⬅ ➡ Move | SPACE Fire | R Restart</p>
</div>

<script>
const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");

// Player
let player={x:170,y:400,w:24,h:12,speed:4,flash:0}
// Bullets
let bullets=[], enemyBullets=[]
// Invaders
let invaders=[]
// Shields
let shields=[]
// UFO
let ufo=null
let ufoScoreFlash=[]
// Game state
let level=1, score=0, direction=1, speed=0.6
let keys={}, lastShot=0, fireDelay=300
let highScore=localStorage.getItem("tpInvaderHighScore")||0
let gameOver=false

document.getElementById("highscore").innerHTML=
"High Score: "+highScore+" | Kernal Coded 2026 for TinyPortal"

// Starfield
const stars=[]
for(let i=0;i<80;i++) stars.push({x:Math.random()*360,y:Math.random()*440,r:Math.random()*2+1,speed:Math.random()*0.5+0.2})
function drawStars(){ ctx.fillStyle="white"; stars.forEach(s=>{ ctx.fillRect(s.x,s.y,s.r,s.r); s.y+=s.speed; if(s.y>440){s.y=0;s.x=Math.random()*360} }) }

// Sprites
const alienA1=["00111100","01111110","11011011","11111111","01111110","01000010"]
const alienA2=["00111100","01111110","11111111","11011011","11111111","00100100"]
const playerShip=["00100","01110","11111"]
const ufoSprite=["00111100","01111110","11111111","01111110"]

let marchStep=0, marchTimer=0, marchInterval=30
function drawSprite(sprite,x,y,s,c){
  ctx.fillStyle=c
  for(let r=0;r<sprite.length;r++)
    for(let d=0;d<sprite[r].length;d++)
      if(sprite[r][d]=="1") ctx.fillRect(x+d*s,y+r*s,s,s)
}

// Sounds
const audioCtx = new (window.AudioContext || window.webkitAudioContext)()
function playTone(freq,duration=0.1,volume=0.2){
  let osc=audioCtx.createOscillator()
  let gain=audioCtx.createGain()
  osc.connect(gain); gain.connect(audioCtx.destination)
  osc.type="square"; osc.frequency.value=freq
  gain.gain.value=volume
  osc.start()
  osc.stop(audioCtx.currentTime+duration)
}

// Explosions
let explosions=[]
function explosion(x,y){
  for(let i=0;i<8;i++) explosions.push({x:x+Math.random()*16,y:y+Math.random()*16,r:Math.random()*2+1,l:12,color:Math.random()>0.5?"yellow":"white"})
  playTone(600,0.1)
}

// Create invaders
function createInvaders(){
  invaders=[];
  for(let r=0;r<5;r++)
    for(let c=0;c<10;c++) invaders.push({x:40+c*28,y:50+r*26,alive:true,flash:0})
}
function drawInvaders(){
  invaders.forEach(i=>{
    if(!i.alive) return
    let sprite=(marchStep%2)?alienA1:alienA2
    if(i.flash>0){ ctx.fillStyle="white"; i.flash--; ctx.fillRect(i.x,i.y,16,16); return }
    drawSprite(sprite,i.x,i.y,2,"white")
  })
}

// Invader marching rhythm
function marchInvaders(){
  marchTimer++
  if(marchTimer>marchInterval){ marchTimer=0; marchStep=(marchStep+1)%4; playTone(100+marchStep*50,0.05) }
}

// Invader movement
function moveInvaders(){
  let edge=false;
  invaders.forEach(i=>{ if(!i.alive) return; i.x+=direction*speed; if(i.x<10||i.x>320) edge=true });
  if(edge){ direction*=-1; invaders.forEach(i=>i.y+=10); }
}

// Alien shooting
function alienShoot(){
  let alive=invaders.filter(i=>i.alive);
  if(alive.length && Math.random()<0.01*level){
    let s=alive[Math.floor(Math.random()*alive.length)];
    enemyBullets.push({x:s.x+8,y:s.y+12});
    playTone(400,0.05)
  }
}

// Shields
function createShields(){
  shields=[];
  for(let i=0;i<4;i++) shields.push({x:50+i*70,y:340,hp:30})
}
function drawShields(){
  ctx.fillStyle="#00ff66";
  shields.forEach(s=>{
    for(let i=0;i<s.hp;i++) ctx.fillRect(s.x+(i%6)*5,s.y+Math.floor(i/6)*5,4,4)
  })
}

// Player
function movePlayer(){
  if(keys["ArrowLeft"]||keys["TouchLeft"]) player.x-=player.speed;
  if(keys["ArrowRight"]||keys["TouchRight"]) player.x+=player.speed;
  if(player.x<0) player.x=0; if(player.x>canvas.width-player.w) player.x=canvas.width-player.w
}
function drawPlayer(){
  if(player.flash>0){ ctx.fillStyle="white"; ctx.fillRect(player.x-2,player.y-6,player.w+4,player.h); player.flash--; return }
  drawSprite(playerShip,player.x,player.y-6,4,"#00ff66");
}

// Bullets
function drawBullets(){ ctx.fillStyle="yellow"; bullets.forEach(b=>ctx.fillRect(b.x,b.y,3,10)); ctx.fillStyle="red"; enemyBullets.forEach(b=>ctx.fillRect(b.x,b.y,3,10)) }
function updateBullets(){
  bullets.forEach(b=>b.y-=6)
  enemyBullets.forEach(b=>b.y+=4)
  bullets.forEach(b=>{
    invaders.forEach(i=>{
      if(i.alive && b.x>i.x && b.x<i.x+16 && b.y>i.y && b.y<i.y+16){
        i.alive=false; i.flash=4; b.y=-100; score+=10; saveHigh(); explosion(i.x,i.y)
      }
    })
    shields.forEach(s=>{ if(s.hp>0 && b.x>s.x && b.x<s.x+30 && b.y>s.y && b.y<s.y+30){ s.hp-=1; b.y=-100 } })
  })
  enemyBullets.forEach(b=>{
    if(b.x>player.x && b.x<player.x+player.w && b.y>player.y){
      player.flash=6; explosion(player.x,player.y); gameOver=true; playTone(80,0.3)
    }
    shields.forEach(s=>{ if(s.hp>0 && b.x>s.x && b.x<s.x+30 && b.y>s.y && b.y<s.y+30){ s.hp-=1; b.y=-100 } })
  })
  bullets=bullets.filter(b=>b.y>0)
  enemyBullets=enemyBullets.filter(b=>b.y<440)
}

// Invaders hitting shields or base
function checkInvaderCollision(){
  let baseHit=false
  invaders.forEach(i=>{
    if(!i.alive) return;
    // Hit shields
    shields.forEach(s=>{
      if(s.hp>0 && i.y+16 >= s.y && i.x+16 > s.x && i.x < s.x+30){
        s.hp-=2; // invader destroys shield
        i.flash=4; // invader flashes
      }
    })
    // Hit player area
    if(i.y+16 >= player.y){
      // Check if shields already gone
      let shieldRemaining=shields.some(s=>s.hp>0)
      if(!shieldRemaining){
        baseHit=true;
        i.flash=4;
        player.flash=6;
        explosion(player.x,player.y);
        explosion(i.x,i.y);
      }
    }
  })
  if(baseHit) gameOver=true
}

// UFO
function spawnUFO(){ if(!ufo && Math.random()<0.003 + level*0.0005) ufo={x:-40,y:25,s:2} }
function updateUFO(){
  if(!ufo) return;
  ufo.x+=ufo.s;
  drawSprite(ufoSprite,ufo.x,ufo.y,2,"red");
  bullets.forEach(b=>{
    if(b.x>ufo.x && b.x<ufo.x+32 && b.y>ufo.y){
      score+=150;
      ufoScoreFlash.push({x:ufo.x,y:ufo.y-10,value:150,timer:20});
      ufo=null;
      playTone(700,0.15)
    }
  });
  if(ufo && ufo.x>380) ufo=null
}

// Draw UFO score flashes
function drawUfoScoreFlash(){
  ufoScoreFlash.forEach(f=>{
    ctx.fillStyle="yellow";
    ctx.font="14px monospace";
    ctx.fillText(f.value,f.x,f.y);
    f.timer--;
  });
  ufoScoreFlash=ufoScoreFlash.filter(f=>f.timer>0)
}

// Explosions
function drawExplosions(){
  explosions.forEach(e=>{ ctx.fillStyle=e.color; ctx.fillRect(e.x,e.y,e.r,e.r); e.l--; });
  explosions=explosions.filter(e=>e.l>0)
}

// Level / HUD
function checkWave(){ if(!invaders.some(i=>i.alive)){ level++; speed+=0.15 + level*0.05; createInvaders() } }
function hud(){ ctx.fillStyle="#00ff66"; ctx.fillText("Score "+score,10,20); ctx.fillText("Level "+level,290,20) }
function saveHigh(){ if(score>highScore){ highScore=score; localStorage.setItem("tpInvaderHighScore",highScore) }; document.getElementById("highscore").innerHTML="High Score: "+highScore+" | Kernal Coded 2026 for TinyPortal" }

// Game reset
function resetGame(){
  score=0; level=1; speed=0.6; bullets=[]; enemyBullets=[];
  createInvaders(); createShields(); ufo=null; gameOver=false;
  marchStep=0; marchTimer=0; ufoScoreFlash=[]; player.flash=0
}

// Game loop
function gameLoop(){
  ctx.clearRect(0,0,canvas.width,canvas.height)
  drawStars()
  if(gameOver) resetGame()
  marchInvaders()
  movePlayer(); drawPlayer(); drawInvaders(); drawBullets(); drawShields(); drawExplosions(); updateBullets()
  moveInvaders(); spawnUFO(); updateUFO(); drawUfoScoreFlash(); checkWave(); checkInvaderCollision()
  if(Math.random()<0.02) alienShoot()
  hud()
  requestAnimationFrame(gameLoop)
}

// Controls
document.addEventListener("keydown",e=>{
  keys[e.key]=true
  if(e.key===" "){ let now=Date.now(); if(now-lastShot>fireDelay && bullets.length<1){ bullets.push({x:player.x+11,y:player.y}); lastShot=now; playTone(800,0.05) } }
  if(e.key==="r"||e.key==="R") resetGame()
})
document.addEventListener("keyup",e=>keys[e.key]=false)

// Mobile buttons
document.getElementById("leftBtn").addEventListener("touchstart",()=>keys["TouchLeft"]=true)
document.getElementById("leftBtn").addEventListener("touchend",()=>keys["TouchLeft"]=false)
document.getElementById("rightBtn").addEventListener("touchstart",()=>keys["TouchRight"]=true)
document.getElementById("rightBtn").addEventListener("touchend",()=>keys["TouchRight"]=false)
document.getElementById("fireBtn").addEventListener("touchstart",()=>{
  let now=Date.now()
  if(now-lastShot>fireDelay && bullets.length<1){ bullets.push({x:player.x+11,y:player.y}); lastShot=now; playTone(800,0.05) }
})

// Init
createInvaders()
createShields()
gameLoop()
</script>
░█░█░█▀▀░█▀▄░█▀█░█▀█░█░░
░█▀▄░█▀▀░█▀▄░█░█░█▀█░█░░
░▀░▀░▀▀▀░▀░▀░▀░▀░▀░▀░▀▀▀

Kernal

Last time i worked on a game was with Ocean software in Manchester squirting coded over a dial up phone  :2funny:
░█░█░█▀▀░█▀▄░█▀█░█▀█░█░░
░█▀▄░█▀▀░█▀▄░█░█░█▀█░█░░
░▀░▀░▀▀▀░▀░▀░▀░▀░▀░▀░▀▀▀

Kernal

#8
I think this game is complete now and I'm going to end it right here so this is my code for Tiny portal Kernal coded in 2026 who even thought I'd be coding this long I'm a c64 & Zx spectrum coder...6510 & z80 chips......And here i am still coding...  :uglystupid2:


Anyway i do hope you all liked the coding with that simple space invader game was somewhat fun to do i think!.....

Take good care and have a good day

KeRnAl




░█░█░█▀▀░█▀▄░█▀█░█▀█░█░░
░█▀▄░█▀▀░█▀▄░█░█░█▀█░█░░
░▀░▀░▀▀▀░▀░▀░▀░▀░▀░▀░▀▀▀

Kernal

#9
Made a bit of a upgrade blowing up space invaders and also added blowing up bases too this is a fun project and can be added to a tiny portal block for time out invaders, Oh i forgot to add that I've also added rapid fire just keep the space bar pressed or fire button to rapid fire the invaders!

Everyone`s welcome to add to this code to complete the ultimate Tinyportal space invader game be fun get involved add upgrades to the code and I'll work with you to update the game too, I`ve also added rapid fire with bases exploding on invader contact.

<html lang="en">
<head>
<meta charset="UTF-8">
<title>Space Invaders - TinyPortal</title>
<style>
  body { margin:0; font-family:monospace; background:black; color:#00ff66; text-align:center; }
  canvas { background:black; border:6px solid #00ff66; box-shadow:0 0 30px #00ff66 inset; display:block; margin:0 auto; }
  #startScreen {
    position:absolute; top:0; left:0; width:100%; height:100%;
    background:black; color:#00ff66; display:flex;
    flex-direction:column; justify-content:center; align-items:center;
  }
  #startScreen h1 { font-size:48px; margin:0; }
  #startScreen p { font-size:24px; margin:5px 0 30px; }
  #pressStart { font-size:20px; margin-top:20px; }
  #gameContainer { display:none; }
  #muteBtn { position:absolute; top:10px; right:10px; width:80px; height:35px; font-size:16px; }
  #highscore { font-size:16px; margin-bottom:5px; }
  button { width:60px; height:40px; margin:5px; font-size:18px; }
</style>
</head>
<body>

<div id="startScreen">
  <h1>SPACE INVADERS</h1>
  <p>Kernal Coded - TinyPortal 2026</p>
  <div id="pressStart">PRESS START</div>
</div>

<div id="gameContainer">
  <div id="highscore">High Score: 0 &nbsp;&nbsp; Kernal Coded For TinyPortal</div>
  <canvas id="game" width="360" height="440"></canvas>
  <div>
    <button id="leftBtn">⬅</button>
    <button id="fireBtn">🔥</button>
    <button id="rightBtn">➡</button>
  </div>
  <p>⬅ ➡ Move | HOLD FIRE | R Restart</p>

  <audio id="bgMusic" src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3" loop autoplay></audio>
  <button id="muteBtn">🔊 Mute</button>
</div>

<script>
const startScreen = document.getElementById("startScreen");
const pressStart = document.getElementById("pressStart");
const gameContainer = document.getElementById("gameContainer");
const bgMusic = document.getElementById("bgMusic");
const muteBtn = document.getElementById("muteBtn");

let blink = true;
setInterval(() => {
  pressStart.style.visibility = blink ? 'visible' : 'hidden';
  blink = !blink;
}, 500);

document.addEventListener("keydown", startGame);
document.addEventListener("click", startGame);
function startGame() {
  startScreen.style.display = "none";
  gameContainer.style.display = "block";
  draw();
  document.removeEventListener("keydown", startGame);
  document.removeEventListener("click", startGame);
}

muteBtn.onclick = () => {
  if(bgMusic.paused){ bgMusic.play(); muteBtn.textContent="🔊 Mute"; }
  else{ bgMusic.pause(); muteBtn.textContent="🔇 Unmute"; }
};

const canvas = document.getElementById("game");
const x = canvas.getContext("2d");
const leftBtn = document.getElementById("leftBtn");
const rightBtn = document.getElementById("rightBtn");
const fireBtn = document.getElementById("fireBtn");

const stars=[];
for(let i=0;i<80;i++) stars.push({x:Math.random()*360,y:Math.random()*440,r:Math.random()*2+1,speed:Math.random()*0.5+0.2});
function drawStars(){ x.fillStyle="white"; stars.forEach(s=>{ x.fillRect(s.x,s.y,s.r,s.r); s.y+=s.speed; if(s.y>440){ s.y=0; s.x=Math.random()*360 } }); }

let player={x:170,y:400,w:24,h:12,s:4};
let bullets=[], enemyBullets=[], invaders=[], explosions=[], shields=[], ufo=null;
let keys={}, firing=false, lastShot=0, fireDelay=120;
let level=1, score=0, dir=1, speed=0.6, gameOver=false;

const alien1=["00111100","01111110","11011011","11111111","01111110","01000010"];
const alien2=["00111100","01111110","11111111","11011011","11111111","00100100"];
const ship=["00100","01110","11111"];
const ufoSprite=["00111100","01111110","11111111","01111110"];

let anim=0, animTick=0;

const A=new (window.AudioContext||window.webkitAudioContext)();
function beep(f,d=0.1){ let o=A.createOscillator(),g=A.createGain(); o.connect(g);g.connect(A.destination); o.frequency.value=f;o.start();o.stop(A.currentTime+d) }

function drawSprite(s,xp,yp,sc,col){ x.fillStyle=col; for(let r=0;r<s.length;r++) for(let d=0;d<s[r].length;d++) if(s[r][d]=="1") x.fillRect(xp+d*sc,yp+r*sc,sc,sc); }
function makeInvaders(){ invaders=[]; for(let r=0;r<5;r++) for(let c=0;c<10;c++) invaders.push({x:40+c*28,y:50+r*26,a:true}); }
function makeShields(){ shields=[]; for(let i=0;i<3;i++) shields.push({x:50+i*90,y:340,hp:40}); }
function drawShields(){ x.fillStyle="#00ff66"; shields.forEach(s=>{ for(let i=0;i<s.hp;i++) x.fillRect(s.x+(i%8)*4,s.y+Math.floor(i/8)*4,3,3) }); }
function movePlayer(){ if(keys["ArrowLeft"]||keys["TouchLeft"]) player.x-=player.s; if(keys["ArrowRight"]||keys["TouchRight"]) player.x+=player.s; player.x=Math.max(0,Math.min(336,player.x)) }
function shoot(){ let now=Date.now(); if(firing && now-lastShot>fireDelay){ bullets.push({x:player.x+11,y:player.y}); lastShot=now; beep(800,0.05) } }

function updateBullets(){
  bullets.forEach(b=>b.y-=5);
  enemyBullets.forEach(b=>b.y+=3);

  bullets.forEach(b=>{
    // INVADERS
    invaders.forEach(i=>{
      if(i.a && b.x>i.x && b.x<i.x+16 && b.y>i.y && b.y<i.y+16){
        i.a=false;
        score+=10;
        explode(i.x,i.y);
        b.y=-100;
      }
    });

    // 🚀 UFO HIT FIX
    if (ufo && b.x > ufo.x && b.x < ufo.x + 32 && b.y > ufo.y && b.y < ufo.y + 16) {
      score += 100;
      explode(ufo.x, ufo.y);
      ufo = null;
      b.y = -100;
      beep(1200, 0.2);
    }

    // SHIELDS
    shields.forEach(s=>{
      if(s.hp>0 && b.x>s.x && b.x<s.x+32 && b.y>s.y && b.y<s.y+32){
        s.hp--;
        b.y=-100;
      }
    });
  });

  enemyBullets.forEach(b=>{
    if(b.x>player.x && b.x<player.x+24 && b.y>player.y){
      explode(player.x,player.y);
      gameOver=true;
    }
    shields.forEach(s=>{
      if(s.hp>0 && b.x>s.x && b.x<s.x+32 && b.y>s.y && b.y<s.y+32){
        s.hp--;
        b.y=999;
      }
    });
  });

  bullets=bullets.filter(b=>b.y>0);
  enemyBullets=enemyBullets.filter(b=>b.y<440);
}

function alienShoot(){ let alive=invaders.filter(i=>i.a); if(alive.length && Math.random()<0.02+(level*0.01)){ let s=alive[Math.floor(Math.random()*alive.length)]; enemyBullets.push({x:s.x+8,y:s.y+12}); beep(400,0.05); } }
function moveInvaders(){ let edge=false; invaders.forEach(i=>{ if(!i.a) return; i.x+=dir*speed; if(i.x<10||i.x>320) edge=true; shields.forEach(s=>{ if(s.hp>0 && i.y+16>=s.y && i.x+16>s.x && i.x<s.x+32){ s.hp--; i.a=false; explode(i.x,i.y); } }); if(i.y+16>=player.y) gameOver=true; }); if(edge){ dir*=-1; invaders.forEach(i=>i.y+=10) } }
function explode(xp,yp){ for(let i=0;i<12;i++) explosions.push({x:xp,y:yp,vx:Math.random()*4-2,vy:Math.random()*4-2,l:20}); beep(300,0.1) }
function drawExplosions(){ explosions.forEach(e=>{ x.fillStyle="yellow"; x.fillRect(e.x,e.y,2,2); e.x+=e.vx;e.y+=e.vy;e.l--; }); explosions=explosions.filter(e=>e.l>0) }
function drawBullets(){ x.fillStyle="cyan"; bullets.forEach(b=>x.fillRect(b.x,b.y,4,10)); x.fillStyle="orange"; enemyBullets.forEach(b=>x.fillRect(b.x,b.y,3,8)) }
function drawUFO(){ if(ufo){ ufo.x+=2; drawSprite(ufoSprite,ufo.x,ufo.y,2,"red"); if(ufo.x>400) ufo=null; } }
function spawnUFO(){ if(!ufo && Math.random()<0.002){ ufo={x:-40,y:25}; } }

function draw(){
  x.clearRect(0,0,360,440);
  drawStars();
  movePlayer();
  shoot();
  updateBullets();
  moveInvaders();
  alienShoot();
  spawnUFO();
  drawUFO();

  animTick++;
  if(animTick>20){ animTick=0; anim=(anim+1)%2 }

  invaders.forEach(i=>{ if(i.a) drawSprite(anim?alien1:alien2,i.x,i.y,2,"white") });

  drawSprite(ship,player.x,player.y-6,4,"#00ff66");
  drawBullets();
  drawShields();
  drawExplosions();

  x.font = "14px monospace";
  x.fillStyle = "#00ff66";
  x.fillText("Score " + score, 10, 20);
  x.fillText("Level " + level, 280, 20);

  if(!invaders.some(i=>i.a)){ level++; speed+=0.2; makeInvaders(); makeShields(); }
  if(gameOver) reset();

  requestAnimationFrame(draw);
}

function reset(){ score=0; level=1; speed=0.6; bullets=[]; enemyBullets=[]; explosions=[]; makeInvaders(); makeShields(); gameOver=false }

document.addEventListener("keydown", e=>{ keys[e.key]=true; if(e.key===" ") firing=true; if(e.key==="r") reset() });
document.addEventListener("keyup", e=>{ keys[e.key]=false; if(e.key===" ") firing=false });
leftBtn.ontouchstart=()=>keys["TouchLeft"]=true; leftBtn.ontouchend=()=>keys["TouchLeft"]=false;
rightBtn.ontouchstart=()=>keys["TouchRight"]=true; rightBtn.ontouchend=()=>keys["TouchRight"]=false;
fireBtn.ontouchstart=()=>firing=true; fireBtn.ontouchend=()=>firing=false;

makeInvaders();
makeShields();
</script>
</body>

░█░█░█▀▀░█▀▄░█▀█░█▀█░█░░
░█▀▄░█▀▀░█▀▄░█░█░█▀█░█░░
░▀░▀░▀▀▀░▀░▀░▀░▀░▀░▀░▀▀▀

This website is proudly hosted on Crocweb Cloud Website Hosting.