diff options
author | Franklin Wei <frankhwei536@gmail.com> | 2016-11-20 15:16:41 -0500 |
---|---|---|
committer | Franklin Wei <me@fwei.tk> | 2016-12-18 18:13:22 +0100 |
commit | 1a6a8b52f7aa4e2da6f4c34a0c743c760b8cfd99 (patch) | |
tree | 8e7f2d6b0cbdb5d15c13457b2c3e1de69f598440 /apps/plugins/puzzles/emccpre.js | |
parent | 3ee79724f6fb033d50e26ef37b33d3f8cedf0c5b (diff) | |
download | rockbox-1a6a8b52f7aa4e2da6f4c34a0c743c760b8cfd99.tar.gz rockbox-1a6a8b52f7aa4e2da6f4c34a0c743c760b8cfd99.zip |
Port of Simon Tatham's Puzzle Collection
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.
Currently builds one plugin per puzzle (about 40 in total, around 100K
each on ARM), but can easily be made to build a single monolithic
overlay (800K or so on ARM).
The following games are at least partially broken for various reasons,
and have been disabled on this commit:
Cube: failed assertion with "Icosahedron" setting
Keen: input issues
Mines: weird stuff happens on target
Palisade: input issues
Solo: input issues, occasional crash on target
Towers: input issues
Undead: input issues
Unequal: input and drawing issues (concave polys)
Untangle: input issues
Features left to do:
- In-game help system
- Figure out the weird bugs
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 0000000000..ebf67d1fc6 --- /dev/null +++ b/apps/plugins/puzzles/emccpre.js | |||
@@ -0,0 +1,364 @@ | |||
1 | /* | ||
2 | * emccpre.js: one of the Javascript components of an Emscripten-based | ||
3 | * web/Javascript front end for Puzzles. | ||
4 | * | ||
5 | * The other parts of this system live in emcc.c and emcclib.js. It | ||
6 | * also depends on being run in the context of a web page containing | ||
7 | * an appropriate collection of bits and pieces (a canvas, some | ||
8 | * buttons and links etc), which is generated for each puzzle by the | ||
9 | * script html/jspage.pl. | ||
10 | * | ||
11 | * This file contains the Javascript code which is prefixed unmodified | ||
12 | * to Emscripten's output via the --pre-js option. It declares all our | ||
13 | * global variables, and provides the puzzle init function and a | ||
14 | * couple of other helper functions. | ||
15 | */ | ||
16 | |||
17 | // To avoid flicker while doing complicated drawing, we use two | ||
18 | // canvases, the same size. One is actually on the web page, and the | ||
19 | // other is off-screen. We do all our drawing on the off-screen one | ||
20 | // first, and then copy rectangles of it to the on-screen canvas in | ||
21 | // response to draw_update() calls by the game backend. | ||
22 | var onscreen_canvas, offscreen_canvas; | ||
23 | |||
24 | // A persistent drawing context for the offscreen canvas, to save | ||
25 | // constructing one per individual graphics operation. | ||
26 | var ctx; | ||
27 | |||
28 | // Bounding rectangle for the copy to the onscreen canvas that will be | ||
29 | // done at drawing end time. Updated by js_canvas_draw_update and used | ||
30 | // by js_canvas_end_draw. | ||
31 | var update_xmin, update_xmax, update_ymin, update_ymax; | ||
32 | |||
33 | // Module object for Emscripten. We fill in these parameters to ensure | ||
34 | // that Module.run() won't be called until we're ready (we want to do | ||
35 | // our own init stuff first), and that when main() returns nothing | ||
36 | // will get cleaned up so we remain able to call the puzzle's various | ||
37 | // callbacks. | ||
38 | var Module = { | ||
39 | 'noInitialRun': true, | ||
40 | 'noExitRuntime': true | ||
41 | }; | ||
42 | |||
43 | // Variables used by js_canvas_find_font_midpoint(). | ||
44 | var midpoint_test_str = "ABCDEFGHIKLMNOPRSTUVWXYZ0123456789"; | ||
45 | var midpoint_cache = []; | ||
46 | |||
47 | // Variables used by js_activate_timer() and js_deactivate_timer(). | ||
48 | var timer = null; | ||
49 | var timer_reference_date; | ||
50 | |||
51 | // void timer_callback(double tplus); | ||
52 | // | ||
53 | // Called every 20ms while timing is active. | ||
54 | var timer_callback; | ||
55 | |||
56 | // The status bar object, if we create one. | ||
57 | var statusbar = null; | ||
58 | |||
59 | // Currently live blitters. We keep an integer id for each one on the | ||
60 | // JS side; the C side, which expects a blitter to look like a struct, | ||
61 | // simply defines the struct to contain that integer id. | ||
62 | var blittercount = 0; | ||
63 | var blitters = []; | ||
64 | |||
65 | // State for the dialog-box mechanism. dlg_dimmer and dlg_form are the | ||
66 | // page-darkening overlay and the actual dialog box respectively; | ||
67 | // dlg_next_id is used to allocate each checkbox a unique id to use | ||
68 | // for linking its label to it (see js_dialog_boolean); | ||
69 | // dlg_return_funcs is a list of JS functions to be called when the OK | ||
70 | // button is pressed, to pass the results back to C. | ||
71 | var dlg_dimmer = null, dlg_form = null; | ||
72 | var dlg_next_id = 0; | ||
73 | var dlg_return_funcs = null; | ||
74 | |||
75 | // void dlg_return_sval(int index, const char *val); | ||
76 | // void dlg_return_ival(int index, int val); | ||
77 | // | ||
78 | // C-side entry points called by functions in dlg_return_funcs, to | ||
79 | // pass back the final value in each dialog control. | ||
80 | var dlg_return_sval, dlg_return_ival; | ||
81 | |||
82 | // The <select> object implementing the game-type drop-down, and a | ||
83 | // list of the <option> objects inside it. Used by js_add_preset(), | ||
84 | // js_get_selected_preset() and js_select_preset(). | ||
85 | // | ||
86 | // gametypethiscustom is an option which indicates some custom game | ||
87 | // params you've already set up, and which will be auto-selected on | ||
88 | // return from the customisation dialog; gametypenewcustom is an | ||
89 | // option which you select to indicate that you want to bring up the | ||
90 | // customisation dialog and select a new configuration. Ideally I'd do | ||
91 | // this with just one option serving both purposes, but instead we | ||
92 | // have to do this a bit oddly because browsers don't send 'onchange' | ||
93 | // events for a select element if you reselect the same one - so if | ||
94 | // you've picked a custom setup and now want to change it, you need a | ||
95 | // way to specify that. | ||
96 | var gametypeselector = null, gametypeoptions = []; | ||
97 | var gametypethiscustom = null, gametypehiddencustom = null; | ||
98 | |||
99 | // The two anchors used to give permalinks to the current puzzle. Used | ||
100 | // by js_update_permalinks(). | ||
101 | var permalink_seed, permalink_desc; | ||
102 | |||
103 | // The undo and redo buttons. Used by js_enable_undo_redo(). | ||
104 | var undo_button, redo_button; | ||
105 | |||
106 | // A div element enclosing both the puzzle and its status bar, used | ||
107 | // for positioning the resize handle. | ||
108 | var resizable_div; | ||
109 | |||
110 | // Helper function to find the absolute position of a given DOM | ||
111 | // element on a page, by iterating upwards through the DOM finding | ||
112 | // each element's offset from its parent, and thus calculating the | ||
113 | // page-relative position of the target element. | ||
114 | function element_coords(element) { | ||
115 | var ex = 0, ey = 0; | ||
116 | while (element.offsetParent) { | ||
117 | ex += element.offsetLeft; | ||
118 | ey += element.offsetTop; | ||
119 | element = element.offsetParent; | ||
120 | } | ||
121 | return {x: ex, y:ey}; | ||
122 | } | ||
123 | |||
124 | // Helper function which is passed a mouse event object and a DOM | ||
125 | // element, and returns the coordinates of the mouse event relative to | ||
126 | // the top left corner of the element by subtracting element_coords | ||
127 | // from event.page{X,Y}. | ||
128 | function relative_mouse_coords(event, element) { | ||
129 | var ecoords = element_coords(element); | ||
130 | return {x: event.pageX - ecoords.x, | ||
131 | y: event.pageY - ecoords.y}; | ||
132 | } | ||
133 | |||
134 | // Init function called from body.onload. | ||
135 | function initPuzzle() { | ||
136 | // Construct the off-screen canvas used for double buffering. | ||
137 | onscreen_canvas = document.getElementById("puzzlecanvas"); | ||
138 | offscreen_canvas = document.createElement("canvas"); | ||
139 | offscreen_canvas.width = onscreen_canvas.width; | ||
140 | offscreen_canvas.height = onscreen_canvas.height; | ||
141 | |||
142 | // Stop right-clicks on the puzzle from popping up a context menu. | ||
143 | // We need those right-clicks! | ||
144 | onscreen_canvas.oncontextmenu = function(event) { return false; } | ||
145 | |||
146 | // Set up mouse handlers. We do a bit of tracking of the currently | ||
147 | // pressed mouse buttons, to avoid sending mousemoves with no | ||
148 | // button down (our puzzles don't want those events). | ||
149 | mousedown = Module.cwrap('mousedown', 'void', | ||
150 | ['number', 'number', 'number']); | ||
151 | buttons_down = 0; | ||
152 | onscreen_canvas.onmousedown = function(event) { | ||
153 | var xy = relative_mouse_coords(event, onscreen_canvas); | ||
154 | mousedown(xy.x, xy.y, event.button); | ||
155 | buttons_down |= 1 << event.button; | ||
156 | onscreen_canvas.setCapture(true); | ||
157 | }; | ||
158 | mousemove = Module.cwrap('mousemove', 'void', | ||
159 | ['number', 'number', 'number']); | ||
160 | onscreen_canvas.onmousemove = function(event) { | ||
161 | if (buttons_down) { | ||
162 | var xy = relative_mouse_coords(event, onscreen_canvas); | ||
163 | mousemove(xy.x, xy.y, buttons_down); | ||
164 | } | ||
165 | }; | ||
166 | mouseup = Module.cwrap('mouseup', 'void', | ||
167 | ['number', 'number', 'number']); | ||
168 | onscreen_canvas.onmouseup = function(event) { | ||
169 | if (buttons_down & (1 << event.button)) { | ||
170 | buttons_down ^= 1 << event.button; | ||
171 | var xy = relative_mouse_coords(event, onscreen_canvas); | ||
172 | mouseup(xy.x, xy.y, event.button); | ||
173 | } | ||
174 | }; | ||
175 | |||
176 | // Set up keyboard handlers. We do all the actual keyboard | ||
177 | // handling in onkeydown; but we also call event.preventDefault() | ||
178 | // in both the keydown and keypress handlers. This means that | ||
179 | // while the canvas itself has focus, _all_ keypresses go only to | ||
180 | // the puzzle - so users of this puzzle collection in other media | ||
181 | // can indulge their instinct to press ^R for redo, for example, | ||
182 | // without accidentally reloading the page. | ||
183 | key = Module.cwrap('key', 'void', ['number', 'number', 'string', | ||
184 | 'string', 'number', 'number']); | ||
185 | onscreen_canvas.onkeydown = function(event) { | ||
186 | key(event.keyCode, event.charCode, event.key, event.char, | ||
187 | event.shiftKey ? 1 : 0, event.ctrlKey ? 1 : 0); | ||
188 | event.preventDefault(); | ||
189 | }; | ||
190 | onscreen_canvas.onkeypress = function(event) { | ||
191 | event.preventDefault(); | ||
192 | }; | ||
193 | |||
194 | // command() is a C function called to pass back events which | ||
195 | // don't fall into other categories like mouse and key events. | ||
196 | // Mostly those are button presses, but there's also one for the | ||
197 | // game-type dropdown having been changed. | ||
198 | command = Module.cwrap('command', 'void', ['number']); | ||
199 | |||
200 | // Event handlers for buttons and things, which call command(). | ||
201 | document.getElementById("specific").onclick = function(event) { | ||
202 | // Ensure we don't accidentally process these events when a | ||
203 | // dialog is actually active, e.g. because the button still | ||
204 | // has keyboard focus | ||
205 | if (dlg_dimmer === null) | ||
206 | command(0); | ||
207 | }; | ||
208 | document.getElementById("random").onclick = function(event) { | ||
209 | if (dlg_dimmer === null) | ||
210 | command(1); | ||
211 | }; | ||
212 | document.getElementById("new").onclick = function(event) { | ||
213 | if (dlg_dimmer === null) | ||
214 | command(5); | ||
215 | }; | ||
216 | document.getElementById("restart").onclick = function(event) { | ||
217 | if (dlg_dimmer === null) | ||
218 | command(6); | ||
219 | }; | ||
220 | undo_button = document.getElementById("undo"); | ||
221 | undo_button.onclick = function(event) { | ||
222 | if (dlg_dimmer === null) | ||
223 | command(7); | ||
224 | }; | ||
225 | redo_button = document.getElementById("redo"); | ||
226 | redo_button.onclick = function(event) { | ||
227 | if (dlg_dimmer === null) | ||
228 | command(8); | ||
229 | }; | ||
230 | document.getElementById("solve").onclick = function(event) { | ||
231 | if (dlg_dimmer === null) | ||
232 | command(9); | ||
233 | }; | ||
234 | |||
235 | gametypeselector = document.getElementById("gametype"); | ||
236 | gametypeselector.onchange = function(event) { | ||
237 | if (dlg_dimmer === null) | ||
238 | command(2); | ||
239 | }; | ||
240 | |||
241 | // In IE, the canvas doesn't automatically gain focus on a mouse | ||
242 | // click, so make sure it does | ||
243 | onscreen_canvas.addEventListener("mousedown", function(event) { | ||
244 | onscreen_canvas.focus(); | ||
245 | }); | ||
246 | |||
247 | // In our dialog boxes, Return and Escape should be like pressing | ||
248 | // OK and Cancel respectively | ||
249 | document.addEventListener("keydown", function(event) { | ||
250 | |||
251 | if (dlg_dimmer !== null && event.keyCode == 13) { | ||
252 | for (var i in dlg_return_funcs) | ||
253 | dlg_return_funcs[i](); | ||
254 | command(3); | ||
255 | } | ||
256 | |||
257 | if (dlg_dimmer !== null && event.keyCode == 27) | ||
258 | command(4); | ||
259 | }); | ||
260 | |||
261 | // Set up the function pointers we haven't already grabbed. | ||
262 | dlg_return_sval = Module.cwrap('dlg_return_sval', 'void', | ||
263 | ['number','string']); | ||
264 | dlg_return_ival = Module.cwrap('dlg_return_ival', 'void', | ||
265 | ['number','number']); | ||
266 | timer_callback = Module.cwrap('timer_callback', 'void', ['number']); | ||
267 | |||
268 | // Save references to the two permalinks. | ||
269 | permalink_desc = document.getElementById("permalink-desc"); | ||
270 | permalink_seed = document.getElementById("permalink-seed"); | ||
271 | |||
272 | // Default to giving keyboard focus to the puzzle. | ||
273 | onscreen_canvas.focus(); | ||
274 | |||
275 | // Create the resize handle. | ||
276 | var resize_handle = document.createElement("canvas"); | ||
277 | resize_handle.width = 10; | ||
278 | resize_handle.height = 10; | ||
279 | { | ||
280 | var ctx = resize_handle.getContext("2d"); | ||
281 | ctx.beginPath(); | ||
282 | for (var i = 1; i <= 7; i += 3) { | ||
283 | ctx.moveTo(8.5, i + 0.5); | ||
284 | ctx.lineTo(i + 0.5, 8.5); | ||
285 | } | ||
286 | ctx.lineWidth = '1px'; | ||
287 | ctx.lineCap = 'round'; | ||
288 | ctx.lineJoin = 'round'; | ||
289 | ctx.strokeStyle = '#000000'; | ||
290 | ctx.stroke(); | ||
291 | } | ||
292 | resizable_div = document.getElementById("resizable"); | ||
293 | resizable_div.appendChild(resize_handle); | ||
294 | resize_handle.style.position = 'absolute'; | ||
295 | resize_handle.style.zIndex = 98; | ||
296 | resize_handle.style.bottom = "0"; | ||
297 | resize_handle.style.right = "0"; | ||
298 | resize_handle.style.cursor = "se-resize"; | ||
299 | resize_handle.title = "Drag to resize the puzzle. Right-click to restore the default size."; | ||
300 | var resize_xbase = null, resize_ybase = null, restore_pending = false; | ||
301 | var resize_xoffset = null, resize_yoffset = null; | ||
302 | var resize_puzzle = Module.cwrap('resize_puzzle', | ||
303 | 'void', ['number', 'number']); | ||
304 | var restore_puzzle_size = Module.cwrap('restore_puzzle_size', 'void', []); | ||
305 | resize_handle.oncontextmenu = function(event) { return false; } | ||
306 | resize_handle.onmousedown = function(event) { | ||
307 | if (event.button == 0) { | ||
308 | var xy = element_coords(onscreen_canvas); | ||
309 | resize_xbase = xy.x + onscreen_canvas.width / 2; | ||
310 | resize_ybase = xy.y; | ||
311 | resize_xoffset = xy.x + onscreen_canvas.width - event.pageX; | ||
312 | resize_yoffset = xy.y + onscreen_canvas.height - event.pageY; | ||
313 | } else { | ||
314 | restore_pending = true; | ||
315 | } | ||
316 | resize_handle.setCapture(true); | ||
317 | event.preventDefault(); | ||
318 | }; | ||
319 | window.addEventListener("mousemove", function(event) { | ||
320 | if (resize_xbase !== null && resize_ybase !== null) { | ||
321 | resize_puzzle((event.pageX + resize_xoffset - resize_xbase) * 2, | ||
322 | (event.pageY + resize_yoffset - resize_ybase)); | ||
323 | event.preventDefault(); | ||
324 | // Chrome insists on selecting text during a resize drag | ||
325 | // no matter what I do | ||
326 | if (window.getSelection) | ||
327 | window.getSelection().removeAllRanges(); | ||
328 | else | ||
329 | document.selection.empty(); } | ||
330 | }); | ||
331 | window.addEventListener("mouseup", function(event) { | ||
332 | if (resize_xbase !== null && resize_ybase !== null) { | ||
333 | resize_xbase = null; | ||
334 | resize_ybase = null; | ||
335 | onscreen_canvas.focus(); // return focus to the puzzle | ||
336 | event.preventDefault(); | ||
337 | } else if (restore_pending) { | ||
338 | // If you have the puzzle at larger than normal size and | ||
339 | // then right-click to restore, I haven't found any way to | ||
340 | // stop Chrome and IE popping up a context menu on the | ||
341 | // revealed piece of document when you release the button | ||
342 | // except by putting the actual restore into a setTimeout. | ||
343 | // Gah. | ||
344 | setTimeout(function() { | ||
345 | restore_pending = false; | ||
346 | restore_puzzle_size(); | ||
347 | onscreen_canvas.focus(); | ||
348 | }, 20); | ||
349 | event.preventDefault(); | ||
350 | } | ||
351 | }); | ||
352 | |||
353 | // Run the C setup function, passing argv[1] as the fragment | ||
354 | // identifier (so that permalinks of the form puzzle.html#game-id | ||
355 | // can launch the specified id). | ||
356 | Module.callMain([location.hash]); | ||
357 | |||
358 | // And if we get here with everything having gone smoothly, i.e. | ||
359 | // we haven't crashed for one reason or another during setup, then | ||
360 | // it's probably safe to hide the 'sorry, no puzzle here' div and | ||
361 | // show the div containing the actual puzzle. | ||
362 | document.getElementById("apology").style.display = "none"; | ||
363 | document.getElementById("puzzle").style.display = "inline"; | ||
364 | } | ||