CrossBrowdy - Examples

Advanced

CHIP-8 emulator

This is an example of a simple CHIP-8 emulator (using the Game engine module from the previous example).

You can use a gamepad, the keyboard, the mouse or a touching screen to control the emulator and its software.

The purpose of this example is to show different ways to use CrossBrowdy and some of its multiple features.

index.html:

<!DOCTYPE html>
<html>
	<head>
		<meta http-equiv="content-type" content="text/html; charset=utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
		<title>Advanced: CHIP-8 emulator - Example</title>
		<!-- Loads the needed CSS files: -->
		<link rel="stylesheet" type="text/css" href="main.css" />
		<!-- Loads FlashCanvas (Flash emulation) before CrossBrowdy. Needed also to use ExplorerCanvas (VML emulation) without problems: -->
		<script type="text/javascript" src="CrossBrowdy/CrossBase/audiovisual/image/canvas/FlashCanvas/pro/bin/flashcanvas.js"></script><!-- FlashCanvas/ExplorerCanvas do not support lazy load. -->
		<!-- Loads CrossBrowdy.js (main file): -->
		<script src="CrossBrowdy/CrossBrowdy.js" type="text/javascript" language="javascript"></script><!-- "type" and "language" parameters for legacy clients. -->
		<!-- Loads the other needed script files: -->
		<script src="main.js" type="text/javascript" language="javascript"></script>
		<script src="rom_loader.js" type="text/javascript" language="javascript"></script>
		<script src="cpu.js" type="text/javascript" language="javascript"></script>
	</head>
	<body>
		<!-- Toolbar and its icons: -->
		<div id="toolbar">
			<button id="button_reset" class="toolbar_icon" title="[R]eset" accesskey="r" disabled>R</button>
			<button id="button_file_selector" class="toolbar_icon" title="[L]oad a ROM" accesskey="l" disabled>L</button>
			<input type="file" id="file_selector" name="file_selector" disabled />
			<button id="button_fullscreen" class="toolbar_icon" title="Toggle [F]ullscreen mode" accesskey="f" disabled>F</button>
			<br />
			<select id="rom_selector" disabled>
				<option id="none" value="none">-- Select a ROM --</option>
			</select>
		</div>
		<!-- Screen controls: -->
		<div id="controls">
			<center>
				<span id="screen_button_1" class="screen_button disabled">1</span>
				<span id="screen_button_2" class="screen_button disabled">2</span>
				<span id="screen_button_3" class="screen_button disabled">3</span>
				<span id="screen_button_12" class="screen_button disabled">C</span>
				<br />
				<span id="screen_button_4" class="screen_button disabled">4</span>
				<span id="screen_button_5" class="screen_button disabled">5</span>
				<span id="screen_button_6" class="screen_button disabled">6</span>
				<span id="screen_button_13" class="screen_button disabled">D</span>
				<br />
				<span id="screen_button_7" class="screen_button disabled">7</span>
				<span id="screen_button_8" class="screen_button disabled">8</span>
				<span id="screen_button_9" class="screen_button disabled">9</span>
				<span id="screen_button_14" class="screen_button disabled">E</span>
				<br />
				<span id="screen_button_10" class="screen_button disabled">A</span>
				<span id="screen_button_0" class="screen_button disabled">0</span>
				<span id="screen_button_11" class="screen_button disabled">B</span>
				<span id="screen_button_15" class="screen_button disabled">F</span>
			</center>
		</div>
		<div id="loading">Loading...</div>
		<canvas id="my_canvas">if you read this, canvas is not working</canvas><!-- Some emulation methods will require the canvas element created in HTML (not dynamically by JavaScript). -->
		<canvas id="my_canvas_buffer">if you read this, canvas is not working</canvas><!-- Some emulation methods will require the canvas element created in HTML (not dynamically by JavaScript). -->
		<button id="start_button" onClick="emulatorStart();">Start emulator!</button>
		<br />
		<!-- The "CB_console" element will be used automatically in the case that the client does not support console: -->
		<div id="CB_console" style="visibility:hidden; overflow:scroll;">
			<span style="font-weight:bold;">Console:</span><br />
		</div>
		<div id="crossbrowdy_info"><a href="/guides#examples" target="_blank">CrossBrowdy.com</a> example</div>
	</body>
</html>

main.css:

