olivarra1 [Lexaloffle Blog Feed]https://www.lexaloffle.com/bbs/?uid=53064 Input lag on cellphones <p>I'm trying to make a rhthm-based game, and although the timing looks OK both on pico-8 and in my computer after exported in HTML, when running it through my cellphone everything has ~300ms delay, which ruins the experience.</p> <p>I've done a very simple cartridge to test the timing - Try to press X to follow the rhythm. Again, on my desktop computer it's fine, but when running it on a cellphone the timing is definetly off.</p> <p>I've debugged a bit, trying to see if it's some lag added by the browser on cellphones to detect gestures, but the same function that sets the button as on is triggered as soon as it's pressed down. It looks like the delay happens in between the <code>buttons</code> JS variable and pico-8 runtime.</p> <p>Is there an alternative way of getting input with close to no lag or it's just the browser export? Any way to fix it?</p> <p> <table><tr><td> <a href="/bbs/?pid=106396#p"> <img src="/bbs/thumbs/pico8_wozowopema-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=106396#p"> wozowopema</a><br><br> by <a href="/bbs/?uid=53064"> olivarra1</a> <br><br><br> <a href="/bbs/?pid=106396#p"> [Click to Play]</a> </td></tr></table> </p> https://www.lexaloffle.com/bbs/?tid=46460 https://www.lexaloffle.com/bbs/?tid=46460 Sat, 05 Feb 2022 20:56:34 UTC [Hackish] http requests from cartridges <p>I've made a spike for cartriges to send http requests and receive their response.</p> <p>The way it works is the cartrige communicates with the browser through gpio. In here I'm using 2 assumptions (I guess dangerous ones) that make it quite simple to send messages:</p> <ol> <li>We get synchronous interrupts from p8 -&gt; JS when p8 writes to a GPIO.</li> <li>We read an analog value 0-255 in perfect accuracy.</li> </ol> <p>So... yep, this code atm only works in a browser. And it might be posible that it breaks later on (specially for assumption 1, as it depends on the runtime implementation)</p> <p>It's separated in two layers: The first one just abstracts over sending and receiving any message</p> <div> <div style="max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>html_messaging.lua html_messaging={ send_message=function(self, message) -- sends a `message` string to the host HTML end, add_listener=function(self, listener) -- adds the `listener` function to be called with receiving messages end, remove_listener=function(self, listener) -- stops sending message updates to the listener end, update=function(self) -- this function must be called on every update, so that html_messaging can check for new messages end }</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p><div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <div> <div style="max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre> html_messaging={ gpio=0x5f80, pins={ send_req=1, -- output read_req=3, -- input clk_out=5, --input data_out=7, -- output data_in=8 -- input }, send_message=function(self, message) if #message == 0 then return end poke(self.gpio + self.pins.send_req, 0xFF) for i=1,#message do poke(self.gpio + self.pins.data_out, ord(message, i)) end poke(self.gpio + self.pins.send_req, 0x00) end, _listeners={}, add_listener=function(self, listener) add(self._listeners, listener) end, remove_listener=function(self, listener) del(self._listeners, listener) end, update=function(self) local message = __messaging_receive_message() if not message then return end for listener in all(self._listeners) do listener(message) end end } function __messaging_receive_message() local gpio = html_messaging.gpio local pins = html_messaging.pins local is_sending = peek(gpio + pins.read_req) == 0xff if not is_sending then return false end local input_buffer = &quot;&quot; while peek(gpio + pins.read_req) == 0xff do input_buffer = input_buffer..chr(peek(gpio + pins.data_in)) poke(gpio + pins.clk_out, 0xff) poke(gpio + pins.clk_out, 0x00) end return input_buffer end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p></div></div></div></p> <div> <div style="max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>gpio_messaging.js // This file also sets window.pico8_gpio - Make sure not to override it, or it won't get the interrupts! window.pico8_gpioMessaging={ sendMessage(message) { // sends the string `message` to p8 // returns a promise that resolves when the message was completely sent // (we don't have synchronous interrupts from js -&gt; p8) } } // pico8_gpioMessaging is an EventEmitter of 'message' events // which have a 'message' prop with the received message</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p><div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <div> <div style="max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre> (function () { class GPIO extends EventTarget { static READ = 'read'; static WRITE = 'write'; pins = new Array(128); get interruptPins() { return new Proxy(this, { get: (object, prop) =&gt; { const evt = new ReadEvent(prop); this.dispatchEvent(evt); return object.pins[prop]; }, set: (object, prop, value) =&gt; { object.pins[prop] = value; const evt = new WriteEvent(prop, value); this.dispatchEvent(evt); } }); } } window.P8GPIO = GPIO; class ReadEvent extends Event { pin = 0; constructor(pin) { super(GPIO.READ); this.pin = Number.parseInt(pin); } } class WriteEvent extends Event { pin = 0; payload = ''; constructor(pin, payload) { super(GPIO.WRITE); this.pin = Number.parseInt(pin); this.payload = payload; } } const gpio = new GPIO(); window.pico8_gpioBus = gpio; window.pico8_gpio = gpio.interruptPins; const HIGH = 0xFF; const LOW = 0x00; const PINS = { sendReq: 1, // input readReq: 3, // output clkIn: 5, // output dataIn: 7, // input dataOut: 8 // output } class GPIOMessaging extends EventTarget { static MESSAGE = 'message'; constructor() { super(); gpio.addEventListener(GPIO.WRITE, event =&gt; { if (event.pin === PINS.sendReq &amp;&amp; event.payload === HIGH) { this._receiveMessage(); } }) } _messageQueue = new Array(); sendMessage(message) { const { resolve, promise } = destructPromise(); this._messageQueue.push({ resolve, message }); this._flushMessages(); return promise; } _isSending = false; _flushMessages() { if (this._isSending || this._messageQueue.length === 0) return; const { resolve, message } = this._messageQueue.shift(); if(message.length === 0) { return resolve(); } this._isSending = true; let idx = 0; const self = this; function handleWrite({ pin, payload }) { if(pin === PINS.clkIn &amp;&amp; payload === LOW) { idx++; if (message.length &lt;= idx) { gpio.removeEventListener(GPIO.WRITE, handleWrite); gpio.pins[PINS.readReq] = LOW resolve(); self._isSending = false; self._flushMessages(); } else { gpio.pins[PINS.dataOut] = message.charCodeAt(idx) } } } gpio.addEventListener(GPIO.WRITE, handleWrite); gpio.pins[PINS.dataOut] = message.charCodeAt(idx) gpio.pins[PINS.readReq] = HIGH } _receiveMessage() { let buffer = &quot;&quot;; const _self = this; function handleWrite({ pin, payload }) { switch (pin) { case PINS.dataIn: buffer += String.fromCharCode(payload); break; case PINS.sendReq: if (payload === LOW) { gpio.removeEventListener(GPIO.WRITE, handleWrite); const event = new MessageEvent(buffer); _self.dispatchEvent(event); } break; } } gpio.addEventListener(GPIO.WRITE, handleWrite); // In here we told pico8 we were ready to receive // Now that's not needed anymore, as p8 assumes interrupts are synchronous // gpio.pins[2] = HIGH } } window.P8GPIOMessaging = GPIOMessaging; class MessageEvent extends Event { message = '' constructor(message) { super(GPIOMessaging.MESSAGE) this.message = message; } } window.pico8_gpioMessaging = new GPIOMessaging(); function destructPromise() { let resolve, reject const promise = new Promise((res, rej) =&gt; { resolve = res; reject = rej; }); return { resolve, reject, promise }; } })();</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p></div></div></div></p> <p>The other layer needs to have that one imported first - This is the one to send and receive HTTP requests</p> <div> <div style="max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>http_request.lua http_request={ get=function(self,url,cb) -- sends a GET request to url, sending the message as a string to callback once it's received end }</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p><div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <div> <div style="max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>-- requires html_messaging.lua http_request={ _cid=0, get=function(self,url,cb) local cid = self._cid self._cid = self._cid + 1 local handle_message = function(message) local match_header = &quot;HTTP RES &quot;..cid local msg_header = sub(message, 1, #match_header) if match_header != msg_header then return end local response = sub(message, #match_header + 2) cb(response) html_messaging:remove_listener(handle_message) end html_messaging:add_listener(handle_message) html_messaging:send_message(&quot;HTTP REQ &quot;..cid..&quot; GET &quot;..url) end }</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p></div></div></div></p> <div> <div style="max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>http_request.js // Exports nothing: This file listens for p8 http requests and proxies them to the real BE</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p><div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <div> <div style="max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>// Requires gpio_messaging.js pico8_gpioMessaging.addEventListener('message', ({ message }) =&gt; { if (message.startsWith(&quot;HTTP REQ&quot;)) { const [http, req, cid, method, url] = message.split(&quot; &quot;); fetch(url, { method }) .then(result =&gt; result.text()) .then(result =&gt; pico8_gpioMessaging.sendMessage(`HTTP RES ${cid} ${result}`)) } })</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p></div></div></div></p> <p>I can't embed an example cartrige here because it also needs some setup on the HTML/JS side, but a simple script that requests the datetime from worldclockapi:</p> <div> <div style="max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>#include html_messaging.lua #include http_request.lua function _update() if btnp(🅾️) then http_request:get(&quot;http://worldclockapi.com/api/json/utc/now&quot;, function (response) printh(response) end ) end html_messaging:update() end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>The only thing the host HTML needs is importing both gpio_messaging.js and http_request.js.</p> <p>Now I only need to write a JSON parser :'D (/s - I know this would be a bad idea).</p> <p>I think this might be useful for simple ad-hoc services, like global highscores. Also maybe to run around the size limit in cartridge for things like level data, or maybe even to some extend turn-based multiplayer games? :exploding_head:</p> https://www.lexaloffle.com/bbs/?tid=42526 https://www.lexaloffle.com/bbs/?tid=42526 Sun, 18 Apr 2021 09:19:47 UTC Mine Walker <p> <table><tr><td> <a href="/bbs/?pid=90301#p"> <img src="/bbs/thumbs/pico8_zujewipewe-6.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=90301#p"> zujewipewe</a><br><br> by <a href="/bbs/?uid=53064"> olivarra1</a> <br><br><br> <a href="/bbs/?pid=90301#p"> [Click to Play]</a> </td></tr></table> </p> <p>I present my very first game in pico-8 - Inspired by a predecessor of the very well known Mine Sweeper, a game for the ZX Spectrum called &quot;Mined Out!&quot;</p> <p>In this game you must reach an objective (the bottom right corner) without stepping on a mine. As you move, you'll get information of how many mines are around you.</p> <p>The level selector (0 -&gt; 9) only the number of mines added, the size of the board and a score multiplier. The position of the mines is completely random.<br /> It also uses a seed system so you can share the same RNG with another player so you can compete in the same conditions.</p> https://www.lexaloffle.com/bbs/?tid=42395 https://www.lexaloffle.com/bbs/?tid=42395 Fri, 09 Apr 2021 21:40:04 UTC