Source: spaceinvaders.js

/** 
 * Space Invaders Game - main game code, loop, event handler 
 *
 * @author Matthew Page <work@mjp.co>
 * @property {number} width - Width of the game in pixels
 * @property {number} height - Height of the game in pixels
 * @property {Object} domElement - DOM element of the game
 * @property {number} score - Current game score
 * @property {boolean} isPaused - Is the game paused
 * @property {string} gameState - State of current game
 * @property {PlayerShip} player - Player 1
 * @property {EnemyBombFactory} bombFactory - Creates enemy bombs
 * @property {EnemyFleet} enemyFleet - The enemy fleet of ships
 * @property {BonusDropper} bonusDropper - Bonus drops
 * @property {BubbleFactory} bubbleFactory - Bubbles
 * @property {ExplosionFactory} explosionFactory - Explosions
 * @property {ShieldGrid} shieldGrid - Shield
 * @property {Trophies} trophySystem - Tophies
 * @property {HUD} HUD - On screen HUD
 * @property {Object} splashScreen - Splashscreen DOM element
 * @property {Object} gameOverScreen - Game over DOM element
 * @property {Object} winnerScreen - Winner DOM element
 */
class SpaceInvaders {
	/**
	 * Create a new space invaders game instance 
	 */
	constructor() {
		this.width = 640;
		this.height = 480;
		this.domElement = document.getElementById('game');
		this.score = 0;
		this.isPaused = true;
		this.gameState = "splash";

		this.player = new PlayerShip(this, 'player1');
		this.bombFactory = new EnemyBombFactory(this);
		this.enemyFleet = new EnemyFleet(this, this.bombFactory);
		this.bonusDropper = new BonusDropper(this);
		this.bubbleFactory = new BubbleFactory(this);
		this.explosionFactory = new ExplosionFactory(this);
		this.shieldGrid = new ShieldGrid(this);
		this.trophySystem = new Trophies(this);
		this.HUD = new HUD(this, this.player);

		this.splashScreen = document.getElementById('splashScreen');
		this.gameOverScreen = document.getElementById('gameOverScreen');
		this.winnerScreen = document.getElementById('winnerScreen');
		
		document.addEventListener("keydown", this, false);
		document.addEventListener("keyup", this, false);	
		this.HUD.update();
		this.update();
	}
	/**
	 * Handle the incoming DOM events (keyup and keydown). Pass relevant 
	 * key events to the PlayerShip instance
	 *
	 * @param {Event} e - The DOM event instance
	 * @returns {boolean} False, prevent the browser processing these events.
	 */
	handleEvent(e) {
		if (e===undefined) e = window.event; // for IE ?? do I still need to do this crap in 2019?
		switch(e.key) {
			case "ArrowLeft" :
				this.player.receiveCommand(e.type, 'left');
				break;
			case "ArrowRight" :
				this.player.receiveCommand(e.type, 'right');
				break;
			case " " :
				this.player.receiveCommand(e.type, 'fire');	
				break;
		}
		return false;
	}
	/**
	 * Restart the game
	 *
	 */
	restart() {
		this.score = 0;
		this.player.reset();
		this.bombFactory.reset();
		this.enemyFleet.reset();
		this.bonusDropper.reset();
		this.bubbleFactory.reset();
		this.explosionFactory.reset();
		this.shieldGrid.reset();
		this.trophySystem.reset();
		this.HUD.reset();
		
		this.isPaused = true;
		this.changeState("splash");
	}
	/**
	 * Change the game state - splash, play, gameover, win
	 *
	 * @param {string} state - The state code to change to
	 */
	changeState(state) {
		switch (state) {
			case "splash":
				this.isPaused = true;
				this.gameState = "splash";
				this.splashScreen.style.display = "block";
				this.gameOverScreen.style.display = "none";
				this.winnerScreen.style.display = "none";
				break;
	
			case "play":
				this.isPaused = false;
				this.gameState = "play";
				this.splashScreen.style.display = "none";
				this.gameOverScreen.style.display = "none";
				this.winnerScreen.style.display = "none";
				break;

			case "gameover":
				this.isPaused = true;
				this.gameState = "gameover";
				this.splashScreen.style.display = "none";
				this.gameOverScreen.style.display = "block";
				this.winnerScreen.style.display = "none";
				this.gameOverScreen.innerHTML = `
					<h2>Game Over</h2>
					<p>You failed, the aliens have taken over!</p>
					<div>
						<h3>Score : ${this.score}</h3>
						<p>Shots fired : ${this.player.gun.shotsFired}</p>
						<p>Shots on target : ${this.player.gun.shotsHit} (${this.player.gun.accuracy}%)</p>
						<p>Enemy Bombs dropped : ${this.bombFactory.makeCounter}</p>
						<p>Explosion particles made : ${this.explosionFactory.particleCounter}</p>
					</div>
					<p><a href="#" onclick="myGame.restart(); return false;">Play again</a></p>
				`;
				new Audio('sfx/gameover.mp3').play();
				break;
			case "win":
				this.isPaused = true;
				this.gameState = "win";
				this.splashScreen.style.display = "none";
				this.gameOverScreen.style.display = "none";
				this.winnerScreen.style.display = "block";
				this.winnerScreen.innerHTML = `
					<h2>WINNER !!!</h2>
					<p>You beat the invasion, well done.</p>
					<h3>Score : ${this.score}</h3>
					<p>Shots fired : ${this.player.gun.shotsFired}</p>
					<p>Shots on target : ${this.player.gun.shotsHit} (${this.player.gun.accuracy}%)</p>
					<p>Enemy Bombs dropped : ${this.bombFactory.makeCounter}</p>
					<p>Explosion particles made : ${this.explosionFactory.particleCounter}</p>
					<p><a href="#" onclick="myGame.restart(); return false;">Restart</a></p>
				`;
				break;
		}
	}
	/**
	 * Main update loop, calls update on other components.
	 *
	 */
	update() {
		if(!this.isPaused) {
			this.enemyFleet.update();
			this.player.update();
			this.HUD.update();
			this.bombFactory.update();
			this.bubbleFactory.update();
			this.bonusDropper.update();
			this.explosionFactory.update();
			this.domElement.style.backgroundPosition = "-"+(this.player.posX/12)+"px 0px";
			this.trophySystem.update();
			this.checkWin();
			this.checkLoss();			
		}
		/* Request this method be called again ... loop forever */
		window.requestAnimationFrame(() => this.update());
	}
	/**
	 * Toggle the pause status
	 *
	 */
	pauseToggle() {
		this.isPaused = (this.isPaused)?false:true;	
		this.domElement.style.opacity = (this.isPaused)?0.1:1;
	}
	/**
	 * Toggle the splash screen
	 *
	 */
	splashToggle() {
		if(this.gameState == "splash") {
			this.changeState("play");
		} else if(this.gameState == "play") {
			this.changeState("splash");
		}
		this.domElement.style.opacity = (this.isPaused)?0.1:1;
	}
	/**
	 * Check if we have won the game, killed all the enemy ships
	 *
	 * @returns {boolean}
	 */
	checkWin() {
		if(this.enemyFleet.totalAlive == 0) {
			this.changeState("win");
			return true;
		}
	}
	/**
	 * Check if we have lost the game, is it game over?
	 * Player hit points < 1
	 *
	 * @returns {boolean}
	 */
	checkLoss() {
		if(this.player.hitPoints < 1) {
			this.changeState("gameover");
			return true;
		}
	}
}