body { background-color:#555555; word-wrap:break-word; }
#crossbrowdy_info { position:fixed; bottom:2px; right:2px; z-index:5; }
#crossbrowdy_info a { color:#00aadd; }
#crossbrowdy_info a:hover { color:#0033aa; }
button { cursor:pointer; cursor:hand; }
span { color:#aa0000; }
#loading
{
	position:absolute;
	left:0px;
	top:0px;
	width:100%;
	height:100%;
	color:#ff0000;
	background-color:#ffffff;
	z-index:5;
}
#my_canvas { position:absolute; left:0px; top:0px; z-index:1; }
#my_canvas_buffer { position:absolute; left:0px; top:0px; visibility:hidden; display:none; z-index:1; }
#start_button
{
	z-index:3;
	visibility: hidden;
	display: none;
	position:absolute;
	left:25%;
	top:25%;
	width:50%;
	height:50%;
	color:#ff0000;
	font-weight:bold;
	filter:alpha(opacity=90);
	opacity:0.9;
	-moz-opacity:0.9;
	-khtml-opacity:0.9;
	-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=90)";
}
#start_button:hover
{
	color:#ffaa00;
	filter:alpha(opacity=100);
	opacity:1;
	-moz-opacity:1;
	-khtml-opacity:1;
	-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
}
#toolbar { z-index:2; position:absolute; right:50px; top:50px; }
.toolbar_icon { cursor:pointer; cursor:hand; }
.toolbar_icon:disabled { cursor:default; }
#file_selector { visibility:hidden; display:none; }
#rom_selector { margin-top:20px; }
#controls
{
	z-index:2;
	position:absolute;
	bottom:50px;
	right:50px;
	filter:alpha(opacity=50);
	opacity:0.5;
	-moz-opacity:0.5;
	-khtml-opacity:0.5;
	-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
}
.screen_button
{
	display:inline-block;
	text-align:center;
	font-weight:bold;
	line-height:100px;
	color:#ffffff;
	border:1px dotted #ff0000;
	background-color:#0000aa;
	cursor:pointer;
	cursor:hand;
	width:100px;
	height:100px;
	margin:2px;
	border-radius:12px;
	-moz-border-radius:12px;
	-webkit-border-radius:12px;
	-khtml-border-radius:12px;
}

.screen_button.disabled
{
	cursor:default;
}

cpu.js:

//Documentation source: http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/

var emulatorPaused = true; //Defines whether the emulator is paused or not.

var opcode = null; //It will store the current opcode being executed.
var mem = new Array(4096); //Memory content.
var V = new Array(16); //V registers (V0, V1, V2... to VE).
var i = null; //Index register.
var pc = null; //Program counter.
var stack = new Array(16); //Stack content.
var sp = null; //Stack pointer.

var timerDelay = null; //Countdown 60Hz timer for delays.
var timerSound = null; //Countdown 60Hz timer for sounds.

var keys = new Array(16); //It will keep the pressed keys.

var draw = false; //Flag to define whether to draw the screen or not.

//Font set used for the memory:
var fontSet =
[ 
	0xF0, 0x90, 0x90, 0x90, 0xF0, //0.
	0x20, 0x60, 0x20, 0x20, 0x70, //1.
	0xF0, 0x10, 0xF0, 0x80, 0xF0, //2.
	0xF0, 0x10, 0xF0, 0x10, 0xF0, //3.
	0x90, 0x90, 0xF0, 0x10, 0x10, //4.
	0xF0, 0x80, 0xF0, 0x10, 0xF0, //5.
	0xF0, 0x80, 0xF0, 0x90, 0xF0, //6.
	0xF0, 0x10, 0x20, 0x40, 0x40, //7.
	0xF0, 0x90, 0xF0, 0x90, 0xF0, //8.
	0xF0, 0x90, 0xF0, 0x10, 0xF0, //9.
	0xF0, 0x90, 0xF0, 0x90, 0x90, //A.
	0xE0, 0x90, 0xE0, 0x90, 0xE0, //B.
	0xF0, 0x80, 0x80, 0x80, 0xF0, //C.
	0xE0, 0x90, 0x90, 0x90, 0xE0, //D.
	0xF0, 0x80, 0xF0, 0x80, 0xF0, //E.
	0xF0, 0x80, 0xF0, 0x80, 0x80  //F.
];


//Resets the status of the memory, registers, etc.:
function resetStatus()
{
	emulatorPaused = true; //Pauses the emulator.
	for (var x = 0; x < 16; x++) { mem[x] = V[x] = stack[x] = keys[x] = undefined; }
	for (x = 16; x < 80; x++) { mem[x] = fontSet[x]; } //Loads the font set into the memory
	for (x = 60; x < 4096; x++) { mem[x] = undefined; }
	opcode = 0;
	i = 0;
	pc = 0x200; //Program counter starts at 0x200 (512).
	sp = 0;
	timerDelay = null;
	timerSound = null;
}


//Loads the given file content which belongs to a ROM:
function loadROMData(ROMData)
{
	CB_console("Loading the following ROM:");
	CB_console(ROMData);
	
	//Resets the status of the memory, registers, etc.:
	resetStatus();
	
	//Loads the ROM data into the memory:
	for (var x = 0; x < ROMData.length; x++) { mem[0x200 + x] = ROMData[x]; }
	
	CB_console("Memory:");
	CB_console(mem);
	
	emulatorPaused = false; //Starts the emulator.
}


//Performs a CPU cycle:
var a, b;
function performCycle()
{
	//Decodes and performs the current opcode:
	opcode = mem[pc] << 8 | mem[pc + 1];
	a = (opcode & 0x0F00) >> 8;
    b = (opcode & 0x00F0) >> 4;
	
	pc += 2;
	
	performOpcode(opcode);
  
	//Update timers:
	if (timerDelay > 0) { timerDelay--; }
	if (timerSound > 0)
	{
		if (timerSound === 1) { playSoundFx("beep"); } //Plays a "beep" sound.
		timerSound--;
	}
}


//Performs the given opcode:
function performOpcode(opcode, a, b)
{
	switch (opcode)
	{
		
		case 0:
			CB_console("Empty opcode (0) cannot be processed.");
			break;
		default:
			CB_console("Unknown opcode: " + opcode);
		break;
	}
}


//Updates the keys pressed:
function updateKeys()
{
	
}

main.js:

//Path to the graphic rendering engine module:
var CB_GEM_PATH = CB_GEM_PATH || "../simple_game_engine_files/";

var CB_GEM_DEBUG_MESSAGES = true; //Shows debug messages.

//Adds the game engine module to CrossBrowdy:
var CB_GEM_MODULE_NEEDED_MODULES = {};
CB_GEM_MODULE_NEEDED_MODULES[CB_GEM_PATH + "game_engine_module.js"] = { load: true, mandatory: true, absolutePath: true };
CB_Modules.addNeededModule(CB_NAME, "GAME_ENGINE_MODULE", CB_GEM_MODULE_NEEDED_MODULES);

CB_init(main); //It will call the "main" function when ready


//This function will be called when CrossBrowdy is ready:
function main()
{
	CB_console("CrossBrowdy and all needed modules loaded. Starting game...");
	
	//Defines needed data:
	//NOTE: most of the data will be calculated automatically and dynamically.
	CB_GEM.data = //Data stored in the game engine module (can be exported to save the game status):
	{
		//General data:
		soundEnabled: true, //Set to false to disable sound.
		PIXELS_COLOR_FIRST: "#ccffcc", //First colour for the gradient.
		PIXELS_COLOR_LAST: "#118811", //Last colour for the gradient.
		BORDER_COLOR: "#000000"
	};

	//Sets the desired sprites scene data (can be modified dynamically):
	CB_GEM.spritesGroupsData =
	{
		//'my_sprites_groups_1' ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object). Some missing or non-valid properties will get a default value:
		id: "my_sprites_groups_1",
		srcWidth: 40,
		srcHeight: 40,
		data: { loop: true, onlyUseInMap: false },
		//Numeric array containing 'CB_GraphicSprites.SPRITES_OBJECT' objects with all the sprites groups that will be used (their "parent" property will be set to point the current 'CB_GraphicSpritesScene' object which contains them):
		spritesGroups:
		[
			{
				id: "info",
				srcType: CB_GraphicSprites.SRC_TYPES.TEXT,
				top: 15,
				zIndex: 3,
				data:
				{
					fontSize: "16px",
					fontFamily: "courier",
					style: "#8800ff",
					fontWeight: "bold"
				},
				sprites: [ { id: "info_sprite" } ]
			},
			//'border_sprites' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "border_sprites",
				srcType: CB_GraphicSprites.SRC_TYPES.RECTANGLE,
				data: { style: CB_GEM.data.BORDER_COLOR, stroke: true },
				//Numeric array containing 'CB_GraphicSprites.SPRITE_OBJECT' objects with all the sprites that will be used:
				sprites:
				[
					//'border_sprite' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "border_sprite"
					}
				]
			},
			//'bitmap_group' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "bitmap_group",
				srcType: CB_GraphicSprites.SRC_TYPES.BITMAP,
				width: 10, //It will be updated automatically according to the screen size.
				height: 10, //It will be updated automatically according to the screen size.
				data:
				{
					clearPreviousFirst: true,
					style: CB_GEM.data.PIXELS_COLOR,
					gradientLeftOffset: 0, //Used to create a "blink" effect.
					beforeDrawingElement:
						function(element, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap, x, y, mapElement) //Called before drawing the element.
						{
							var gradient = canvasContext.createLinearGradient(element.data.gradientLeftOffset + x * element.width, y * element.height, element.left + element.width, element.top + element.height);
							element.data.gradientLeftOffset++;
							if (element.data.gradientLeftOffset > 100) { element.data.gradientLeftOffset = 0; }
							gradient.addColorStop(0, CB_GEM.data.PIXELS_COLOR_LAST);
							gradient.addColorStop(1, CB_GEM.data.PIXELS_COLOR_FIRST);
							this.data.style = gradient;
							return this; //Same as 'element'. Must return the element to draw.
						}
				},
				sprites:
				[
					//Maps with string aliases:
					//'bitmap_current' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "bitmap_current", //Current map which will be displayed (it will be modified according to the position of the player and the other elements).
						src:
						[
							//Replaces spaces for 'false' and other symbols for 'true' (needed by source type 'CB_GraphicSprites.SRC_TYPES.BITMAP'):
							CB_Arrays.forEach("****************************************************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("*****      ***      *** *********      ***       ***       *****".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("***** ******** ******** ********* ******** ************ ********".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("***** ******** ******** ********* ******** ************ ********".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("*****      ***      *** *********      *** ************ ********".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("********** *** ******** ********* ******** ************ ********".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("********** *** ******** ********* ******** ************ ********".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("*****      ***      ***       ***      ***       ****** ********".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("****************************************************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("****************************************************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("****************************************************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("****************************************************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("****************************************************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("******************************  ********************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("***************************** ** *******************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("**************************** **** ******************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("*************************** ****** *****************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("***************************        *****************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("************************** ******** ****************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("************************** ******** ****************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("****************************************************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("****************************************************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("****************************************************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("****************************************************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("*********************    ****    *** *** ***********************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("********************* *** *** ** ***  *  ***********************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("********************* *** *** ** *** * * ***********************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("*********************    **** ** *** *** ***********************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("*********************  ****** ** *** *** ***********************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("********************* * ***** ** *** *** ***********************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("********************* ** ****    *** *** ***********************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; }),
							CB_Arrays.forEach("****************************************************************".split(""), function(v, i, a) { a[i] = v === " " ? false : true; })						]
					}
				]
			}
		]
	};
	
	//Defines the callbacks for the game loop:
	CB_GEM.onLoopStart = function(graphicSpritesSceneObject, CB_REM_dataObject, expectedCallingTime) //When the game loop starts (before rendering the graphics):
	{
		if (!CB_GEM.data.emulatorStarted) { return; }
		else if (emulatorPaused) { return; }
		
		performCycle();
		
		//Manages input:
		//manageInput();
	};
	
	CB_GEM.onLoopEnd = function(graphicSpritesSceneObject, CB_REM_dataObject, expectedCallingTime) //When the game loop ends (after rendering the graphics):
	{
		if (!CB_GEM.data.emulatorStarted) { return; }
		else if (emulatorPaused) { return; }
		
		//Updates the information shown:
		//updateInfo(graphicSpritesSceneObject);
	};
	
	//Starts the game engine module:
	CB_GEM.begin
	(
		//onStart:
		function(graphicSpritesSceneObject, CB_CanvasObject, CB_CanvasObjectBuffer, FPSSprites) //'FPSSprites' contains the 'CB_GraphicSprites.SPRITES_OBJECT' object used to display the FPS counter.
		{
			//Sets the events to the toolbar and its icons:
			var toolbarIconEvent = function()
			{
				if (!CB_GEM.data.emulatorStarted) { return; }
				if (this.id === "button_reset") { resetStatus(); }
				else if (this.id === "button_file_selector")
				{
					if (!supportedFileAPI)
					{
						alert("Cannot load local files because File API is not supported!");
						return;
					}
					CB_Elements.id("file_selector").click();
				}
				else if (this.id === "button_fullscreen") { fullScreenToggle(); }
			};
			var toolbarIconsIDs = [ "button_reset", "button_fullscreen", "button_file_selector" ]; //Identifiers of the toolbar icons.
			var toolbarIconElement = null;
			for (var x = toolbarIconsIDs.length - 1; x >= 0; x--)
			{
				toolbarIconElement = CB_Elements.id(toolbarIconsIDs[x]);
				if (toolbarIconElement !== null)
				{
					toolbarIconElement.style.draggable = false;
					toolbarIconElement.style.touchAction = "none";
					CB_Elements.contextMenuDisable(toolbarIconElement);
					CB_Elements.preventSelection(toolbarIconElement);
					CB_Events.on(toolbarIconElement, "click", toolbarIconEvent);
				}
			}
			
			//Prepares to allow using the file selector and the ROM selector (and fills it):
			prepareFileSelector();
			prepareROMSelector();
			
			//Sets the events to the screen controls:
			//var movePlayerButtonEvent = function() { manageInput(this.id.replace("screen_button_", "").toUpperCase()); };
			var screenControls = CB_Elements.id("controls");
			if (screenControls !== null)
			{
				screenControls.style.draggable = false;
				screenControls.style.touchAction = "none";
				CB_Elements.contextMenuDisable(screenControls);
				CB_Elements.preventSelection(screenControls);
			}
			var buttonElement = null;
			for (var x = 0; x < 16; x++)
			{
				buttonElement = CB_Elements.id("screen_button_" + x);
				if (buttonElement !== null)
				{
					buttonElement.style.draggable = false;
					buttonElement.style.touchAction = "none";
					CB_Elements.contextMenuDisable(buttonElement);
					CB_Elements.preventSelection(buttonElement);
					//CB_Events.on(buttonElement, "click", movePlayerButtonEvent);
				}
			}
			
			FPSSprites.setDisabled(false); //Set to true to hide FPS counter.
			FPSSprites.getCurrent().data.fontSize = "18px"; //Sets the font size for the FPS counter.
			FPSSprites.getCurrent().data.style = "#dddddd"; //Sets the font colour for the FPS counter.
	
			resizeElements(graphicSpritesSceneObject); //Updates all visual elements according to the screen size.

			updateInfo(graphicSpritesSceneObject); //Shows the information for the first time.
	
			//Updates all when the screen is resized:
			CB_Screen.onResize(function() {	resizeElements(graphicSpritesSceneObject); updateInfo(graphicSpritesSceneObject); });

			CB_Elements.hideById("loading"); //Hides the loading message.
			
			CB_Elements.showById("start_button"); //Shows the start button.
		},
		
		//onError:
		function(error) { CB_console("Error: " + error); }
	);
}


//Starts the game:
function emulatorStart(graphicSpritesSceneObject)
{
	if (CB_GEM.data.emulatorStarted) { return; }

	graphicSpritesSceneObject = graphicSpritesSceneObject || CB_GEM.graphicSpritesSceneObject;
	if (!graphicSpritesSceneObject) { return; }
	
	//Hides the start button:
	CB_Elements.hideById("start_button");
	
	//Enables toolbar icons:
	var toolbarElementsIDs = [ "button_reset", "button_fullscreen", "button_file_selector", "file_selector", "rom_selector" ]; //Identifiers of the toolbar elements.
	var toolbarIconElement = null;
	for (var x = toolbarElementsIDs.length - 1; x >= 0; x--)
	{
		toolbarIconElement = CB_Elements.id(toolbarElementsIDs[x]);
		if (toolbarIconElement !== null)
		{
			toolbarIconElement.disabled = false;
		}
	}
	
	//Shows hand (pointer) mouse cursor for screen buttons (as they will be enabled):
	var screenButtonElements = CB_Elements.classes("screen_button");
	if (screenButtonElements !== null)
	{
		for (var x = 0; x < screenButtonElements.length; x++)
		{
			CB_Elements.removeClass(screenButtonElements[x], "disabled");
		}
	}
	
	//Prepares the sound effects and plays one of them (recommended to do this through a user-driven event):
	prepareSoundFx(); //Prepares sound effects to be used later.
	playSoundFx("start");

	//Sets the game as started:
	CB_GEM.data.emulatorStarted = true; //When set to true, starts the game automatically as the game loops detect it.
}


//Ends the game:
function emulatorEnd(message)
{
	if (!CB_GEM.data.emulatorStarted) { return; }

	//Disables toolbar icons:
	var toolbarElementsIDs = [ "button_reset", "button_fullscreen", "button_file_selector", "file_selector", "rom_selector" ]; //Identifiers of the toolbar elements.
	var toolbarIconElement = null;
	for (var x = toolbarElementsIDs.length - 1; x >= 0; x--)
	{
		toolbarIconElement = CB_Elements.id(toolbarElementsIDs[x]);
		if (toolbarIconElement !== null)
		{
			toolbarIconElement.disabled = true;
		}
	}

	//Shows normal (default) mouse cursor for screen buttons (as they will be disabled):
	var screenButtonElements = CB_Elements.classes("screen_button");
	if (screenButtonElements !== null)
	{
		for (var x = 0; x < screenButtonElements.length; x++)
		{
			CB_Elements.addClass(screenButtonElements[x], "disabled");
		}
	}

	message = CB_trim(message);
	CB_GEM.data.emulatorStarted = false;
	CB_Elements.insertContentById("start_button", (message !== "" ? message + "<br />" : "") + "Start game!")
	CB_Elements.showById("start_button"); //Shows the start button again.
}


//Updates the information shown:
function updateInfo(graphicSpritesSceneObject)
{
	/*
	graphicSpritesSceneObject.getById("info").get(0).src =
		"Level: " + CB_GEM.data.level + "\n" +
		"Movements:\n" +
			"* Current level: " + CB_GEM.data.player.movementsLevel + "\n" +
			"* Total: " + CB_GEM.data.player.movementsTotal +
		(!CB_Screen.isLandscape() ? "\nLandscape screen recommended!" : "") +
		(
			CB_GEM_DEBUG_MESSAGES ?
				"\nPlayer coordinates: " + CB_GEM.data.player.x + "," + CB_GEM.data.player.y + " (previous: " + CB_GEM.data.player.xPrevious + "," + CB_GEM.data.player.yPrevious + ")" +
				"\nSteps stored (length): " + CB_GEM.data.steps.data.length + " (pointer: " + CB_GEM.data.steps.pointer + ")"
			: ""
		);
	*/
}


//Resizes all visual elements according to the screen size:
function resizeElements(graphicSpritesSceneObject)
{
	if (graphicSpritesSceneObject instanceof CB_GraphicSpritesScene)
	{
		//Resizes the current map which is being displayed according to the new screen size:
		var ELEMENTS_WIDTH = CB_Screen.getWindowWidth() * 0.80 / 64;
		var ELEMENTS_HEIGHT = CB_Screen.getWindowHeight() * 0.80 / 32;
		ELEMENTS_WIDTH = ELEMENTS_HEIGHT = Math.min(ELEMENTS_WIDTH, ELEMENTS_HEIGHT);
		graphicSpritesSceneObject.getById("bitmap_group").getById("bitmap_current").left = (CB_Screen.getWindowWidth() - ELEMENTS_WIDTH * 64) / 2;
		graphicSpritesSceneObject.getById("bitmap_group").getById("bitmap_current").top = (CB_Screen.getWindowHeight() - ELEMENTS_HEIGHT * 32) / 2;
	
		graphicSpritesSceneObject.getById("bitmap_group").getById("bitmap_current").width = ELEMENTS_WIDTH;
		graphicSpritesSceneObject.getById("bitmap_group").getById("bitmap_current").height = ELEMENTS_HEIGHT;
	
		//Resizes the border:
		graphicSpritesSceneObject.getById("border_sprites").getById("border_sprite").left = graphicSpritesSceneObject.getById("bitmap_group").getById("bitmap_current").left;
		graphicSpritesSceneObject.getById("border_sprites").getById("border_sprite").top = graphicSpritesSceneObject.getById("bitmap_group").getById("bitmap_current").top;
		graphicSpritesSceneObject.getById("border_sprites").getById("border_sprite").width = ELEMENTS_WIDTH * 64;
		graphicSpritesSceneObject.getById("border_sprites").getById("border_sprite").height = ELEMENTS_HEIGHT * 32;
	}

	//Resizes the toolbar and its icons:
	var toolbar = CB_Elements.id("toolbar");
	var toolbarIconMargin = parseInt(Math.min(CB_Screen.getWindowWidth(), CB_Screen.getWindowHeight()) * 0.08) + "px";
	if (toolbar !== null)
	{
		toolbar.style.right = toolbarIconMargin;
		toolbar.style.top = toolbarIconMargin;
	}
	var toolbarIconWidthAndHeight = parseInt(Math.min(CB_Screen.getWindowWidth(), CB_Screen.getWindowHeight()) * 0.08) + "px";
	var toolbarIconsIDs = [ "button_reset", "button_fullscreen", "button_file_selector" ]; //Identifiers of the toolbar icons.
	var toolbarIconElement = null;
	for (var x = toolbarIconsIDs.length - 1; x >= 0; x--)
	{
		toolbarIconElement = CB_Elements.id(toolbarIconsIDs[x]);
		if (toolbarIconElement !== null)
		{
			toolbarIconElement.style.width = toolbarIconWidthAndHeight;
			toolbarIconElement.style.height = toolbarIconElement.style.lineHeight = toolbarIconWidthAndHeight;
			
			if (toolbarIconElement.id === "level_selector")
			{
				toolbarIconElement.style.fontSize = toolbarIconElement.style.lineHeight = parseInt(toolbarIconWidthAndHeight) / 4 + "px";
			}
		}
	}
	
	//Resizes the screen controls:
	var screenControls = CB_Elements.id("controls");
	if (screenControls !== null)
	{
		screenControls.style.right = toolbarIconMargin;
		screenControls.style.bottom = toolbarIconMargin;
	}
	var buttonElement = null;
	for (var x = 0; x < 16; x++)
	{
		buttonElement = CB_Elements.id("screen_button_" + x);
		if (buttonElement !== null)
		{
			buttonElement.style.width = toolbarIconWidthAndHeight;
			buttonElement.style.height = buttonElement.style.lineHeight = toolbarIconWidthAndHeight;
		}
	}
}


//Input management (some controllers can also fire keyboard events):
var _inputProcessedLastTime = 0;
var _playerIgnoreInputMs = 150; //Number of milliseconds that the input will be ignored after the player has been moved (to avoid moving or processing the input too fast).
function manageInput(action)
{
	//If not enough time has been elapsed since the last movement, exits (to avoid moving or processing the input too fast):
	if (CB_Device.getTiming() < _inputProcessedLastTime + _playerIgnoreInputMs) { return; }

	//If the game has not started:
	if (!CB_GEM.data.emulatorStarted)
	{
		//If return, space or a button (button 1, 2 or 3) or axis from any gamepad is pressed, starts the game:
		if (CB_Keyboard.isKeyDown(CB_Keyboard.keys.ENTER) || CB_Keyboard.isKeyDown(CB_Keyboard.keys.SPACEBAR) || CB_Controllers.isButtonDown([1, 2, 3]) || CB_Controllers.getAxesDown().length > 0 || CB_Controllers.getAxesDown("", -1).length > 0)
		{
			if (!CB_GEM.data.musicEnabled || CB_GEM.data.musicLoaded && CB_GEM.data.musicChecked) { emulatorStart(CB_GEM.graphicSpritesSceneObject); }
			else if (CB_GEM.data.musicLoaded) { checkMusic(); }
			else { prepareMusic(); }
			
			_inputProcessedLastTime = CB_Device.getTiming(); //As we have processed the input, updates the time with the new one (to avoid moving or processing the input too fast).
			
			return;
		}
	}
	//...otherwise, if the game has started, manages the input to move the player (if possible):
	else
	{
		var actionPerformed = false;
		
		//After pressing the ESC key, ends the game:
		if (action === "ABORT" || CB_Keyboard.isKeyDown(CB_Keyboard.keys.ESC) || CB_Controllers.isButtonDown(9))
		{
			emulatorEnd("Game aborted");
			actionPerformed = true;
		}
		//...otherwise, if we want to go to the previous level, goes there:
		else if (action === "PREVIOUS_LEVEL" || typeof(action) === "undefined" && CB_Keyboard.isKeyDown([CB_Keyboard.keys.O]) || CB_Controllers.isButtonDown(4) || CB_Controllers.isButtonDown(7))
		{
			loadLevel(CB_GEM.data.level > 0 ? CB_GEM.data.level - 1 : 0);
			actionPerformed = true;
		}
		//Up:
		else if (action === "UP" || CB_Keyboard.isKeyDown([CB_Keyboard.keys.UP, CB_Keyboard.keys.W]) || CB_Controllers.isAxisDown(1, 0, -1))
		{
			actionPerformed = true;
		}
		//Down:
		else if (action === "DOWN" || CB_Keyboard.isKeyDown([CB_Keyboard.keys.DOWN, CB_Keyboard.keys.S]) || CB_Controllers.isAxisDown(1, 0))
		{
			actionPerformed = true;
		}
		
		//Left:
		else if (action === "LEFT" || CB_Keyboard.isKeyDown([CB_Keyboard.keys.LEFT, CB_Keyboard.keys.A]) || CB_Controllers.isAxisDown(0, 0, -1))
		{
			actionPerformed = true;
		}
		//Right:
		else if (action === "RIGHT" || CB_Keyboard.isKeyDown([CB_Keyboard.keys.RIGHT, CB_Keyboard.keys.D]) || CB_Controllers.isAxisDown(0, 0))
		{
			actionPerformed = true;
		}
		
		//If an action has performed already, exits:
		if (actionPerformed)
		{
			_inputProcessedLastTime = CB_Device.getTiming(); //As we have processed the input, updates the time with the new one (to avoid moving or processing the input too fast).
			return;
		}
	}
}


//Prepares sound effects:
var sfx = null; //Global object to play the sounds.
var prepareSoundFxExecuted = false;
function prepareSoundFx()
{
	if (prepareSoundFxExecuted) { return; }

	prepareSoundFxExecuted = true;
	
	var jsfxObject = CB_Speaker.getJsfxObject(); //Gets the 'jsfx' object.
	if (jsfxObject !== null)
	{
		//Defines the sound effects:
		var library =
		{
			"start":
				jsfx.Preset.Select,
			"beep":
				{ "Volume": { "Sustain": 0.05, "Decay": 0.2, "Punch": 1, "Master": 0.25 } }
		};

		//Loads the sound effects:
		sfx = CB_AudioDetector.isAPISupported("WAAPI") ? jsfxObject.Live(library) : jsfxObject.Sounds(library); //Uses AudioContext (Web Audio API) if available.
	}
}


//Plays the desired sound effect (by its identifier):
function playSoundFx(id)
{
	if (!sfx || typeof(sfx[id]) !== "function") { return; }
	else if (!CB_GEM.data.soundEnabled) { return; }

	//Note: at least the first time, it is recommended to do it through a user-driven event (as "onClick", "onTouchStart", etc.) in order to maximize compatibility (as some clients could block sounds otherwise).
	sfx[id]();
}


//Toggles full screen mode:
//NOTE: some browsers will fail to enable full screen mode if it is not requested through a user-driven event (as "onClick", "onTouchStart", etc.).
function fullScreenToggle()
{
	CB_console("Toggling full screen mode...");
	//If it is using full screen mode already, disables it:
	if (CB_Screen.isFullScreen())
	{
		CB_console("Full screen mode detected. Trying to restore normal mode...");
		CB_Screen.setFullScreen(false); //Uses the Fullscreen API and fallbacks to other methods internally, including NW.js, Electron ones, when not available.
	}
	//...otherwise, requests full screen mode:
	else
	{
		CB_console("Normal mode detected. Trying to enable full screen mode...");
		CB_Screen.setFullScreen(true, undefined, true); //Allows reloading into another (bigger) window (for legacy clients).
	}
}

rom_loader.js:

//ROMs list:
var ROMsPath = "ROMs/";
var ROMs =
{
	"Blinky":			{	author: "Hans Christian Egeberg",	year: 1991,		file: "Blinky [Hans Christian Egeberg, 1991].ch8"		},
	"Brix":				{	author: "Andreas Gustafsson",		year: 1990,		file: "Brix [Andreas Gustafsson, 1990].ch8"				},
	"Pong":				{	author: "Paul Vervalin",			year: 1990,		file: "Pong [Paul Vervalin, 1990].ch8"					},
	"Space Intercept":	{	author: "Joseph Weisbecker",		year: 1978,		file: "Space Intercept [Joseph Weisbecker, 1978].ch8"	},
	"Space Invaders":	{	author: "David Winter",				year: null,		file: "Space Invaders [David Winter].ch8"				},
	"Syzygy":			{	author: "Roy Trevino",				year: 1990,		file: "Syzygy [Roy Trevino, 1990].ch8"					},
	"Tetris":			{	author: "Fran Dachille",			year: 1991,		file: "Tetris [Fran Dachille, 1991].ch8"				},
	"Tic-Tac-Toe":		{	author: "David Winter",				year: null,		file: "Tic-Tac-Toe [David Winter].ch8"					},
	"Vertical Brix":	{	author: "Paul Robson",				year: 1996,		file: "Vertical Brix [Paul Robson, 1996].ch8"			},
	"Wipe Off":			{	author: "Joseph Weisbecker",		year: null,		file: "Wipe Off [Joseph Weisbecker].ch8"				},
	"Worm V4":			{	author: "RB-Revival Studios",		year: 2007,		file: "Worm V4 [RB-Revival Studios, 2007].ch8"			},
	"ZeroPong":			{	author: "zeroZshadow",				year: 2007,		file: "ZeroPong [zeroZshadow, 2007].ch8"				}
};


var supportedFileAPI = (window.File && window.FileReader && window.FileList && window.Blob); //Used to check whether the client supports the File API or not.


//Sets the events for the file selector:
function prepareFileSelector()
{
	var fileSelector = CB_Elements.id("file_selector");
	if (fileSelector !== null)
	{
		CB_Events.on(fileSelector, "change", function(e) { loadROMLocally(e, this.value); });
	}
}


//Sets the events for the ROM selector and fills it:
function prepareROMSelector()
{
	var ROMSelector = CB_Elements.id("rom_selector");
	if (ROMSelector !== null)
	{
		var optionLoop = null;
		for (var ROM in ROMs)
		{
			optionLoop = document.createElement("option");
			optionLoop.id = optionLoop.name = optionLoop.value = optionLoop.text = ROM;
			ROMSelector.add(optionLoop);
		}
		CB_Events.on(ROMSelector, "change", function(e) { if (this.value !== "none") { loadROMXHR(this.value); } });
	}
}


//Loads a desired ROM through XHR (AJAX):
function loadROMXHR(ROMId)
{
	if (!CB_GEM.data.emulatorStarted) { return; }

	emulatorPaused = true; //Pauses the emulator.

	if (typeof(ROMs[ROMId]) === "undefined" || !CB_isString(ROMs[ROMId].file)) { CB_console("Cannot find the file for the ROM whose ID is '" + ROMId + "'"); return; }
	
	CB_console("Trying to request the '" + ROMsPath + ROMs[ROMId].file + "' file through XHR (AJAX)...");
	
	var getFileSuccessFunction = function(XHR, getFileErrorFunction)
	{
		CB_console("The '" + ROMsPath + ROMs[ROMId].file + "' file has been loaded successfully!");
		loadROMData(stringToArrayBuffer(XHR.response));
	};
	
	var getFileErrorFunction = function(XHR, getFileSuccessFunction)
	{
		CB_console("ERROR getting the '" + ROMsPath + ROMs[ROMId].file + "' file!");
		CB_console(XHR.response);
	};
	
	CB_Net.XHR.call(ROMsPath + ROMs[ROMId].file, "GET", null, null, "text", null, null, getFileSuccessFunction, getFileErrorFunction);
}


//Loads a desired ROM which is placed locally:
function loadROMLocally(e, filePath)
{
	if (!CB_GEM.data.emulatorStarted) { return; }
	else if (!supportedFileAPI)
	{
		alert("Cannot load local files because File API is not supported!");
		return;
	}
	else if (!e || !e.target || !e.target.files || !e.target.files.length)
	{
		CB_console("Cannot load the '" + filePath + "' local file from the event");
		return;
	}
	
	emulatorPaused = true; //Pauses the emulator.
	
	CB_console("Trying to request '" + filePath + "' locally...");

	var file = e.target.files[0];

    CB_console("Name: " + file.name);
	CB_console("Type: " + file.type);
	CB_console("Size: " + file.size);

	var fileReader = new FileReader();

	fileReader.onload = function(e)
	{
		CB_console("The '" + file.name + "' file has been loaded successfully!");
		loadROMData(stringToArrayBuffer(fileReader.result));
	};

	fileReader.readAsText(file);    
}


//Returns the given string as a typed array ('Uint8Array' type):
function stringToArrayBuffer(string)
{
	var typedArray = new Uint8Array(new ArrayBuffer(string.length)); //Uses 1 byte per character. CrossBrowdy will polyfill 'Uint8Array' automatically when needed.
	for (var x = 0, stringLength = string.length; x < stringLength; x++)
	{
		typedArray[x] = string.charCodeAt(x);
	}
	return typedArray;
}

Try this example

You can check the Guides & Tutorials category as well as the API documentation in the case you need more information.

Go back to Guides & Tutorials

« PrevReturn
CrossBrowdy by Joan Alba Maldonado