Source: Game.js

import * as BAGEL from "./index.js";

/**
 *  The Game class sets up the canvas where the game appears,
 *   manages a collection of {@link Screen} objects, which contain the code for each individual game screen,
 *   and manages up the {@link Input} object and {@link Clock} objects, shared by all screens.
 *  <br/>
 *  Classes which extend Game must implement the {@link Game#initialize|initialize} method.
 */
class Game
{

	/**
	 * Set up the canvas where the game will appear.
	 * @constructor
	 * @param {number} width the width of the canvas containing the game
	 * @param {number} height the height of the canvas containing the game
	 * @param {string} divID the ID of the HTML div element where the canvas should be placed;
	 *                  if not included, a div element will be created
	 */
	constructor(width=800, height=600, divID=null)
	{
		if (!divID)
		{
			divID = "gameArea";
			let newDiv = document.createElement("div");
			newDiv.id = divID;
			// add to bottom of page
			document.body.appendChild(newDiv);
		}		

		let div = document.getElementById(divID);
		this.canvas = document.createElement("canvas");
		this.canvas.width = width;
		this.canvas.height = height;
		div.appendChild( this.canvas );

		this.context = this.canvas.getContext("2d");
	}

	/**
	 *  Add {@link Screen} objects and set first screen to display.
	 *  <br/>
	 *  Must be implemented by extending class.
	 */
	initialize()
	{ 
		throw new Error("initialize() method not implemented");
	}

	/**
	 * Add a {@link Screen} object to this game.
	 * @param {string} screenName - the name that should be used to refer to the screen when using the {@link Game#setScreen|setScreen} method
	 * @param {Screen} screenObject - the screen to be added to the game
	 */
	addScreen(screenName, screenObject)
	{
		this.screenList[screenName] = screenObject;	
	}

	/**
	 * Set the active {@link Screen} object for this game. 
	 * <br/>
	 * During each frame, the game calls the {@link Screen#update|Screen.update} method and
	 *  renders the {@link Sprite|Sprites} in the {@link Group} of the active screen. 
	 * @param {string} screenName - the name of the associated screen initially specified by the {@link Game#addScreen|addScreen} method
	 */
	setScreen(screenName)
	{
		if ( !this.screenFadeTransition || this.activeScreen == null)
		{
			this.activeScreen = this.screenList[screenName];
		    this.activeScreen.resume();
		}
		else
		{
			this.screenFadeInProgress = true;
			this.screenFadeType = "out";
			this.screenFadeOpacity = 0.00;
			this.nextScreenName = screenName;
		}
	}

	
	/**
	 * Determine whether current screen will fade out and
	 *  next screen will fade in, when the screen is changed
	 *  using {@link Game#setScreen|setScreen}, 
	 *  and set the screen fade duration and color. 
	 * @param {boolean} screenFadeTransition - whether or not screens will fade in and fade out
	 * @param {number} screenFadeDuration - the amount of time to fade in and fade out
	 * @param {string} screenFadeColor - the color of the fade between screens
	 */
	setScreenFadeTransition(screenFadeTransition = true,
		screenFadeDuration = 1.0, screenFadeColor = "#000000" )
	{
		this.screenFadeTransition = screenFadeTransition;
		this.screenFadeDuration   = screenFadeDuration;
		this.screenFadeColor      = screenFadeColor;
	}


	/**
	 * Store data at a global level so it is accessible to all {@link Screen|screens} via {@link Game#getData|getData}.
	 * <br>
	 * Note: required data must be set before screens are created.
	 * @param {string} dataName - name used to reference and retrieve data later
	 * @param {object} dataValue - value to be stored
	 */
	setData(dataName, dataValue)
	{
		this.data[dataName] = dataValue;
	}

	/**
	 * Retrieve data previously stored by {@link Game#setData|setData}.
	 */
	getData(dataName)
	{
		return this.data[dataName];
	}

	/**
	 * Start the game: create game objects and run the {@link Game#initialize|initialize} method.
	 */
	start()
	{
		this.input = new BAGEL.Input();
		this.clock = new BAGEL.Clock();
	
		// store collection of screen objects, indexed by name
		this.screenList = {};

		// the currently active screen, will be displayed in game
		this.activeScreen = null;

		// controls entire update loop
		this.running = true;

		// controls automatic group updates (calling update function of all sprites)
		this.paused = false;

		// variables for screen fade transitions
		this.screenFadeTransition = false;
		this.screenFadeInProgress = false;
		this.screenFadeType = "out";
		this.screenFadeDuration = 1.0;
		this.screenFadeColor = "#000000";
		this.screenFadeOpacity = 0.00;
		this.nextScreenName = null;

		// global data structure for passing data between screens
		this.data = {};

		this.initialize();
		this.update();
	}

    /**
	 * Clear the game canvas by drawing a colored rectangle that fills the canvas.
	 * @param {string} clearColor - name or hex code of color to use when clearing the canvas
	 */
	clearCanvas(clearColor = "#000000")
	{
		this.context.setTransform(1,0, 0,1, 0,0);
		this.context.fillStyle = clearColor
		this.context.fillRect(0,0, this.canvas.width, this.canvas.height);
	}


    /**
	 * Update the game: update the game {@link Input} and {@link Clock} objects,
	 *   run the active screen's update method, and draw the active screen's sprite images to the canvas. 
	 */
	update()
	{
		this.clock.update();

		// process input
		this.input.update();
		
		if ( this.input.keyPressed("Escape") )
			this.quit();

		// update active screen's game state
		let deltaTime = this.clock.getDeltaTime();
		this.activeScreen.updateGroups(deltaTime);
		this.activeScreen.update();
		
		// clear window canvas
		this.clearCanvas("#337");

		// render active screen's sprite images to canvas
		this.activeScreen.drawGroups(this.context);

		// if screen fade is active, draw a translucent overlay
		//  and switch screen between fade in and out
		if (this.screenFadeTransition && this.screenFadeInProgress)
		{
			if (this.screenFadeType == "out")
			{
				this.screenFadeOpacity += deltaTime / this.screenFadeDuration;
				if (this.screenFadeOpacity > 1)
				{
					this.screenFadeOpacity = 1;
					this.activeScreen = this.screenList[this.nextScreenName];
		    		this.activeScreen.resume();
		    		this.screenFadeType = "in";
				}
			}
			else if (this.screenFadeType == "in")
			{
				this.screenFadeOpacity -= deltaTime / this.screenFadeDuration;
				if (this.screenFadeOpacity < 0)
				{
					this.screenFadeOpacity = 0;
					this.screenFadeInProgress = false;
				}
			}

			this.context.globalAlpha = this.screenFadeOpacity;
			this.clearCanvas(this.screenFadeColor);
			this.context.globalAlpha = 1.00;
		}

		// creates a loop that repeats at monitor refresh rate.
		// bind(this) required so update function uses correct context for "this"
		if (this.running)
		  this.intervalID = requestAnimationFrame( this.update.bind(this) );
	}
	

	/**
	 * Quit the game: stop the game loop, stop the game from listening for user keyboard input, and clear the canvas.
	 */
	quit()
	{
		this.running = false;

		// stop game from listening for player input
		this.input.shutdown();

		// blank screen
		this.clearCanvas("#888");
	}



}

export { Game };