games/wick-dodge/index.html (view raw)
1<html><head>
2<title>PICO-8 Cartridge</title>
3<meta name="viewport" content="width=device-width, user-scalable=no">
4<script type="text/javascript">
5
6 // Default shell for PICO-8 0.2.6 (includes @weeble's gamepad mod 1.0)
7
8 // options
9
10 // fullscreen, sound, close button at top when playing on touchscreen
11 var p8_allow_mobile_menu = true;
12
13 // p8_autoplay true to boot the cartridge automatically after page load when possible
14 // if the browser can not create an audio context outside of a user gesture (e.g. on iOS), p8_autoplay has no effect
15 var p8_autoplay = false;
16
17 // When pico8_state is defined, PICO-8 will set .is_paused, .sound_volume and .frame_number each frame
18 // (used for determining button icons)
19 var pico8_state = [];
20
21 // When pico8_buttons is defined, PICO-8 reads each int as a bitfield holding that player's button states
22 // 0x1 left, 0x2 right, 0x4 up, 0x8 right, 0x10 O, 0x20 X, 0x40 menu
23 // (used by p8_update_gamepads)
24 var pico8_buttons = [0, 0, 0, 0, 0, 0, 0, 0]; // max 8 players
25
26 // When pico8_mouse is defined, PICO-8 reads the 3 integers as X, Y and a bitfield for buttons: 0x1 LMB, 0x2 RMB
27 var pico8_mouse = [];
28
29 // used to display number of detected joysticks
30 var pico8_gamepads = {};
31 pico8_gamepads.count = 0;
32
33 // When pico8_gpio is defined, reading and writing to gpio pins will read and write to these values
34 var pico8_gpio = new Array(128);
35
36 // When pico8_audio_context context is defined, the html shell (this file) is responsible for creating and managing it.
37 // This makes satisfying browser requirements easier -- e.g. initialising audio from a short script in response to a user action.
38 // Otherwise PICO-8 will try to create and use its own context.
39
40 var pico8_audio_context;
41
42
43 // menu button and controller graphics
44 p8_gfx_dat={
45 "p8b_pause1": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAOUlEQVRIx2NgGPbg/8cX/0F46FtAM4vobgHVLRowC6hm0YBbQLFFoxaM4FQ0dHPy0C1Nh26NNugBAAnizNiMfvbGAAAAAElFTkSuQmCC",
46"p8b_controls":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAQ0lEQVRIx2NgGAXEgP8fX/ynBaap4XBLhqcF1IyfYWQBrZLz0LEAlzqqxQFVLcAmT3MLqJqTaW7B4CqLaF4fjIIBBwBL/B2vqtPVIwAAAABJRU5ErkJggg==",
47"p8b_full":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAN0lEQVRIx2NgGPLg/8cX/2mJ6WcBrUJm4CwgOSgGrQVEB8WoBaMWDGMLhm5OHnql6dCt0YY8AAA9oZm+9Z9xQAAAAABJRU5ErkJggg==",
48"p8b_pause0":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAKUlEQVRIx2NgGHbg/8cX/7FhctWNWjBqwagFoxaMWjBqwagF5Fkw5AAAPaGZvsIUtXUAAAAASUVORK5CYII=",
49"p8b_sound0":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAANklEQVRIx2NgGDHg/8cX/5Hx0LEA3cChYwEugwavBcRG4qgFoxYMZwuGfk4efqXp8KnRBj0AAMz7cLDnG4FeAAAAAElFTkSuQmCC",
50"p8b_sound1":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAPUlEQVRIx2NgGDHg/8cX/5Hx0LEA3cChYwEugwhZQLQDqG4BsZFIKMhGLRi1YChbMPRz8vArTYdPjTboAQCSVgpXUWQAMAAAAABJRU5ErkJggg==",
51"p8b_close":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAU0lEQVRIx2NkoDFgpJsF/z+++I8iwS9BkuW49A+cBcRaREgf/Swg1SJi1dHfAkIG4EyOOIJy4Cwg1iJCiWDUAvItGLqpaOjm5KFfmg79Gm3ItioAl+mAGVYIZUUAAAAASUVORK5CYII=",
52
53"controls_left_panel":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAEI0lEQVR42u3dMU7DQBCG0Tjam9DTcP8jpEmfswS5iHBhAsLxev/hvQY6pGXyZRTQ+nQCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHqbHEEtl+vt7hS+fLy/mXHBQqxEi/6aI/AiFW9SnB2BWDkDBAtAsADBAhAsAMECBAtAsAAECxAsAMECECxAsAAEC0CwONJ8tYvrXRAsImK19j0IFsPGSrQQLCJiNV+et7xAT7QQLIaN1dr3ooVgMWysRAvBIipWooVgERUr0UKwiIqVaCFYRMVKtBAsomIlWggWUbESLQSLqFiJFoJFVKxEC8EiKlaihWARFSvRQrDYJSSVfhaCBSBYAIIFCBbAHpoj4Bl/scOGBWDD4lX8iwE2LADBAgQLQLAABAsQLADBAhAsQLAABAtAsADBAhAsAMECBAtAsAAECxAsAMECECxAsAAECxAsAMECECxAsMh1ud7uTsHZVDcZyFo8Yt5sVJ6NyUAaSNEyIymaXwZepIKd4mwoQbAFC0CwAMECECwAwQIEC0CwAAQLECwAwQIQLECwAAQLQLAAwQI4UHME2/10QZq7usyBObBhRQwpmBUb1nADuPbuaUD/p2ezMH+1admwhosVfBcxb2SCJVaIlmAhVoiWYIkVoiVagiVWiJZgiZVYIVqCJVaIlmgJllghWoIlViBagiVWiJZoCZZYIVqCJVYgWoIlViBaggUIlnc0sPELlmghVmIlWKKFWAmWaIFYCZZoIVYIlmghVoIlWiBWgiVaiJVgIVqIlWCJFoiVYIkWYiVYiBZiJViihViJ1XbNEWyL1mMQRYvfvIGJlQ1rmE0LzIoNyyBiDrBhAYIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUgWIBgDc+Nn1D/tdH8YupwgZy5qG4ykKIlVmZDsDjshSlazqQqH7p793Q2CBaAYAGCBSBYAIIFCBaAYAEIFiBYAIIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUgWIBgAQgWwENzBKxZPub9CJ7WjA0LsGFRV+9N5+jNDhsWgGABggUgWACCxW56fgjuA3cEiz9Z/nWwR0iWP8P/YCFYDBstsUKwiIiWWCFYRERLrBAsIqIlVggWEdESKwSLiGiJFYJFRLTECsEiIlpihWARES2xQrCIiJZYIVhEREusECwioiVWCBYx0RIrBIuoaIkVr+YhFHTZtMCGBQgWgGABCBYgWACCBSBYgGABCBaAYAGCBSBYAIIFCBbj2uOR8s6AEbhexgsWYri3SKhKczcXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA2n+e0UMDzh3yTAAAAAElFTkSuQmCC",
54
55
56"controls_right_panel":"data:image/png;base64,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",
57
58 };
59
60
61 // added 0.2.1: work-around for iOS/Safari running from an iFrame (e.g. from itch.io page):
62 // touch events only register after adding dummy listeners on document.
63
64 document.addEventListener('touchstart', {});
65 document.addEventListener('touchmove', {});
66 document.addEventListener('touchend', {});
67
68
69 // --------------------------------------------------------------------------------------------------------------------------------
70 // pico-8 0.2.2: allow dropping files
71 var p8_dropped_cart = null;
72 var p8_dropped_cart_name = "";
73 function p8_drop_file(e)
74 {
75 // console.log("@@ dropping file...");
76
77 e.stopPropagation();
78 e.preventDefault();
79
80 if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0])
81 {
82 // read from file
83 reader = new FileReader();
84 reader.onload = function (event)
85 {
86 p8_dropped_cart_name = 'untitled.p8';
87 if (typeof e.dataTransfer.files[0].name !== 'undefined') p8_dropped_cart_name = e.dataTransfer.files[0].name;
88 if (typeof e.dataTransfer.files[0].fileName !== 'undefined') p8_dropped_cart_name = e.dataTransfer.files[0].fileName;
89 p8_dropped_cart = reader.result;
90 // data:image/png;base64
91 e.stopPropagation();
92 e.preventDefault();
93 codo_command = 9; // read directly from p8_dropped_cart with libb64 decoder
94 };
95 reader.readAsDataURL(e.dataTransfer.files[0]);
96
97 }
98 else
99 {
100 // read from url (or data url)
101 txt = e.dataTransfer.getData('Text');
102 if (txt){
103 p8_dropped_cart_name = "untitled.p8.png";
104 p8_dropped_cart = txt;
105 codo_command = 9;
106 }
107 }
108 }
109 function nop(evt) {
110 evt.stopPropagation();
111 evt.preventDefault();
112 }
113 function dragover(evt) {
114 evt.stopPropagation();
115 evt.preventDefault();
116 Module.pico8DragOver();
117 }
118 function dragstop(evt) {
119 evt.stopPropagation();
120 evt.preventDefault();
121 Module.pico8DragStop();
122 }
123
124 // download (pico-8 0.2.4d web exports can save a .wav file)
125 function download_browser_file(filename, contents)
126 {
127 var element = document.createElement('a');
128 if (filename.substr(filename.length - 7) == ".p8.png")
129 element.setAttribute('href', 'data:image/png;base64,' + encodeURIComponent(contents));
130 else if (filename.substr(filename.length - 4) == ".wav")
131 element.setAttribute('href', 'data:audio/x-wav;base64,' + encodeURIComponent(contents));
132 else
133 element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(contents));
134 element.setAttribute('download', filename);
135 element.style.display = 'none';
136 document.body.appendChild(element);
137 element.click();
138 document.body.removeChild(element);
139 }
140 // --------------------------------------------------------------------------------------------------------------------------------
141
142
143 var p8_buttons_hash = -1;
144 function p8_update_button_icons()
145 {
146 // buttons only appear when running
147 if (!p8_is_running)
148 {
149 requestAnimationFrame(p8_update_button_icons);
150 return;
151 }
152 var is_fullscreen=(document.fullscreenElement || document.mozFullScreenElement || document.webkitIsFullScreen || document.msFullscreenElement);
153
154 // hash based on: pico8_state.sound_volume pico8_state.is_paused bottom_margin left is_fullscreen p8_touch_detected
155 var hash = 0;
156 hash = pico8_state.sound_volume;
157 if (pico8_state.is_paused) hash += 0x100;
158 if (p8_touch_detected) hash += 0x200;
159 if (is_fullscreen) hash += 0x400;
160
161 if (p8_buttons_hash == hash)
162 {
163 requestAnimationFrame(p8_update_button_icons);
164 return;
165 }
166
167 p8_buttons_hash = hash;
168 // console.log("@@ updating button icons");
169
170 els = document.getElementsByClassName('p8_menu_button');
171 for (i = 0; i < els.length; i++)
172 {
173 el = els[i];
174 index = el.id;
175 if (index == 'p8b_sound') index += (pico8_state.sound_volume == 0 ? "0" : "1"); // 1 if undefined
176 if (index == 'p8b_pause') index += (pico8_state.is_paused > 0 ? "1" : "0"); // 0 if undefined
177
178 new_str = '<img width=24 height=24 style="pointer-events:none" src="'+p8_gfx_dat[index]+'">';
179 if (el.innerHTML != new_str)
180 el.innerHTML = new_str;
181
182
183
184
185 // hide all buttons for touch mode (can pause with menu buttons)
186
187 var is_visible = p8_is_running;
188
189 if ((!p8_touch_detected || !p8_allow_mobile_menu) && el.parentElement.id == "p8_menu_buttons_touch")
190 is_visible = false;
191
192 if (p8_touch_detected && el.parentElement.id == "p8_menu_buttons")
193 is_visible = false;
194
195 if (is_fullscreen)
196 is_visible = false;
197
198 if (is_visible)
199 el.style.display="";
200 else
201 el.style.display="none";
202 }
203 requestAnimationFrame(p8_update_button_icons);
204 }
205
206
207
208 function abs(x)
209 {
210 return x < 0 ? -x : x;
211 }
212
213 // step 0 down 1 drag 2 up (not used)
214 function pico8_buttons_event(e, step)
215 {
216 if (!p8_is_running) return;
217
218 pico8_buttons[0] = 0;
219
220 if (step == 2 && typeof(pico8_mouse) !== 'undefined')
221 {
222 pico8_mouse[2] = 0;
223 }
224
225 var num = 0;
226 if (e.touches) num = e.touches.length;
227
228 if (num == 0 && typeof(pico8_mouse) !== 'undefined')
229 {
230 // no active touches: release mouse button from anywhere on page. (maybe redundant? but just in case)
231 pico8_mouse[2] = 0;
232 }
233
234
235 for (var i = 0; i < num; i++)
236 {
237 var touch = e.touches[i];
238 var x = touch.clientX;
239 var y = touch.clientY;
240 var w = window.innerWidth;
241 var h = window.innerHeight;
242
243 var r = Math.min(w,h) / 12;
244 if (r > 40) r = 40;
245
246 // mouse (0.1.12d)
247
248 let canvas = document.getElementById("canvas");
249 if (p8_touch_detected)
250 if (typeof(pico8_mouse) !== 'undefined')
251 if (canvas)
252 {
253 var rect = canvas.getBoundingClientRect();
254 //console.log(rect.top, rect.right, rect.bottom, rect.left, x, y);
255
256 if (x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom)
257 {
258 pico8_mouse = [
259 Math.floor((x - rect.left) * 128 / (rect.right - rect.left)),
260 Math.floor((y - rect.top) * 128 / (rect.bottom - rect.top)),
261 step < 2 ? 1 : 0
262 ];
263 // return; // commented -- blocks overlapping buttons
264 }else
265 {
266 pico8_mouse[2] = 0;
267 }
268 }
269
270
271 // buttons
272
273 b = 0;
274
275 if (y < h - r*8)
276 {
277 // no controller buttons up here; includes canvas and menu buttons at top in touch mode
278 }
279 else
280 {
281 e.preventDefault();
282
283 if ((y < h - r*6) && y > (h - r*8))
284 {
285 // menu button: half as high as X O button
286 // stretch across right-hand half above X O buttons
287 if (x > w - r*3)
288 b |= 0x40;
289 }
290 else if (x < w/2 && x < r*6)
291 {
292 // stick
293
294 mask = 0xf; // dpad
295 var cx = 0 + r*3;
296 var cy = h - r*3;
297
298 deadzone = r/3;
299 var dx = x - cx;
300 var dy = y - cy;
301
302 if (abs(dx) > abs(dy) * 0.6) // horizontal
303 {
304 if (dx < -deadzone) b |= 0x1;
305 if (dx > deadzone) b |= 0x2;
306 }
307 if (abs(dy) > abs(dx) * 0.6) // vertical
308 {
309 if (dy < -deadzone) b |= 0x4;
310 if (dy > deadzone) b |= 0x8;
311 }
312 }
313 else if (x > w - r*6)
314 {
315 // button; diagonal split from bottom right corner
316
317 mask = 0x30;
318
319 // one or both of [X], [O]
320 if ( (h-y) > (w-x) * 0.8) b |= 0x10;
321 if ( (w-x) > (h-y) * 0.8) b |= 0x20;
322 }
323 }
324
325 pico8_buttons[0] |= b;
326
327 }
328 }
329
330 // p8_update_layout_hash is used to decide when to update layout (expensive especially when part of a heavy page)
331 var p8_update_layout_hash = -1;
332 var last_windowed_container_height = 512;
333 var p8_layout_frames = 0;
334
335 function p8_update_layout()
336 {
337 var canvas = document.getElementById("canvas");
338 var p8_playarea = document.getElementById("p8_playarea");
339 var p8_container = document.getElementById("p8_container");
340 var p8_frame = document.getElementById("p8_frame");
341 var csize = 512;
342 var margin_top = 0;
343 var margin_left = 0;
344
345 // page didn't load yet? first call should be after p8_frame is created so that layout doesn't jump around.
346 if (!canvas || !p8_playarea || !p8_container || !p8_frame)
347 {
348 p8_update_layout_hash = -1;
349 requestAnimationFrame(p8_update_layout);
350 return;
351 }
352
353 p8_layout_frames ++;
354
355 // assumes frame doesn't have padding
356
357 var is_fullscreen=(document.fullscreenElement || document.mozFullScreenElement || document.webkitIsFullScreen || document.msFullscreenElement);
358 var frame_width = p8_frame.offsetWidth;
359 var frame_height = p8_frame.offsetHeight;
360
361 if (is_fullscreen)
362 {
363 // same as window
364 frame_width = window.innerWidth;
365 frame_height = window.innerHeight;
366 }
367 else{
368 // never larger than window // (happens when address bar is down in portraight mode on phone)
369 frame_width = Math.min(frame_width, window.innerWidth);
370 frame_height = Math.min(frame_height, window.innerHeight);
371 }
372
373 // as big as will fit in a frame..
374 csize = Math.min(frame_width,frame_height);
375
376 // .. but never more than 2/3 of longest side for touch (e.g. leave space for controls on iPad)
377 if (p8_touch_detected && p8_is_running)
378 {
379 var longest_side = Math.max(window.innerWidth,window.innerHeight);
380 csize = Math.min(csize, longest_side * 2/3);
381 }
382
383 // pixel perfect: quantize to closest multiple of 128
384 // only when large display (desktop)
385 if (frame_width >= 512 && frame_height >= 512)
386 {
387 csize = (csize+1) & ~0x7f;
388 }
389
390 // csize should never be higher than parent frame
391 // (otherwise stretched large when fullscreen and then return)
392 if (!is_fullscreen && p8_frame)
393 csize = Math.min(csize, last_windowed_container_height); // p8_frame_0 parent
394
395
396 if (is_fullscreen)
397 {
398 // always center horizontally
399 margin_left = (frame_width - csize)/2;
400
401 if (p8_touch_detected)
402 {
403 if (window.innerWidth < window.innerHeight)
404 {
405 // portrait: keep at y=40 (avoid rounded top corners / camera nub thing etc.)
406 margin_top = Math.min(40, frame_height - csize);
407 }
408 else
409 {
410 // landscape: put a little above vertical center
411 margin_top = (frame_height - csize)/4;
412 }
413 }
414 else{
415 // non-touch: center vertically
416 margin_top = (frame_height - csize)/2;
417 }
418 }
419
420 // skip if relevant state has not changed
421
422 var update_hash = csize + margin_top * 1000.3 + margin_left * 0.001 + frame_width * 333.33 + frame_height * 772.15134;
423 if (is_fullscreen) update_hash += 0.1237;
424
425 // unexpected things can happen in the first few seconds, so just keep re-calculating layout. wasm version breaks layout otherwise.
426 // also: bonus refresh at 5, 8 seconds just in case ._.
427 if (p8_layout_frames < 180 || p8_layout_frames == 60*5 || p8_layout_frames == 60*8 )
428 update_hash = p8_layout_frames;
429
430 if (!is_fullscreen) // fullscreen: update every frame for safety. should be cheap!
431 if (!p8_touch_detected) // mobile: update every frame because nothing can be trusted
432 if (p8_update_layout_hash == update_hash)
433 {
434 //console.log("p8_update_layout(): skipping");
435 requestAnimationFrame(p8_update_layout);
436 return;
437 }
438 p8_update_layout_hash = update_hash;
439
440 // record this for returning to original size after fullscreen pushes out container height (argh)
441 if (!is_fullscreen && p8_frame)
442 last_windowed_container_height = p8_frame.parentNode.parentNode.offsetHeight;
443
444
445 // mobile in portrait mode: put screen at top (w / a little extra space for fullscreen button if needed)
446 // (don't cart too about buttons overlapping screen)
447 if (p8_touch_detected && p8_is_running && document.body.clientWidth < document.body.clientHeight)
448 p8_playarea.style.marginTop = p8_allow_mobile_menu ? 32 : 8;
449 else if (p8_touch_detected && p8_is_running) // landscape: slightly above vertical center (only relevant for iPad / highres devices)
450 p8_playarea.style.marginTop = (document.body.clientHeight - csize) / 4;
451 else
452 p8_playarea.style.marginTop = "";
453
454 canvas.style.width = csize;
455 canvas.style.height = csize;
456
457 // to do: this should just happen from css layout
458 canvas.style.marginLeft = margin_left;
459 canvas.style.marginTop = margin_top;
460
461 p8_container.style.width = csize;
462 p8_container.style.height = csize;
463
464 // set menu buttons position to bottom right
465 el = document.getElementById("p8_menu_buttons");
466 el.style.marginTop = csize - el.offsetHeight;
467
468 if (p8_touch_detected && p8_is_running)
469 {
470 // turn off pointer events to prevent double-tap zoom etc (works on Android)
471 // don't want this for desktop because breaks mouse input & click-to-focus when using codo_textarea
472 canvas.style.pointerEvents = "none";
473
474 p8_container.style.marginTop = "0px";
475
476 // buttons
477
478 // same as touch event handling
479 var w = window.innerWidth;
480 var h = window.innerHeight;
481 var r = Math.min(w,h) / 12;
482
483 if (r > 40) r = 40;
484
485 el = document.getElementById("controls_right_panel");
486 el.style.left = w-r*6;
487 el.style.top = h-r*7;
488 el.style.width = r*6;
489 el.style.height = r*7;
490 if (el.getAttribute("src") != p8_gfx_dat["controls_right_panel"]) // optimisation: avoid reload? (browser should handle though)
491 el.setAttribute("src", p8_gfx_dat["controls_right_panel"]);
492
493 el = document.getElementById("controls_left_panel");
494 el.style.left = 0;
495 el.style.top = h-r*6;
496 el.style.width = r*6;
497 el.style.height = r*6;
498 if (el.getAttribute("src") != p8_gfx_dat["controls_left_panel"]) // optimisation: avoid reload? (browser should handle though)
499 el.setAttribute("src", p8_gfx_dat["controls_left_panel"]);
500
501 // scroll to cart (commented; was a failed attempt to prevent scroll-on-drag on some browsers)
502 // p8_frame.scrollIntoView(true);
503
504 document.getElementById("touch_controls_gfx").style.display="table";
505 document.getElementById("touch_controls_background").style.display="table";
506
507 }
508 else{
509 document.getElementById("touch_controls_gfx").style.display="none";
510 document.getElementById("touch_controls_background").style.display="none";
511 }
512
513 if (!p8_is_running)
514 {
515 p8_playarea.style.display="none";
516 p8_container.style.display="flex";
517 p8_container.style.marginTop="auto";
518
519 el = document.getElementById("p8_start_button");
520 if (el) el.style.display="flex";
521 }
522 requestAnimationFrame(p8_update_layout);
523 }
524
525
526 var p8_touch_detected = false;
527 addEventListener("touchstart", function(event)
528 {
529 p8_touch_detected = true;
530
531 // hide codo_textarea -- clipboard support on mobile is not feasible
532 el = document.getElementById("codo_textarea");
533 if (el && el.style.display != "none"){
534 el.style.display="none";
535 }
536
537 }, {passive: true});
538
539 function p8_create_audio_context()
540 {
541 if (pico8_audio_context)
542 {
543 try {
544 pico8_audio_context.resume();
545 }
546 catch(err) {
547 console.log("** pico8_audio_context.resume() failed");
548 }
549 return;
550 }
551
552 var webAudioAPI = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.oAudioContext || window.msAudioContext;
553 if (webAudioAPI)
554 {
555 pico8_audio_context = new webAudioAPI;
556
557 // wake up iOS
558 if (pico8_audio_context)
559 {
560 try {
561 var dummy_source_sfx = pico8_audio_context.createBufferSource();
562 dummy_source_sfx.buffer = pico8_audio_context.createBuffer(1, 1, 22050); // dummy
563 dummy_source_sfx.connect(pico8_audio_context.destination);
564 dummy_source_sfx.start(1, 0.25); // gives InvalidStateError -- why? hasn't been played before
565 //dummy_source_sfx.noteOn(0); // deleteme
566 }
567 catch(err) {
568 console.log("** dummy_source_sfx.start(1, 0.25) failed");
569 }
570 }
571 }
572 }
573
574 function p8_close_cart()
575 {
576 // just reload page! used for touch buttons -- hard to roll back state
577 window.location.hash = ""; // triggers reload
578 }
579
580 var p8_is_running = false;
581 var p8_script = null;
582 var Module = null;
583 function p8_run_cart()
584 {
585 if (p8_is_running) return;
586 p8_is_running = true;
587
588 // touch: hide everything except p8_frame_0
589 if (p8_touch_detected)
590 {
591 el = document.getElementById("body_0");
592 el2 = document.getElementById("p8_frame_0");
593 if (el && el2)
594 {
595 el.style.display="none";
596 el.parentNode.appendChild(el2);
597 }
598 }
599
600 // create audio context and wake it up (for iOS -- needs happen inside touch event)
601 p8_create_audio_context();
602
603 // show touch elements
604 els = document.getElementsByClassName('p8_controller_area');
605 for (i = 0; i < els.length; i++)
606 els[i].style.display="";
607
608
609 // install touch events. These also serve to block scrolling / pinching / zooming on phones when p8_is_running
610 // moved event.preventDefault(); calls into pico8_buttons_event() (want to let top buttons pass through)
611 addEventListener("touchstart", function(event){ pico8_buttons_event(event, 0); }, {passive: false});
612 addEventListener("touchmove", function(event){ pico8_buttons_event(event, 1); }, {passive: false});
613 addEventListener("touchend", function(event){ pico8_buttons_event(event, 2); }, {passive: false});
614
615
616 // load and run script
617 e = document.createElement("script");
618 p8_script = e;
619 e.onload = function(){
620
621 // show canvas / menu buttons only after loading
622 el = document.getElementById("p8_playarea");
623 if (el) el.style.display="table";
624
625 if (typeof(p8_update_layout_hash) !== 'undefined')
626 p8_update_layout_hash = -77;
627 if (typeof(p8_buttons_hash) !== 'undefined')
628 p8_buttons_hash = -33;
629
630
631 }
632 e.type = "application/javascript";
633 e.src = "wick-dodge.js";
634 e.id = "e_script";
635
636 document.body.appendChild(e); // load and run
637
638 // hide start button and show canvas / menu buttons. hide start button
639 el = document.getElementById("p8_start_button");
640 if (el) el.style.display="none";
641
642 // add #playing for touchscreen devices (allows back button to close)
643 // X button can also be used to trigger this
644 if (p8_touch_detected)
645 {
646 window.location.hash = "#playing";
647 window.onhashchange = function()
648 {
649 if (window.location.hash.search("playing") < 0)
650 window.location.reload();
651 }
652 }
653
654 // install drag&drop listeners
655 {
656 let canvas = document.getElementById("canvas");
657 if (canvas)
658 {
659 canvas.addEventListener('dragenter', dragover, false);
660 canvas.addEventListener('dragover', dragover, false);
661 canvas.addEventListener('dragleave', dragstop, false);
662 canvas.addEventListener('drop', nop, false);
663 canvas.addEventListener('drop', p8_drop_file, false);
664 }
665 }
666 }
667
668
669 // Gamepad code
670
671 var P8_BUTTON_O = {action:'button', code: 0x10};
672 var P8_BUTTON_X = {action:'button', code: 0x20};
673 var P8_DPAD_LEFT = {action:'button', code: 0x1};
674 var P8_DPAD_RIGHT = {action:'button', code: 0x2};
675 var P8_DPAD_UP = {action:'button', code: 0x4};
676 var P8_DPAD_DOWN = {action:'button', code: 0x8};
677 var P8_MENU = {action:'menu'};
678 var P8_NO_ACTION = {action:'none'};
679
680 var P8_BUTTON_MAPPING = [
681 // ref: https://w3c.github.io/gamepad/#remapping
682 P8_BUTTON_O, // Bottom face button
683 P8_BUTTON_X, // Right face button
684 P8_BUTTON_X, // Left face button
685 P8_BUTTON_O, // Top face button
686 P8_NO_ACTION, // Near left shoulder button (L1)
687 P8_NO_ACTION, // Near right shoulder button (R1)
688 P8_NO_ACTION, // Far left shoulder button (L2)
689 P8_NO_ACTION, // Far right shoulder button (R2)
690 P8_MENU, // Left auxiliary button (select)
691 P8_MENU, // Right auxiliary button (start)
692 P8_NO_ACTION, // Left stick button
693 P8_NO_ACTION, // Right stick button
694 P8_DPAD_UP, // Dpad up
695 P8_DPAD_DOWN, // Dpad down
696 P8_DPAD_LEFT, // Dpad left
697 P8_DPAD_RIGHT, // Dpad right
698 ];
699
700 // Track which player is controller by each gamepad. Gamepad index i controls the
701 // player with index pico8_gamepads_mapping[i]. Gamepads with null player are
702 // currently unassigned - they get assigned to a player when a button is pressed.
703 var pico8_gamepads_mapping = [];
704
705 function p8_unassign_gamepad(gamepad_index) {
706 if (pico8_gamepads_mapping[gamepad_index] == null) {
707 return;
708 }
709 pico8_buttons[pico8_gamepads_mapping[gamepad_index]] = 0;
710 pico8_gamepads_mapping[gamepad_index] = null;
711 }
712
713
714 function p8_first_player_without_gamepad(max_players) {
715 var allocated_players = pico8_gamepads_mapping.filter(function(x) { return x != null; });
716 var sorted_players = Array.from(allocated_players).sort();
717 for (var desired = 0; desired < sorted_players.length && desired < max_players; ++desired) {
718 if (desired != sorted_players[desired]) {
719 return desired;
720 }
721 }
722 if (sorted_players.length < max_players) {
723 return sorted_players.length;
724 }
725 return null;
726 }
727
728 function p8_assign_gamepad_to_player(gamepad_index, player_index) {
729 p8_unassign_gamepad(gamepad_index);
730 pico8_gamepads_mapping[gamepad_index] = player_index;
731 }
732
733
734
735 function p8_convert_standard_gamepad_to_button_state(gamepad, axis_threshold, button_threshold) {
736 // Given a gamepad object, return:
737 // {
738 // button_state: the binary encoded Pico 8 button state
739 // menu_button: true if any menu-mapped button was pressed
740 // any_button: true if any button was pressed, including d-pad
741 // buttons and unmapped buttons
742 // }
743 if (!gamepad || !gamepad.axes || !gamepad.buttons) {
744 return {
745 button_state: 0,
746 menu_button: false,
747 any_button: false
748 };
749 }
750 function button_state_from_axis(axis, low_state, high_state, default_state) {
751 if (axis && axis < -axis_threshold) return low_state;
752 if (axis && axis > axis_threshold) return high_state;
753 return default_state;
754 }
755 var axes_actions = [
756 button_state_from_axis(gamepad.axes[0], P8_DPAD_LEFT, P8_DPAD_RIGHT, P8_NO_ACTION),
757 button_state_from_axis(gamepad.axes[1], P8_DPAD_UP, P8_DPAD_DOWN, P8_NO_ACTION),
758 ];
759
760 var button_actions = gamepad.buttons.map(function (button, index) {
761 var pressed = button.value > button_threshold || button.pressed;
762 if (!pressed) return P8_NO_ACTION;
763 return P8_BUTTON_MAPPING[index] || P8_NO_ACTION;
764 });
765
766 var all_actions = axes_actions.concat(button_actions);
767
768 var menu_button = button_actions.some(function (action) { return action.action == 'menu'; });
769 var button_state = (all_actions
770 .filter(function (a) { return a.action == 'button'; })
771 .map(function (a) { return a.code; })
772 .reduce(function (result, code) { return result | code; }, 0)
773 );
774 var any_button = gamepad.buttons.some(function (button) {
775 return button.value > button_threshold || button.pressed;
776 });
777
778 any_button |= button_state; //jww: include axes 0,1 as might be first intended action
779
780 return {
781 button_state,
782 menu_button,
783 any_button
784 };
785 }
786
787 // jww: pico-8 0.2.1 version for unmapped gamepads, following p8_convert_standard_gamepad_to_button_state
788 // axes 0,1 & buttons 0,1,2,3 are reasonably safe. don't try to read dpad.
789 // menu buttons are unpredictable, but use 6..8 anyway (better to have a weird menu button than none)
790
791 function p8_convert_unmapped_gamepad_to_button_state(gamepad, axis_threshold, button_threshold) {
792
793 if (!gamepad || !gamepad.axes || !gamepad.buttons) {
794 return {
795 button_state: 0,
796 menu_button: false,
797 any_button: false
798 };
799 }
800
801 var button_state = 0;
802
803 if (gamepad.axes[0] && gamepad.axes[0] < -axis_threshold) button_state |= 0x1;
804 if (gamepad.axes[0] && gamepad.axes[0] > axis_threshold) button_state |= 0x2;
805 if (gamepad.axes[1] && gamepad.axes[1] < -axis_threshold) button_state |= 0x4;
806 if (gamepad.axes[1] && gamepad.axes[1] > axis_threshold) button_state |= 0x8;
807
808 // buttons: first 4 taken to be O/X, 6..8 taken to be menu button
809
810 for (j = 0; j < gamepad.buttons.length; j++)
811 if (gamepad.buttons[j].value > 0 || gamepad.buttons[j].pressed)
812 {
813 if (j < 4)
814 button_state |= (0x10 << (((j+1)/2)&1)); // 0 1 1 0 -- A,X -> O,X on xbox360
815 else if (j >= 6 && j <= 8)
816 button_state |= 0x40;
817 }
818
819 var menu_button = button_state & 0x40;
820
821 var any_button = gamepad.buttons.some(function (button) {
822 return button.value > button_threshold || button.pressed;
823 });
824
825 any_button |= button_state; //jww: include axes 0,1 as might be first intended action
826
827 return {
828 button_state,
829 menu_button,
830 any_button
831 };
832 }
833
834
835 // gamepad https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API
836 // (sets bits in pico8_buttons[])
837 function p8_update_gamepads() {
838 var axis_threshold = 0.3;
839 var button_threshold = 0.5; // Should be unnecessary, we should be able to trust .pressed
840 var max_players = 8;
841 var gps = navigator.getGamepads() || navigator.webkitGetGamepads();
842
843 if (!gps) return;
844
845 // In Chrome, gps is iterable but it's not an array.
846 gps = Array.from(gps);
847
848 pico8_gamepads.count = gps.length;
849 while (gps.length > pico8_gamepads_mapping.length) {
850 pico8_gamepads_mapping.push(null);
851 }
852
853 var menu_button = false;
854 var gamepad_states = gps.map(function (gp) {
855 return (gp && gp.mapping == "standard") ?
856 p8_convert_standard_gamepad_to_button_state(gp, axis_threshold, button_threshold) :
857 p8_convert_unmapped_gamepad_to_button_state(gp, axis_threshold, button_threshold);
858 });
859
860 // Unassign disconnected gamepads.
861 // gps.forEach(function (gp, i) { if (gp && !gp.connected) { p8_unassign_gamepad(i); }});
862 gps.forEach(function (gp, i) { if (!gp || !gp.connected) { p8_unassign_gamepad(i); }}); // https://www.lexaloffle.com/bbs/?pid=87132#p
863
864
865 // Assign unassigned gamepads when any button is pressed.
866 gamepad_states.forEach(function (state, i) {
867 if (state.any_button && pico8_gamepads_mapping[i] == null) {
868 var first_free_player = p8_first_player_without_gamepad(max_players);
869 p8_assign_gamepad_to_player(i, first_free_player);
870 }
871 });
872
873 // Update pico8_buttons array.
874 gamepad_states.forEach(function (gamepad_state, i) {
875 if (pico8_gamepads_mapping[i] != null) {
876 pico8_buttons[pico8_gamepads_mapping[i]] = gamepad_state.button_state;
877 }
878 });
879
880 // Update menu button.
881 // Pico 8 only recognises the menu button on the first player, so we
882 // press it when any gamepad has pressed a button mapped to menu.
883 if (gamepad_states.some(function (state) { return state.menu_button; })) {
884 pico8_buttons[0] |= 0x40;
885 }
886
887 requestAnimationFrame(p8_update_gamepads);
888 }
889 requestAnimationFrame(p8_update_gamepads);
890
891 // End of gamepad code
892
893
894 // key blocker. prevent browser operations while playing cart so that PICO-8 can use those keys e.g. cursors to scroll, ctrl-r to reload
895 document.addEventListener('keydown',
896 function (event) {
897 event = event || window.event;
898 if (!p8_is_running) return;
899
900 if (pico8_state.has_focus == 1)
901 if ([32, 37, 38, 39, 40, 77, 82, 80, 9].indexOf(event.keyCode) > -1) // block only cursors, M R P, tab
902 if (event.preventDefault) event.preventDefault();
903
904 },{passive: false});
905
906 // when using codo_textarea to determine focus, need to explicitly hand focus back when clicking a p8_menu_button
907 function p8_give_focus()
908 {
909 el = (typeof codo_textarea === 'undefined') ? document.getElementById("codo_textarea") : codo_textarea;
910 if (el)
911 {
912 el.focus();
913 el.select();
914 }
915 }
916
917 function p8_request_fullscreen() {
918
919 var is_fullscreen=(document.fullscreenElement || document.mozFullScreenElement || document.webkitIsFullScreen || document.msFullscreenElement);
920
921 if (is_fullscreen)
922 {
923 if (document.exitFullscreen) {
924 document.exitFullscreen();
925 } else if (document.webkitExitFullscreen) {
926 document.webkitExitFullscreen();
927 } else if (document.mozCancelFullScreen) {
928 document.mozCancelFullScreen();
929 } else if (document.msExitFullscreen) {
930 document.msExitFullscreen();
931 }
932 return;
933 }
934
935 var el = document.getElementById("p8_playarea");
936
937 if ( el.requestFullscreen ) {
938 el.requestFullscreen();
939 } else if ( el.mozRequestFullScreen ) {
940 el.mozRequestFullScreen();
941 } else if ( el.webkitRequestFullScreen ) {
942 el.webkitRequestFullScreen( Element.ALLOW_KEYBOARD_INPUT );
943 }
944 }
945
946</script>
947
948<STYLE TYPE="text/css">
949<!--
950.p8_menu_button{
951 opacity:0.3;
952 padding:4px;
953 display:table;
954 width:24px;
955 height:24px;
956 float:right;
957}
958
959@media screen and (min-width:512px) {
960 .p8_menu_button{
961 width:24px; margin-left:12px; margin-bottom:8px;
962 }
963}
964.p8_menu_button:hover{
965 opacity:1.0;
966 cursor:pointer;
967}
968
969canvas{
970 image-rendering: optimizeSpeed;
971 image-rendering: -moz-crisp-edges;
972 image-rendering: -webkit-optimize-contrast;
973 image-rendering: optimize-contrast;
974 image-rendering: pixelated;
975 -ms-interpolation-mode: nearest-neighbor;
976 border: 0px;
977 cursor: none;
978}
979
980
981.p8_start_button{
982 cursor:pointer;
983 background:url("data:image/png;base64,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");
984 -repeat center;
985 -webkit-background-size:cover; -moz-background-size:cover; -o-background-size:cover; background-size:cover;
986}
987
988.button_gfx{
989 stroke-width:2;
990 stroke: #ffffff;
991 stroke-opacity:0.4;
992 fill-opacity:0.2;
993 fill:black;
994}
995
996.button_gfx_icon{
997 stroke-width:3;
998 stroke: #909090;
999 stroke-opacity:0.7;
1000 fill:none;
1001}
1002
1003-->
1004</STYLE>
1005
1006</head>
1007
1008<body style="padding:0px; margin:0px; background-color:#222; color:#ccc">
1009<div id="body_0"> <!-- hide this when playing in mobile (p8_touch_detected) so that elements don't affect layout -->
1010
1011
1012<!-- Add any content above the cart here -->
1013
1014
1015<div id="p8_frame_0" style="max-width:800px; max-height:800px; margin:auto;"> <!-- double function: limit size, and display only this div for touch devices -->
1016<div id="p8_frame" style="display:flex; width:100%; max-width:95vw; height:100vw; max-height:95vh; margin:auto;">
1017
1018 <div id="p8_menu_buttons_touch" style="position:absolute; width:100%; z-index:10; left:0px;">
1019 <div class="p8_menu_button" id="p8b_full" style="float:left;margin-left:10px" onClick="p8_give_focus(); p8_request_fullscreen();"></div>
1020 <div class="p8_menu_button" id="p8b_sound" style="float:left;margin-left:10px" onClick="p8_give_focus(); p8_create_audio_context(); Module.pico8ToggleSound();"></div>
1021 <div class="p8_menu_button" id="p8b_close" style="float:right; margin-right:10px" onClick="p8_close_cart();"></div>
1022 </div>
1023
1024 <div id="p8_container"
1025 style="margin:auto; display:table;"
1026 onclick="p8_create_audio_context(); p8_run_cart();">
1027
1028 <div id="p8_start_button" class="p8_start_button" style="width:100%; height:100%; display:flex;">
1029 <img width=80 height=80 style="margin:auto;"
1030 src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAABpklEQVR42u3au23DQBCEYUXOXIGKcujQXUgFuA0XIKgW90Q9oEAg+Ljd27vd2RsCf058gEDqhofPj+OB6SMCAQlIQAIyAhKQgARkBAQDnM6XSRsB7/2e/tSA0//12fCAKsQX3ntDA4oRFwBRIc0AixE38BAhTQGLEAsBUSDNAXcRhYDRIZsAPlp99VECRoXsDpgN0g0wC6Q7IDpkGEBUyG6A0+vKBtkdMBukG2AWSHdAdMgwgKiQ4QDRIMMCokCGB4wOCQPYFVKw2cABNocUjl6wgE0gFashPKAZpHJ2TQNYBVmxW6cDFENWDv9pAUshCVgJScBKSAISkD9hPkT4GkNAMdzepyj8Kye852EBLe51CZHHWQK4JcThD1SlcHPEYY/0a+A0n6SkGZV6w6WZNb3g4Id1b7hwgGhwYQBR4dwB0eHcALPAdQfMBhcOEA0uDCAqnDsgOpwbYBa4poA/31+rZYFrBriFpwGMCtcEcA9PAhgdzhywBK8EEQXOFFCCtwaIBmcGKMWbI6LCmQBq8R6hw5kAMgISkIAEJCAjIAEJSEBGQI9ukV7lRn9nD+gAAAAASUVORK5CYII="/>
1031 </div>
1032
1033 <div id="p8_playarea" style="display:none; margin:auto;
1034 -webkit-user-select:none; -moz-user-select: none; user-select: none; -webkit-touch-callout:none;
1035 ">
1036
1037 <div id="touch_controls_background"
1038 style=" pointer-events:none; display:none; background-color:#000;
1039 position:fixed; top:0px; left:0px; border:0; width:100vw; height:100vh">
1040  
1041 </div>
1042
1043 <div style="display:flex; position:relative">
1044 <!-- pointer-events turned off for mobile in p8_update_layout because need for desktop mouse -->
1045 <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault();" >
1046 </canvas>
1047 <div class=p8_menu_buttons id="p8_menu_buttons" style="margin-left:10px;">
1048 <div class="p8_menu_button" style="position:absolute; bottom:125px" id="p8b_controls" onClick="p8_give_focus(); Module.pico8ToggleControlMenu();"></div>
1049 <div class="p8_menu_button" style="position:absolute; bottom:90px" id="p8b_pause" onClick="p8_give_focus(); Module.pico8TogglePaused(); p8_update_layout_hash = -22;"></div>
1050 <div class="p8_menu_button" style="position:absolute; bottom:55px" id="p8b_sound" onClick="p8_give_focus(); p8_create_audio_context(); Module.pico8ToggleSound();"></div>
1051 <div class="p8_menu_button" style="position:absolute; bottom:20px" id="p8b_full" onClick="p8_give_focus(); p8_request_fullscreen();"></div>
1052 </div>
1053 </div>
1054
1055
1056 <!-- display after first layout update -->
1057 <div id="touch_controls_gfx"
1058 style=" pointer-events:none; display:table;
1059 position:fixed; top:0px; left:0px; border:0; width:100vw; height:100vh">
1060
1061 <img src="" id="controls_right_panel" style="position:absolute; opacity:0.5;">
1062 <img src="" id="controls_left_panel" style="position:absolute; opacity:0.5;">
1063
1064
1065 </div> <!-- touch_controls_gfx -->
1066
1067 <!-- used for clipboard access & keyboard input; displayed and used by PICO-8 only once needed. can be safely removed if clipboard / key presses not needed. -->
1068 <!-- (needs to be inside p8_playarea so that it still works under Chrome when fullscreened) -->
1069 <!-- 0.2.5: added "display:none"; pico8.js shows on demand to avoid mac osx accent character selector // https://www.lexaloffle.com/bbs/?tid=47743 -->
1070
1071 <textarea id="codo_textarea" class="emscripten" style="display:none; position:absolute; left:-9999px; height:0px; overflow:hidden"></textarea>
1072
1073 </div> <!--p8_playarea -->
1074
1075 </div> <!-- p8_container -->
1076
1077</div> <!-- p8_frame -->
1078</div> <!-- p8_frame_0 size limit -->
1079
1080<script type="text/javascript">
1081
1082 p8_update_layout();
1083 p8_update_button_icons();
1084
1085 var canvas = document.getElementById("canvas");
1086 Module = {};
1087 Module.canvas = canvas;
1088
1089 // from @ultrabrite's shell: test if an AudioContext can be created outside of an event callback.
1090 // If it can't be created, then require pressing the start button to run the cartridge
1091
1092 if (p8_autoplay)
1093 {
1094 var temp_context = new AudioContext();
1095 temp_context.onstatechange = function ()
1096 {
1097 if (temp_context.state=='running')
1098 {
1099 p8_run_cart();
1100 temp_context.close();
1101 }
1102 };
1103 }
1104
1105 // pointer lock request needs to be inside a canvas interaction event
1106 // pico8_state.request_pointer_lock is true when 0x5f2d bit 0 and bit 2 are set -- poke(0x5f2d,0x5)
1107 // note on mouse acceleration for future: // https://github.com/w3c/pointerlock/pull/49
1108 canvas.addEventListener("click", function()
1109 {
1110 if (!p8_touch_detected)
1111 if (pico8_state.request_pointer_lock)
1112 canvas.requestPointerLock();
1113 });
1114
1115</script>
1116
1117
1118
1119<!-- Add content below the cart here -->
1120
1121
1122
1123
1124</div> <!-- body_0 -->
1125</body></html>
1126