<div class="grad"></div>
<canvas id="canvas"></canvas>
<div class="reflect"></div>
<div class="reflect1"></div>
<div id="instructs">[1] Coin [S] Play / Cont [Left][Right] Move [Up] Shoot | grmagazin.blogspot.com</div>
body {
background : black;
font-family : consola;
padding : 0px;
margin : 0px;
}
.grad {
position : absolute;
top : 0px;
height: 100%;
background: linear-gradient(90deg, rgba(20,10,0,1) 0%, rgba(0,34,68,1) 50%, rgba(2,0,30,1) 100%);
background-blend-mode: screen
}
.reflect {
position : absolute;
top : 0px;
height: 100%;
background: rgb(0,0,0);
background: linear-gradient(137deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 62%, rgba(78,178,178,0.1) 64%, rgba(56,20,0,0.2) 100%);
background-blend-mode: lighten;
mix-blend-mode: lighten;
}
.reflect1 {
position : absolute;
top : 0px;
height: 100%;
background: rgb(0,0,0);
background: linear-gradient(130deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 62%, rgba(78,78,78,0.1) 64%, rgba(56,56,56,0.2) 100%);
}
#canvas {
image-rendering: pixelated;
opacity : 0;
top : 0px;
position : absolute;
}
div {
text-align : center;
color : #88ccff;
position : absolute;
width : 100%;
height : 16px;
font-size : 12px;
bottom : 0px;
}
/* Code to make it fit in Code pen */
// Canvas is rendered to the HTML canvas at the bottom of the game code with some
// Fx just for fun.
const cctx = canvas.getContext("2d");
const fxCan = document.createElement("canvas");
const fxCtx = fxCan.getContext("2d");
var lineHandle;
var ready = false;
var linesReady = false;
const resize = () => {
const ratio = 225 / 126;
fxCan.width = canvas.height = innerHeight;
fxCan.height = canvas.width = innerHeight * ratio;
linesReady = false;
clearTimeout(lineHandle,100);
lineHandle = setTimeout(drawTVLines,100); // debounce so not to chew power
canvas.style.left = ((innerWidth * (1/2) - innerHeight * ratio * (1/2)) | 0) + "px";
ready = true;
}
function drawTVLines(){
var y;
const w = fxCan.width;
const h = fxCan.height;
const w10 = w * 0.1;
const c = fxCtx;
const g = c.createLinearGradient(0,0,w,0);
g.addColorStop(0,"rgba(33,30,55,0)");
g.addColorStop(0.2,"rgba(6,66,0,7.8)");
g.addColorStop(0.8,"rgba(4,77,0,1.8)");
g.addColorStop(1,"rgba(2,33,88,1)");
c.strokeStyle = g;
c.lineWidth = 226 * 2 / h;
c.beginPath();
for(y = 0; y < h; y += h / 126){
c.moveTo(0,y + Math.random() * 0.2 - 0.1);
c.lineTo(w,y + Math.random() * 0.2 - 0.1);
c.moveTo(w10,y + Math.random() * 0.2 - 0.1);
c.lineTo(w-w10,y + Math.random() * 0.2 - 0.1);
}
c.stroke();
linesReady = true;
}
addEventListener("resize",resize);
setTimeout(resize,0);
focus();
/* Game starts here */
(() => {
function drawSpriteOn(ctx, idx, x, y){
var spr = spriteImg.sprites[idx];
ctx.drawImage(spriteImg, spr.x, spr.y, spr.w, spr.h, x, y, spr.w, spr.h);
}
function drawSprite(idx, x, y){
var spr = spriteImg.sprites[idx];
ctx.drawImage(spriteImg, spr.x, spr.y, spr.w, spr.h, x, y, spr.w, spr.h);
}
function drawSpriteSize(idx, x, y, w, h){
var spr = spriteImg.sprites[idx];
ctx.drawImage(spriteImg, spr.x, spr.y, spr.w, spr.h, x, y, w, h);
}
function drawNumber(num, x, y){
var i = num.length;
while(i--){
const c = (num.charCodeAt(i) - ZERO) * 2;
const sx = numberSprites[c];
const w = numberSprites[c+1]
ctx.drawImage(numbers, sx, 0, w, 5, x - w, y, w, 5);
x -= w + 1;
}
}
function createBinImage(color,data) {
var w,h,can,c,d,d32,x,y,i,bits
can = document.createElement("canvas");
can.width = w = data[0];
can.height = h = data[1];
data = data[2].split(",")
c = can.getContext("2d");
d = c.getImageData(0,0,w,h);
d32 = new Uint32Array(d.data.buffer);
for (y = 0; y < h; y++) {
x = 0;
i = w - Math.floor(w / 32) * 32;
i = i === 0 ? 32 : i;
while (x < w) {
bits = parseInt(data.shift(),16);
while (i--) { d32[(x++) + y * w] = bits & (1<<i) ? color : 0 }
i = 32;
}
}
c.putImageData(d,0,0);
return can;
}
const color = 0xFFFFCC88; // 32Bit
const bulletColor = "#88CCFF";
const insertCoinImg = createBinImage(color, [41, 6, "1d2,6eee1929,9a,88a422ad,9a,4ce422ad,96,28c422ab,1d2,cea41929"]);
const numbers = createBinImage(color, [37, 5, "1d,77377777,15,11444155,15,22567177,15,41715251,1d,77167477"]);
const spriteImg = createBinImage(color, [ 26, 34,"91486,1006336,240449,10a3369,400546,2115a46,248599,802a89,4010,36dada0,921249,1bb1ac6,fe3fe6,fe1fc9,6c0d80,441046,3e01f0,410208,9484a4,808404,3ffffff,379b73b,1b34b36,ff87fc,7ff028,fff810,1fffc10,3fffe7c,3fffefe,3ffffff,3ffffff,3ffffff,3f07fff,3c01e00"]);
const gameOverImg = createBinImage(color,[ 32, 21,"3c3bc1bf,7e7ce3bf,e6eef7b1,c0c6ffb4,cefeddbc,cefec9bc,c6c6c1b4,e6c6c1b1,7ec6c1bf,3deee3bf,0,3df77efe,7e637eff,e76362c3,c36368c3,c37778ff,c33678fe,c33668d8,e73e62cc,7e1c7ec6,3c1dfee7" ]);
const ZERO = ("0").charCodeAt(0);
const numberSprites = [0,3,4,1,6,3,10,3,14,3,18,3,22,3,26,3,30,3,34,3];
spriteImg.sprites = [
{x: 0, y: 24,w: 17,h: 10}, //0 base.png
{x: 17,y: 24,w: 9, h: 9}, //1 Ship.png
{x: 0, y: 0, w: 11,h: 8}, //2 expolode2.png
{x: 11,y: 0, w: 11,h: 8}, //3 explode1.png
{x: 13,y: 16,w: 13,h: 8}, //4 UFO2.png
{x: 0, y: 16,w: 13,h: 8}, //5 UFO1.png
{x: 0, y: 8, w: 11,h: 8}, //6 Alien1.png
{x: 11,y: 8, w: 11,h: 8}, //7 Alien2.png
{x: 22,y: 0, w: 2, h: 8}, //8 Bomb2.png
{x: 15,y: 0, w: 2, h: 8}, //9 Bomb3.png
{x: 24,y: 0, w: 2, h: 8}, //10 Bomb1.png
{x: 22,y: 12,w: 4, h: 4}, //11 splash1.png
{x: 22,y: 8, w: 4, h: 4}, //12 splash2.png
{x: 3, y: 10,w: 5, h: 5}, //13 life
];
const spriteNames = {
base : 0,
alien : [7,6],
alienExplode : [2,3],
ufo : [4,5],
ship : 1,
bombs : [8,9,10],
splash : [11,12],
life : 13,
}
const UFO_W = spriteImg.sprites[spriteNames.ufo[0]].w;
const UFO_H = spriteImg.sprites[spriteNames.ufo[0]].h;
const UFO_TIME = 60 * 2; // 60 frame per second * two seconds
const UFO_STEP_FRAME = 4; // number of frame between moves
const PLAYER_W = spriteImg.sprites[spriteNames.ship].w;
const PLAYER_H = spriteImg.sprites[spriteNames.ship].h;
const ALIEN_W = spriteImg.sprites[spriteNames.alien[0]].w;
const ALIEN_H = spriteImg.sprites[spriteNames.alien[0]].h;
const BOMB_W = spriteImg.sprites[spriteNames.bombs[0]].w;
const BOMB_H = spriteImg.sprites[spriteNames.bombs[0]].h;
const ALIEN_ROWS = 5;
const ALIEN_COLUMNS = 11;
const ALIEN_START_SPEED = 32;
const INFO_H = 6; // space at top for lives score and what not
const SHIP_BULLET_COUNT = 1; // Max number of bullets at one time for player's ship
const W_SPACING = 4;
const H_SPACING = 4;
const UFO_TOP = UFO_H / 2 + INFO_H | 0;
const ALIEN_TOP = UFO_H * 2 + INFO_H;
const SIDES = 15;
const W = (ALIEN_W + W_SPACING) * ALIEN_COLUMNS + SIDES * 4;
const H = (ALIEN_H + H_SPACING) * ALIEN_ROWS + SIDES * 4 + INFO_H;
const GROUND = H - 10;
const ALIEN_X_STEP = 2;
const ALIEN_Y_STEP = 2;
const BOMB_SPEED = 1;
const BULLET_SPEED = 1;
const BULLET_W = 2;
const BULLET_H = 4;
const ALIEN_BOMBING_RATE = 1 /(ALIEN_ROWS * ALIEN_COLUMNS);
const UFO_ANIM_FRAME_COUNT = 8; // number of frames
const BOMB_ANIM_FRAME_COUNT = 8; // number of frames
const BOMB_ANIM_FRAMES = 3; // number of frames
const BOMB_ANIM_SPLASH_FRAMES = 2; // number of frames
const BASE_W = spriteImg.sprites[spriteNames.base].w;
const BASE_H = spriteImg.sprites[spriteNames.base].h;
const BASE_COUNT = 4;
const BASE_PLAYER_CLEARANCE = BASE_H / 2 | 0;
const BASE_SPACING = (((W - SIDES * 4) - (BASE_COUNT * BASE_W)) / (BASE_COUNT - 1)) | 0;
const LIFE_BLINK_RATE = 8; // Blink rate of life used in frames
const COIN_BLINK_RATE = 35;
const TIME_TO_RESTART = 10000; // Time for game ove to apear and alow continue
const TIME_TILL_NEXT_LIFE = 2000; // time from death till next life
const TIME_TILL_GAME_OVER = 2000; // time from last life death to game over displayed
const TIME_TO_LEVEL_UP = 1000; // Time from level end to start of next
const GET_READY_TIME = 1000; // time to get ready
const baseCan = document.createElement("canvas");
baseCan.width = BASE_COUNT * BASE_W + BASE_SPACING * (BASE_COUNT - 1);
baseCan.height = BASE_H ;
const baseCtx = baseCan.getContext("2d");
var can = document.createElement("canvas");
can.width = W;
can.height = H;
var ctx = can.getContext('2d');
ctx.imageSmoothingEnabled = false;
const states = {
init : 0,
gameOver : 1,
inPlay : 2,
levelUp : 3,
getReady : 4,
insertCoin : 5,
startGame : 6,
nextLife : 7,
};
var stateChangeTimerHandle;
var frameCount = 0;
function drawCoins(blink = false) {
if((blink && (frameCount / COIN_BLINK_RATE | 0) % 2 === 0) || !blink){
const x = (W / 2 | 0) - ((insertCoinImg.width + 9) / 2 | 0);
ctx.drawImage(insertCoinImg,(W / 2 | 0) - ((insertCoinImg.width + 9) / 2 | 0),2);
drawNumber(""+game.coins,x + insertCoinImg.width + 9,2);
}
}
function drawScore() {
drawNumber(game.score + "", W-12, 2);
}
function drawLives(blink = false) {
var x;
const len = game.lives - (blink ? 1 : 0);
for(x = 0; x < len; x++){
drawSprite(spriteNames.life, 2 + x * 6, 2);
}
if(blink && (frameCount / LIFE_BLINK_RATE | 0) % 2){
drawSprite(spriteNames.life, 2 + x * 6, 2);
}
}
function drawLevel(){
drawNumber(game.level + "", W-4, 2);
}
function showGameOver() {
const x = W / 2 - gameOverImg.width | 0;
const y = H / 2 - gameOverImg.height * 2 | 0;
const w = gameOverImg.width * 2;
const h = gameOverImg.height * 2;
ctx.clearRect(x - 1, y - 1, w + 2, h + 2);
ctx.drawImage(gameOverImg,x, y, w, h);
}
const player = {
x: W / 2 | 0,
dx: 0,
dead : false,
anim : 0,
step() {
if(!player.dead){
this.x += this.dx;
this.x = this.x < SIDES - PLAYER_W ? SIDES - PLAYER_W : this.x > W-SIDES +PLAYER_W ? W-SIDES +PLAYER_W : this.x;
}else{
if(player.anim === 0){
game.state = states.nextLife;
}
player.anim ++;
}
},
draw() {
if(player.dead){
const f = (frameCount/4|0) % spriteNames.alienExplode.length;
drawSpriteSize(spriteNames.alienExplode[f], player.x, GROUND, PLAYER_W, PLAYER_H);
}else{
drawSprite(spriteNames.ship, this.x, GROUND);
}
},
};
function List(type, extraProperties){
const list = Object.assign([],{
each(cb){
var i;
for(i = 0; i < list.length; i++) { cb(list[i],i) }
},
filtered(cb){
var i;
for(i = 0; i < list.length; i++){
if(cb(list[i],i) === false) { list.splice(i--,1) }
}
},
add(...args) { list.push(new type(...args)) },
drawArgs(...args) { for(const item of list){ item.draw(...args) } },
draw() { for(const item of list){ item.draw() } },
},
extraProperties
);
return list
}
function Bullet(x) {
this.x = player.x + PLAYER_W / 2 - BULLET_W / 2 | 0;
this.y = GROUND;
this.dead = false;
}
Bullet.prototype = {
draw() {
ctx.fillStyle = bulletColor;
ctx.fillRect(this.x,this.y,BULLET_W, BULLET_H);
},
move() { this.y -= BULLET_SPEED }
}
function Bomb(x,y){
this.x = x;
this.y = y;
this.hitGround = false;
this.animate = 0;
}
Bomb.prototype = {
draw() {
this.animate += 1;
if(this.hitGround){
drawSprite(spriteNames.splash[(this.animate / BOMB_ANIM_FRAME_COUNT | 0) % BOMB_ANIM_SPLASH_FRAMES],this.x - 2 , this.y - 4);
}else{
drawSprite(spriteNames.bombs[(this.animate / BOMB_ANIM_FRAME_COUNT | 0) % BOMB_ANIM_FRAMES], this.x, this.y)
}
},
move() {
if(!this.hitGround){
this.y += BOMB_SPEED;
if(!player.dead && this.y > GROUND - PLAYER_H && this.x+ 1 > player.x && this.x-1 < player.x + PLAYER_W){
this.y = GROUND + PLAYER_H;
player.dead = true;
player.anim = 0;
this.animate = 0;
this.hitGround = true;
}else if(this.y + BOMB_H > GROUND + PLAYER_H){
this.y = GROUND + PLAYER_H;
this.animate = 0;
this.hitGround = true;
}
}
}
}
function Alien(x, y, frame) {
this.x = x;
this.y = y;
this.alive = true;
this.dieCount = 0;
this.frame = frame;
}
Alien.prototype = {
draw(frameStep) {
if(this.alive){
const f = (this.frame + frameStep) % spriteNames.alien.length;
drawSprite(spriteNames.alien[f], this.x, this.y)
}else{
this.dieCount += 1;
if(this.dieCount < 32){
const f = (this.dieCount/4|0) % spriteNames.alienExplode.length;
drawSprite(spriteNames.alienExplode[f], this.x, this.y)
}
}
}
}
function Base(idx){
this.x = SIDES * 2 + idx * (BASE_W + BASE_SPACING);
this.y = GROUND - BASE_H - BASE_PLAYER_CLEARANCE;
this.active = true;
this.shape = [];
for (let j = 0; j < BASE_W; j++) { this.shape.push([0, BASE_H]) }
drawSpriteOn(baseCtx, spriteNames.base, this.x - SIDES * 2, 0);
}
Base.prototype = {
checkHit(x, y, w, h, dy) {
if (x + w < this.x || x > this.x + BASE_W || y + h < y || y > this.y + BASE_H) {
return false;
}
var xx = x - this.x;
xx = xx < 0 ? 0 : xx >= BASE_W - 1 ? BASE_W - 2 : xx;
const idx = dy < 0 ? 1 : 0;
const minY = Math.min( this.shape[xx][idx], this.shape[xx + 1][idx]);
if((idx && y < this.y + minY) || (!idx && y + h > this.y + minY)){
const bSprite = spriteNames.bombs[0];
x -= SIDES * 2;
if(idx){
y -= 1 + this.y;
this.shape[xx][idx] = y;
this.shape[xx + 1][idx] = y;
}else{
y += (h / 2 | 0) - this.y
this.shape[xx][idx] = y + h;
this.shape[xx + 1][idx] = y + h;
}
drawSpriteOn(baseCtx, bSprite + 1, x - 1 , y);
drawSpriteOn(baseCtx, bSprite, x, y);
drawSpriteOn(baseCtx, bSprite + 1, x + 1, y);
return true;
}
return false;
},
isClear() {
var size = 0;
for(const s of this.shape) {
if (s[0] >= s[1] - 1) {
s[1] = -(s[0] = 1000);
} else { size += s[1] - s[0] }
}
if (size < 15) {
baseCtx.clearRect(this.x - SIDES * 2, 0, BASE_W, BASE_H);
return true;
}
return false;
}
}
const bullets = List(
Bullet, {
shoot(x){
if(bullets.length < SHIP_BULLET_COUNT && !player.dead){ bullets.add(x) }
},
step() {
this.filtered(bullet=> {
bullet.move();
if(bullet.y < 0 || bullet.dead) { return false }
});
},
}
);
const bombs = List(
Bomb, {
step(){
this.filtered(bomb => {
var remove = false;
if(bomb.hitGround && bomb.animate > 16){
remove = true;
}else{
bomb.move();
bullets.filtered(b => {
if(!(bomb.x + BOMB_W < b.x || bomb.x > b.x + BULLET_W ||
bomb.y + BOMB_H < b.y || bomb.y > b.y + BULLET_H)){
remove = true;
return false;
}
})
}
return ! remove;
});
}
}
)
const aliens = List(
Alien, {
speed : 32,
direction : ALIEN_X_STEP,
counter : 0,
frame : 0,
stepDown : false,
step() {
aliens.counter += 1;
var aliveCount = 0;
aliens.filtered(alien => {
if(alien.alive){
bullets.filtered(bullet => {
if(!bullet.dead && !(bullet.x + BULLET_W < alien.x || bullet.x > alien.x + ALIEN_W ||
bullet.y + BULLET_H < alien.y || bullet.y > alien.y + ALIEN_H)){
alien.alive = false;
game.score += 2;
return false;
}
})
aliveCount ++;
}else{
if(alien.dieCount > 32){
return false;
}
}
});
aliens.speed = (aliveCount / 55) * (ALIEN_START_SPEED - (game.level >= ALIEN_START_SPEED ? ALIEN_START_SPEED : game.level)) + 2| 0;
if((aliens.counter % aliens.speed) === 0){
let dy = 0;
let dx = aliens.direction;
if(aliens.stepDown ) {
dy = ALIEN_Y_STEP;
dx = 0;
aliens.direction = -aliens.direction;
aliens.stepDown = false;
}
aliens.frame += 1;
aliens.each(alien => {
alien.y += dy;
alien.x += dx;
if(alien.alive){
if(Math.random() < ALIEN_BOMBING_RATE){
const offset = Math.random() < 0.5 ? 1 : 0; // Offset so that bases dont have thin sections remaining
bombs.add(alien.x + offset + ALIEN_W / 2 | 0, alien.y + ALIEN_H);
}
if(bases.length > 0 && alien.y + ALIEN_H > GROUND - BASE_H - BASE_PLAYER_CLEARANCE){
bases.length = 0;
}
if(alien.y + ALIEN_H > GROUND){
game.state = states.gameOver;
game.canContinue = false; // NOTE this line must come after the state change
}
if(!aliens.stepDown){
if(aliens.direction > 0 && alien.x > W - SIDES - ALIEN_W){
aliens.stepDown = true;
}else if(aliens.direction < 0 && alien.x < SIDES){
aliens.stepDown = true;
}
}
}
})
}
if(aliveCount === 0){
game.state = states.levelUp;
}
},
draw() {
aliens.drawArgs(aliens.frame)
},
create() {
//aliens.speed = alienStartSpeed;
aliens.direction = ALIEN_X_STEP;
aliens.counter = 0;
var y,x;
if(aliens.length === 0){
for(y = 0; y < ALIEN_ROWS; y ++){
for (x = 0; x < ALIEN_COLUMNS; x++) {
aliens.add(x * (ALIEN_W + W_SPACING) + SIDES, y * (ALIEN_H + H_SPACING) + ALIEN_TOP + game.level, y);
}
}
}
}
}
)
const UFO = {
x : -2 - UFO_W,
direction : 1,
frame : 0,
nextTime : UFO_TIME,
explode:false,
set side(val) {
if(val === "right"){
UFO.nextTime = UFO_TIME;
UFO.direction = -1;
UFO.x = W + 2;
} else {
UFO.nextTime = UFO_TIME;
UFO.direction = 1;
UFO.x = - 2 - UFO_W;
}
UFO.explode = false;
},
draw(){
if(UFO.explode){
// using alien explosion sprites
const f = (UFO.frame/4|0) % spriteNames.alienExplode.length;
drawSpriteSize(spriteNames.alienExplode[f], UFO.x, UFO_TOP, UFO_W, UFO_H)
}else if(UFO.nextTime === 0){
drawSprite(spriteNames.ufo[(UFO.frame / UFO_ANIM_FRAME_COUNT|0) % spriteNames.ufo.length], UFO.x, UFO_TOP);
}
},
step(){
UFO.frame++;
if(UFO.explode){
if(UFO.frame > 32){
UFO.side = UFO.direction > 0 ? "right" : "left";
UFO.explode = false;
}
}else if(UFO.nextTime === 0){
if(UFO.frame % UFO_STEP_FRAME === 0){
UFO.x += UFO.direction;
}
bullets.filtered(bullet => {
if(!bullet.dead && !(bullet.x + BULLET_W < UFO.x || bullet.x > UFO.x + UFO_W ||
bullet.y + BULLET_H < UFO_TOP || bullet.y > UFO_TOP + ALIEN_H)){
UFO.explode = true;
game.score += 10;
UFO.frame = 0;
return false;
}
})
if(UFO.direction > 0){
if(UFO.x > W){
UFO.side = "right";
}
} else {
if(UFO.x + UFO_W < 0){
UFO.side = "left";
}
}
}else{
UFO.nextTime -= 1;
}
}
}
const bases = List(
Base, {
create() {
bases.length = 0;
for (let i = 0; i < BASE_COUNT; i++) { bases.add(i) }
},
draw() {
if(bases.length > 0){
ctx.drawImage(baseCan, SIDES * 2, GROUND - BASE_H - BASE_PLAYER_CLEARANCE);
}
},
step() {
baseCtx.globalCompositeOperation = "destination-out";
bases.filtered((base, idx) => {
bullets.filtered(b => {
if (!b.dead && base.checkHit(b.x, b.y, BULLET_W, BULLET_H, -1)) { return false }
});
bombs.filtered(b => {
if (base.checkHit(b.x, b.y, BOMB_W, BOMB_H, 1)) { return false }
});
return !base.isClear()
});
baseCtx.globalCompositeOperation = "source-over";
}
}
);
const game = {
score : 0,
lives : 0,
coins : 0,
level : 1,
canContinue : true,
canRestart : true,
gameState : 0,
move : false,
shoot : false,
aliens : false,
alienFreeze : false,
showGameOver : false,
blinkLives : false,
currentState(){
game.restart();
keyboard.addCallback("Digit1",()=>{
if(game.coins < 99){
game.coins += 1;
}
return false;
})
keyboard.addCallback("KeyS",()=>{
if(game.coins > 0){
if(game.canContinue){
game.coins -= 1;
game.lives += 3;
game.state = states.nextLife
instructs.textContent= "";
}else if(game.canRestart){
game.coins -= 1;
game.lives += 3;
game.state = states.startGame;
instructs.textContent= "";
}
}
return false;
})
game.currentState = game.step
game.state = states.insertCoin;
},
startLevel(){
game.level += 1;
bases.length = 0;
aliens.length = 0;
UFO.side = "left";
aliens.create();
bases.create();
},
restart() {
game.level = 0;
bombs.length = 0;
bullets.length = 0;
game.startLevel();
player.dead = false;
player.x = W / 2 | 0;
keys.anyKey = false;
},
nextLife() {
bombs.length = 0;
bullets.length = 0;
if(game.lives > 0){ return states.getReady }
return states.gameOver;
},
draw(){
bases.draw();
bullets.draw()
bombs.draw();
UFO.draw();
aliens.draw();
drawScore();
drawLevel();
if(game.showGameOver){ showGameOver() }
if(game.showPlayer){ player.draw() }
drawLives(game.blinkLives);
drawCoins(game.blinkCoins);
},
step() {
if(game.gameState !== states.gameOver){
if(game.shoot){
if (keys.ArrowUp) { bullets.shoot(player.x + 5) }
}
player.dx = 0;
if(game.move){
if (keys.ArrowLeft) { player.dx = -1 }
if (keys.ArrowRight) { player.dx = 1 }
}
if(game.move || game.shoot){ player.step() }
if(game.shoot){ bullets.step() }
if(game.aliens){
bombs.step();
bases.step();
if(!game.alienFreeze){
aliens.step();
}
UFO.step();
}
}
},
stepState(shoot, move, aliens = false, alienFreeze = false){
game.shoot = shoot;
game.move = move;
game.aliens = aliens;
game.alienFreeze = alienFreeze;
},
drawState(showPlayer, blinkLives = false, blinkCoins = false, showGameOver = false){
game.showPlayer = showPlayer;
game.blinkLives = blinkLives;
game.blinkCoins = blinkCoins;
game.showGameOver = showGameOver;
},
set state(state){
const nextState = (s, t) => (state = s, time = t);
var time = -1;
clearTimeout(stateChangeTimerHandle);
game.canRestart = false;
game.canContinue = false;
const prevState = game.gameState;
game.gameState = state;
if(state === states.insertCoin){
game.restart();
game.canRestart = true;
game.stepState(false,false,true);
game.drawState(false,false,true)
}else if(state === states.startGame){
game.restart();
game.stepState(false,true);
game.drawState(true,true);
nextState(states.inPlay, GET_READY_TIME);
}else if(state === states.getReady){
UFO.side = "left";
player.dead = false;
player.x = W / 2 | 0;
game.stepState(false,true);
game.drawState(true,true);
nextState(states.inPlay, GET_READY_TIME);
}else if(state === states.levelUp){
bullets.length = 0;
game.stepState(false,true, true, true, true);
nextState(states.inPlay, TIME_TO_LEVEL_UP);
}else if(state === states.gameOver){
game.lives = 0;
game.canContinue = true;
player.dead = false;
player.x = W / 2 | 0;
game.drawState(false,false,true,true);
nextState(states.insertCoin, TIME_TO_RESTART);
}else if(state === states.nextLife){
game.stepState(false, false, true, true);
game.drawState(true,false,false,false);
state = game.nextLife();
time = state === states.getReady ? TIME_TILL_NEXT_LIFE : TIME_TILL_GAME_OVER;
}else if(state === states.inPlay){
if (prevState === states.levelUp) { game.startLevel() }
else { game.lives -= 1 }
game.stepState(true, true, true, false);
game.drawState(true);
}
if(time > -1) {
stateChangeTimerHandle = setTimeout(()=>game.state = state, time);
}
},
}
var fade = 0;
function mainLoop(){
frameCount += 1;
ctx.clearRect(0,0,can.width,can.height);
game.currentState();
game.draw();
requestAnimationFrame(mainLoop);
if(ready){
if(fade < 1){
fade += 0.02;
if(fade >= 1){
canvas.style.opacity = 1;
}else{
canvas.style.opacity = (fade ** 2);
}
}
cctx.globalCompositeOperation = "copy";
cctx.imageSmoothingEnabled = false;
cctx.drawImage(can,0,0,cctx.canvas.width,cctx.canvas.height);
if(linesReady && cctx.canvas.height > H * 3){
cctx.globalCompositeOperation = "source-over";
cctx.imageSmoothingEnabled = true;
cctx.drawImage(fxCan,0,0,cctx.canvas.width,cctx.canvas.height);
}
}
}
const keyboard = (() => {
const keys = {
anyKey : false,
};
const callbacks = {};
const callbackOnce = {};
function keyEvents(e){
const isDown = e.type === "keydown";
if(keys[e.code] !== undefined){
keys[e.code] = isDown;
e.preventDefault();
}
var anyKey = isDown;
if(isDown && !keys.anyKey){
if(callbacks.anyKey) {
anyKey = callbacks.anyKey(e) === true;
}
if(callbackOnce.anyKey) {
anyKey = callbackOnce.anyKey(e) === true;
delete callbackOnce.anyKey;
}
keys.anyKey = anyKey;
}
if(callbacks[e.code] && isDown){
callbacks[e.code](e);
}
if(callbackOnce[e.code] && isDown){
callbackOnce[e.code](e);
delete callbackOnce[e.code];
}
}
const API = {
start(...keyNames){
for(const key of keyNames){
keys[key] = false;
}
addEventListener("keyup", keyEvents);
addEventListener("keydown", keyEvents);
if (window.keys === undefined) { window.keys = keys }
return keys;
},
stop(){
removeEventListener("keyup", keyEvents);
removeEventListener("keydown", keyEvents);
},
addCallback(keyName, callback){
callbacks[keyName] = callback;
},
addCallbackOnce(keyName, callback){
callbacksOnce[keyName] = callback;
}
}
return API;
})();
const keys = keyboard.start("ArrowUp","ArrowLeft","ArrowRight");
requestAnimationFrame(mainLoop);
})();
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.