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.
<!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>
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;
}
//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()
{
}
//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).
}
}
//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;
}
You can check the Guides & Tutorials category as well as the API documentation in the case you need more information.