diff options
| author | Franklin Wei <frankhwei536@gmail.com> | 2016-11-20 15:16:41 -0500 |
|---|---|---|
| committer | Franklin Wei <frankhwei536@gmail.com> | 2016-11-24 16:23:09 -0500 |
| commit | 56c9984511f016eab7e1278ba9e40d88bb59a162 (patch) | |
| tree | 1bfa6d3aeb3bf2a6ffec71387ac073cd0b8b2a51 /apps/plugins/puzzles/emccpre.js | |
| parent | 29648f817677b84c03c2bcfe89eb8cf53653e7db (diff) | |
| download | rockbox-puzzles.zip rockbox-puzzles.tar.gz rockbox-puzzles.tar.bz2 rockbox-puzzles.tar.xz | |
[WIP] Port of Simon Tatham's Puzzle Collectionpuzzles
Original revision: 5123b1bf68777ffa86e651f178046b26a87cf2d9
MIT Licensed. Some games still crash and others are unplayable due to
issues with controls. Still need a "real" polygon filling algorithm.
The following games are at least partially broken for various reasons:
Cube: crash with certain settings
Galaxies: crash
Inertia: crash
Keen: input issues
Loopy: weird stuff happens
Map: crash on input
Mines: weird stuff happens on target
Palisade: input issues
Signpost: crash on input
Solo: input issues
Towers: input and drawing issues
Train Tracks: drawing issues
Twiddle: weird animation on target
Undead: input and drawing issues
Unequal: input and drawing issues
Untangle: input issues
All in all, about 40% of the games are at least partially broken.
Change-Id: I7c69b6860ab115f973c8d76799502e9bb3d52368
Diffstat (limited to 'apps/plugins/puzzles/emccpre.js')
| -rw-r--r-- | apps/plugins/puzzles/emccpre.js | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/emccpre.js b/apps/plugins/puzzles/emccpre.js new file mode 100644 index 0000000..ebf67d1 --- /dev/null +++ b/apps/plugins/puzzles/emccpre.js @@ -0,0 +1,364 @@ +/* + * emccpre.js: one of the Javascript components of an Emscripten-based + * web/Javascript front end for Puzzles. + * + * The other parts of this system live in emcc.c and emcclib.js. It + * also depends on being run in the context of a web page containing + * an appropriate collection of bits and pieces (a canvas, some + * buttons and links etc), which is generated for each puzzle by the + * script html/jspage.pl. + * + * This file contains the Javascript code which is prefixed unmodified + * to Emscripten's output via the --pre-js option. It declares all our + * global variables, and provides the puzzle init function and a + * couple of other helper functions. + */ + +// To avoid flicker while doing complicated drawing, we use two +// canvases, the same size. One is actually on the web page, and the +// other is off-screen. We do all our drawing on the off-screen one +// first, and then copy rectangles of it to the on-screen canvas in +// response to draw_update() calls by the game backend. +var onscreen_canvas, offscreen_canvas; + +// A persistent drawing context for the offscreen canvas, to save +// constructing one per individual graphics operation. +var ctx; + +// Bounding rectangle for the copy to the onscreen canvas that will be +// done at drawing end time. Updated by js_canvas_draw_update and used +// by js_canvas_end_draw. +var update_xmin, update_xmax, update_ymin, update_ymax; + +// Module object for Emscripten. We fill in these parameters to ensure +// that Module.run() won't be called until we're ready (we want to do +// our own init stuff first), and that when main() returns nothing +// will get cleaned up so we remain able to call the puzzle's various +// callbacks. +var Module = { + 'noInitialRun': true, + 'noExitRuntime': true +}; + +// Variables used by js_canvas_find_font_midpoint(). +var midpoint_test_str = "ABCDEFGHIKLMNOPRSTUVWXYZ0123456789"; +var midpoint_cache = []; + +// Variables used by js_activate_timer() and js_deactivate_timer(). +var timer = null; +var timer_reference_date; + +// void timer_callback(double tplus); +// +// Called every 20ms while timing is active. +var timer_callback; + +// The status bar object, if we create one. +var statusbar = null; + +// Currently live blitters. We keep an integer id for each one on the +// JS side; the C side, which expects a blitter to look like a struct, +// simply defines the struct to contain that integer id. +var blittercount = 0; +var blitters = []; + +// State for the dialog-box mechanism. dlg_dimmer and dlg_form are the +// page-darkening overlay and the actual dialog box respectively; +// dlg_next_id is used to allocate each checkbox a unique id to use +// for linking its label to it (see js_dialog_boolean); +// dlg_return_funcs is a list of JS functions to be called when the OK +// button is pressed, to pass the results back to C. +var dlg_dimmer = null, dlg_form = null; +var dlg_next_id = 0; +var dlg_return_funcs = null; + +// void dlg_return_sval(int index, const char *val); +// void dlg_return_ival(int index, int val); +// +// C-side entry points called by functions in dlg_return_funcs, to +// pass back the final value in each dialog control. +var dlg_return_sval, dlg_return_ival; + +// The <select> object implementing the game-type drop-down, and a +// list of the <option> objects inside it. Used by js_add_preset(), +// js_get_selected_preset() and js_select_preset(). +// +// gametypethiscustom is an option which indicates some custom game +// params you've already set up, and which will be auto-selected on +// return from the customisation dialog; gametypenewcustom is an +// option which you select to indicate that you want to bring up the +// customisation dialog and select a new configuration. Ideally I'd do +// this with just one option serving both purposes, but instead we +// have to do this a bit oddly because browsers don't send 'onchange' +// events for a select element if you reselect the same one - so if +// you've picked a custom setup and now want to change it, you need a +// way to specify that. +var gametypeselector = null, gametypeoptions = []; +var gametypethiscustom = null, gametypehiddencustom = null; + +// The two anchors used to give permalinks to the current puzzle. Used +// by js_update_permalinks(). +var permalink_seed, permalink_desc; + +// The undo and redo buttons. Used by js_enable_undo_redo(). +var undo_button, redo_button; + +// A div element enclosing both the puzzle and its status bar, used +// for positioning the resize handle. +var resizable_div; + +// Helper function to find the absolute position of a given DOM +// element on a page, by iterating upwards through the DOM finding +// each element's offset from its parent, and thus calculating the +// page-relative position of the target element. +function element_coords(element) { + var ex = 0, ey = 0; + while (element.offsetParent) { + ex += element.offsetLeft; + ey += element.offsetTop; + element = element.offsetParent; + } + return {x: ex, y:ey}; +} + +// Helper function which is passed a mouse event object and a DOM +// element, and returns the coordinates of the mouse event relative to +// the top left corner of the element by subtracting element_coords +// from event.page{X,Y}. +function relative_mouse_coords(event, element) { + var ecoords = element_coords(element); + return {x: event.pageX - ecoords.x, + y: event.pageY - ecoords.y}; +} + +// Init function called from body.onload. +function initPuzzle() { + // Construct the off-screen canvas used for double buffering. + onscreen_canvas = document.getElementById("puzzlecanvas"); + offscreen_canvas = document.createElement("canvas"); + offscreen_canvas.width = onscreen_canvas.width; + offscreen_canvas.height = onscreen_canvas.height; + + // Stop right-clicks on the puzzle from popping up a context menu. + // We need those right-clicks! + onscreen_canvas.oncontextmenu = function(event) { return false; } + + // Set up mouse handlers. We do a bit of tracking of the currently + // pressed mouse buttons, to avoid sending mousemoves with no + // button down (our puzzles don't want those events). + mousedown = Module.cwrap('mousedown', 'void', + ['number', 'number', 'number']); + buttons_down = 0; + onscreen_canvas.onmousedown = function(event) { + var xy = relative_mouse_coords(event, onscreen_canvas); + mousedown(xy.x, xy.y, event.button); + buttons_down |= 1 << event.button; + onscreen_canvas.setCapture(true); + }; + mousemove = Module.cwrap('mousemove', 'void', + ['number', 'number', 'number']); + onscreen_canvas.onmousemove = function(event) { + if (buttons_down) { + var xy = relative_mouse_coords(event, onscreen_canvas); + mousemove(xy.x, xy.y, buttons_down); + } + }; + mouseup = Module.cwrap('mouseup', 'void', + ['number', 'number', 'number']); + onscreen_canvas.onmouseup = function(event) { + if (buttons_down & (1 << event.button)) { + buttons_down ^= 1 << event.button; + var xy = relative_mouse_coords(event, onscreen_canvas); + mouseup(xy.x, xy.y, event.button); + } + }; + + // Set up keyboard handlers. We do all the actual keyboard + // handling in onkeydown; but we also call event.preventDefault() + // in both the keydown and keypress handlers. This means that + // while the canvas itself has focus, _all_ keypresses go only to + // the puzzle - so users of this puzzle collection in other media + // can indulge their instinct to press ^R for redo, for example, + // without accidentally reloading the page. + key = Module.cwrap('key', 'void', ['number', 'number', 'string', + 'string', 'number', 'number']); + onscreen_canvas.onkeydown = function(event) { + key(event.keyCode, event.charCode, event.key, event.char, + event.shiftKey ? 1 : 0, event.ctrlKey ? 1 : 0); + event.preventDefault(); + }; + onscreen_canvas.onkeypress = function(event) { + event.preventDefault(); + }; + + // command() is a C function called to pass back events which + // don't fall into other categories like mouse and key events. + // Mostly those are button presses, but there's also one for the + // game-type dropdown having been changed. + command = Module.cwrap('command', 'void', ['number']); + + // Event handlers for buttons and things, which call command(). + document.getElementById("specific").onclick = function(event) { + // Ensure we don't accidentally process these events when a + // dialog is actually active, e.g. because the button still + // has keyboard focus + if (dlg_dimmer === null) + command(0); + }; + document.getElementById("random").onclick = function(event) { + if (dlg_dimmer === null) + command(1); + }; + document.getElementById("new").onclick = function(event) { + if (dlg_dimmer === null) + command(5); + }; + document.getElementById("restart").onclick = function(event) { + if (dlg_dimmer === null) + command(6); + }; + undo_button = document.getElementById("undo"); + undo_button.onclick = function(event) { + if (dlg_dimmer === null) + command(7); + }; + redo_button = document.getElementById("redo"); + redo_button.onclick = function(event) { + if (dlg_dimmer === null) + command(8); + }; + document.getElementById("solve").onclick = function(event) { + if (dlg_dimmer === null) + command(9); + }; + + gametypeselector = document.getElementById("gametype"); + gametypeselector.onchange = function(event) { + if (dlg_dimmer === null) + command(2); + }; + + // In IE, the canvas doesn't automatically gain focus on a mouse + // click, so make sure it does + onscreen_canvas.addEventListener("mousedown", function(event) { + onscreen_canvas.focus(); + }); + + // In our dialog boxes, Return and Escape should be like pressing + // OK and Cancel respectively + document.addEventListener("keydown", function(event) { + + if (dlg_dimmer !== null && event.keyCode == 13) { + for (var i in dlg_return_funcs) + dlg_return_funcs[i](); + command(3); + } + + if (dlg_dimmer !== null && event.keyCode == 27) + command(4); + }); + + // Set up the function pointers we haven't already grabbed. + dlg_return_sval = Module.cwrap('dlg_return_sval', 'void', + ['number','string']); + dlg_return_ival = Module.cwrap('dlg_return_ival', 'void', + ['number','number']); + timer_callback = Module.cwrap('timer_callback', 'void', ['number']); + + // Save references to the two permalinks. + permalink_desc = document.getElementById("permalink-desc"); + permalink_seed = document.getElementById("permalink-seed"); + + // Default to giving keyboard focus to the puzzle. + onscreen_canvas.focus(); + + // Create the resize handle. + var resize_handle = document.createElement("canvas"); + resize_handle.width = 10; + resize_handle.height = 10; + { + var ctx = resize_handle.getContext("2d"); + ctx.beginPath(); + for (var i = 1; i <= 7; i += 3) { + ctx.moveTo(8.5, i + 0.5); + ctx.lineTo(i + 0.5, 8.5); + } + ctx.lineWidth = '1px'; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + ctx.strokeStyle = '#000000'; + ctx.stroke(); + } + resizable_div = document.getElementById("resizable"); + resizable_div.appendChild(resize_handle); + resize_handle.style.position = 'absolute'; + resize_handle.style.zIndex = 98; + resize_handle.style.bottom = "0"; + resize_handle.style.right = "0"; + resize_handle.style.cursor = "se-resize"; + resize_handle.title = "Drag to resize the puzzle. Right-click to restore the default size."; + var resize_xbase = null, resize_ybase = null, restore_pending = false; + var resize_xoffset = null, resize_yoffset = null; + var resize_puzzle = Module.cwrap('resize_puzzle', + 'void', ['number', 'number']); + var restore_puzzle_size = Module.cwrap('restore_puzzle_size', 'void', []); + resize_handle.oncontextmenu = function(event) { return false; } + resize_handle.onmousedown = function(event) { + if (event.button == 0) { + var xy = element_coords(onscreen_canvas); + resize_xbase = xy.x + onscreen_canvas.width / 2; + resize_ybase = xy.y; + resize_xoffset = xy.x + onscreen_canvas.width - event.pageX; + resize_yoffset = xy.y + onscreen_canvas.height - event.pageY; + } else { + restore_pending = true; + } + resize_handle.setCapture(true); + event.preventDefault(); + }; + window.addEventListener("mousemove", function(event) { + if (resize_xbase !== null && resize_ybase !== null) { + resize_puzzle((event.pageX + resize_xoffset - resize_xbase) * 2, + (event.pageY + resize_yoffset - resize_ybase)); + event.preventDefault(); + // Chrome insists on selecting text during a resize drag + // no matter what I do + if (window.getSelection) + window.getSelection().removeAllRanges(); + else + document.selection.empty(); } + }); + window.addEventListener("mouseup", function(event) { + if (resize_xbase !== null && resize_ybase !== null) { + resize_xbase = null; + resize_ybase = null; + onscreen_canvas.focus(); // return focus to the puzzle + event.preventDefault(); + } else if (restore_pending) { + // If you have the puzzle at larger than normal size and + // then right-click to restore, I haven't found any way to + // stop Chrome and IE popping up a context menu on the + // revealed piece of document when you release the button + // except by putting the actual restore into a setTimeout. + // Gah. + setTimeout(function() { + restore_pending = false; + restore_puzzle_size(); + onscreen_canvas.focus(); + }, 20); + event.preventDefault(); + } + }); + + // Run the C setup function, passing argv[1] as the fragment + // identifier (so that permalinks of the form puzzle.html#game-id + // can launch the specified id). + Module.callMain([location.hash]); + + // And if we get here with everything having gone smoothly, i.e. + // we haven't crashed for one reason or another during setup, then + // it's probably safe to hide the 'sorry, no puzzle here' div and + // show the div containing the actual puzzle. + document.getElementById("apology").style.display = "none"; + document.getElementById("puzzle").style.display = "inline"; +} |