<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>AI RPG</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> <style type="text/css"> body { margin: 0; background-color: black; } .game { position: absolute; top: 0px; left: 0px; margin: 0px; border: 0; width: 100%; height: 100%; overflow: hidden; display: block; image-rendering: optimizeSpeed; image-rendering: -moz-crisp-edges; image-rendering: -o-crisp-edges; image-rendering: -webkit-optimize-contrast; image-rendering: optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated; -ms-interpolation-mode: nearest-neighbor; } #inputdiv, #buy_menu { position: absolute; top: 0px; left: 0px; margin: 0px; border: 0; width: 100%; height: 100%; justify-content: center; align-items: center; } #inputdiv { z-index: 10; display: none; /* set to flex by javascript */ overflow: hidden; background: #00000090; } #inputtext { position: absolute; top: 0; left: 0; background: none; width: 90%; height: 50%; margin-left: 5%; margin-right: 5%; border-top-style: hidden; border-right-style: hidden; border-left-style: hidden; border-bottom-style: hidden; transform: translateY(100%); text-align: center; font-size: 2em; font-family: Arial; color: white; } #inputtext:focus { outline: none; } #inputbuttondiv { position: absolute; display: flex; width: 100vw; justify-content: center; flex-wrap: wrap; height: 170px; margin-top: 40vh; } #inputbutton { font-family: Arial; font-size: 3em; color: white; background: none; border-radius: 10px; border: 2px solid; border-color: white; padding: 10px; margin: 30px; } #buy_menu { display: none; /* set in javascript when shown when error code */ flex-direction: column; justify-content: center; word-wrap: break-word; align-items: center; text-align: center; height: 100vh; background: #000000D0; font-family: arial; gap: 20px; color: #fff; z-index: 20; } #buy_menu h1, #daypass { margin-left: 30px; margin-right: 30px; } /* Style the payment screen title */ #buy_menu h1 { font-size: 32px; } /* Style the payment button */ #payment-button, #submit-button { background-color: #fff; color: #000; font-size: 24px; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; } #daypass { border: 4px solid white; border-radius: 25px; padding: 10px; display: flex; gap: 20px; } #codebox { border: 4px solid white; border-radius: 10px; padding: 10px; margin: 30px; } #visibility-toggle, #closebutton { border: none; font-size: 30px; background: none; color: white; } #closebutton { float: right; } /* Media queries for smaller screens */ @media screen and (max-width: 480px) { #buy_menu h1 { font-size: 24px; margin-bottom: 20px; } #payment-button { font-size: 20px; padding: 8px 16px; } } </style> <link rel="icon" type="image/x-icon" href="/favicon.ico"> </head> <body style="background:black"> <div id="buy_menu"> <h1>AI is expensive so you need to pay for it</h1> <p>You can play free 10 minutes a day. If you're seeing this you must buy a day pass to continue. If you already bought one, but are still seeing this, check that the code matches up. If you're unsatisfied with your service, you can get a refund via the email stripe sent you.</p> <div id="daypass"> <button id="closebutton" onclick="closepayment()"><i class="fa-regular fa-xmark"></i></button> <p>24 Hour Pass</p> <button id="payment-button" onclick="do_checkout()">Buy Now</button> </div> <div id="codebox"> <label for="pass">Day pass code:</label> <input type="password" name="pass" id="codeinput" onkeyup='on_codeinput_key(event);' required> <button id="visibility-toggle" onclick="visibility_toggle()"><i class="fa fa-eye-slash" id="visibility-toggle-eye"></i></button> </div> </div> <div id="inputdiv" class="inputdiv"> <textarea onkeyup='on_textarea_key(event);' id="inputtext"> </textarea> <div id="inputbuttondiv"> <button id="inputbutton" onclick="end_dialog()"> Submit </button> <button id="inputbutton" onclick="end_without_saying()"> Cancel </button> </div> </div> <canvas class="game" id="canvas" oncontextmenu="event.preventDefault()" width="1504" height="864"></canvas> <script type="text/javascript"> var Module = { preRun: [], postRun: [], print: (function() { return function(text) { text = Array.prototype.slice.call(arguments).join(' '); console.log(text); }; })(), printErr: function(text) { text = Array.prototype.slice.call(arguments).join(' '); console.error(text); }, canvas: (function() { var canvas = document.getElementById('canvas'); canvas.addEventListener("webglcontextlost", function(e) { alert('FIXME: WebGL context lost, please reload the page'); e.preventDefault(); }, false); return canvas; })(), setStatus: function(text) { }, monitorRunDependencies: function(left) { }, }; window.onerror = function(event) { console.log("onerror: " + event.message); for(;;) {} }; </script> <script type="text/javascript"> let game_doing_input = true; let server_url = ""; let codeinput = document.getElementById("codeinput"); function set_my_code(new_code) { let final_codeinput_string = ""; for(let i = 0; i < new_code.length; i++) { let cur = new_code[i]; let cur_charcode = cur.charCodeAt(0); if(cur_charcode >= "a".charCodeAt(0) && cur_charcode <= "z".charCodeAt(0)) { final_codeinput_string += String.fromCharCode(cur_charcode + ("A".charCodeAt(0) - "a".charCodeAt(0))); } else { if((cur_charcode >= "A".charCodeAt(0) && cur_charcode <= "Z".charCodeAt(0)) || (cur_charcode >= "0".charCodeAt(0) && cur_charcode <= "9".charCodeAt(0))) { final_codeinput_string += cur; } } } codeinput.value = final_codeinput_string; } function get_my_code() { return codeinput.value; } let code_visible = false; let input_modal = document.getElementById("inputdiv"); let pause_modal = document.getElementById("buy_menu"); let save_game_data = null; function visibility_toggle() { if(code_visible) { code_visible = false; document.getElementById("codeinput").type = "password"; document.getElementById("visibility-toggle-eye").className = "fa fa-eye-slash"; } else { code_visible = true; document.getElementById("codeinput").type = "text"; document.getElementById("visibility-toggle-eye").className = "fa fa-eye"; } } function frame(delta) { let input_visible = input_modal.style.display === "flex"; let pause_visible = pause_modal.style.display === "flex"; if( Module.ccall('is_receiving_text_input', 'bool', [], [])) { if(!input_visible) { document.getElementById("inputtext").value = ""; setTimeout(function(){document.getElementById("inputtext").focus();},50); // for some reason focus doesn't work immediately here document.getElementById("inputdiv").style.display = "flex"; } } if( (input_visible || pause_visible) && game_doing_input) { Module.ccall('stop_controlling_input', 'void', [], []); game_doing_input = false; } if( !input_visible && !pause_visible && !game_doing_input) { Module.ccall('start_controlling_input', 'void', [], []); game_doing_input = true; } window.requestAnimationFrame(frame); } Module.onRuntimeInitialized = () => { window.requestAnimationFrame(frame); } const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; // Use a lookup table to find the index. const lookup = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256); for (let i = 0; i < chars.length; i++) { lookup[chars.charCodeAt(i)] = i; } const encode = (arraybuffer) => { let bytes = new Uint8Array(arraybuffer), i, len = bytes.length, base64 = ''; for (i = 0; i < len; i += 3) { base64 += chars[bytes[i] >> 2]; base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; base64 += chars[bytes[i + 2] & 63]; } if (len % 3 === 2) { base64 = base64.substring(0, base64.length - 1) + '='; } else if (len % 3 === 1) { base64 = base64.substring(0, base64.length - 2) + '=='; } return base64; }; const decode = (base64) => { let bufferLength = base64.length * 0.75, len = base64.length, i, p = 0, encoded1, encoded2, encoded3, encoded4; if (base64[base64.length - 1] === '=') { bufferLength--; if (base64[base64.length - 2] === '=') { bufferLength--; } } const arraybuffer = new ArrayBuffer(bufferLength), bytes = new Uint8Array(arraybuffer); for (i = 0; i < len; i += 4) { encoded1 = lookup[base64.charCodeAt(i)]; encoded2 = lookup[base64.charCodeAt(i + 1)]; encoded3 = lookup[base64.charCodeAt(i + 2)]; encoded4 = lookup[base64.charCodeAt(i + 3)]; bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); } return arraybuffer; }; function end_dialog() { document.getElementById("inputdiv").style.display = "none"; Module.ccall('end_text_input', 'void', ['string'], [document.getElementById("inputtext").value]); } function show_paywall() { document.getElementById('buy_menu').style.display = "flex"; } function hide_paywall() { document.getElementById('buy_menu').style.display = "none"; } function on_codeinput_key(event) { set_my_code(get_my_code()); } function save_all() { document.cookie = get_my_code(); if (typeof(Storage) !== "undefined") { console.log("Saving full game"); Module.ccall('dump_save_data', 'void', [], []); localStorage.setItem("game", encode(save_game_data)); } } function load_all() { set_my_code(document.cookie); if (typeof(Storage) !== "undefined") { console.log("Attempting read of full game..."); let read_data = localStorage.getItem("game"); if (read_data != null) { console.log("Reading full game"); let decoded = decode(read_data); Module.ccall('read_from_save_data', 'void', ['array', 'number'], [new Uint8Array(decoded), decoded.byteLength]); } } } function end_without_saying() { document.getElementById("inputtext").value = ""; end_dialog(); } function on_textarea_key(event) { let final_textarea_string = ""; let cur_textarea_string = document.getElementById("inputtext").value; let should_end = false; for(let i = 0; i < Math.min(cur_textarea_string.length, 800); i++) { let cur = cur_textarea_string[i]; if(cur === "\n") { should_end = true; continue; } if(cur.charCodeAt(0) >= 255) continue; // non ascii gtfo if(cur === "|") continue; // used for splitting final_textarea_string += cur_textarea_string[i]; } document.getElementById("inputtext").value = final_textarea_string; if(event.key === "Enter") should_end = true; if(event.key === "Escape") { document.getElementById("inputtext").value = ""; should_end = true; } // have a boolean flag here and call it once so the game's end dialog function isn't called twice if(should_end) end_dialog(); } const max_retries = 5; let cur_id = 1; // zero is not a valid id, the zero value means no async request currently active let generation_requests = []; // array of dictionaries with structure: /* { id: number, request: XMLHTTPRequest, request_info: { url: string, body: string } retries_remaining: number, // on failure, retries over and over until out of retries } */ function do_checkout() { fetch(server_url + "/checkout").then((response) => response.text()).then((text) => { split_up = text.split("|"); if(split_up.length !== 2) { console.log("Weird response from server, length isn't 2"); } else { set_my_code(split_up[0]) save_all(); let url_to_go_to = split_up[1]; window.location.href = url_to_go_to; } }).catch((error) => { console.log("Error doing checkout: " + error); }); } function resend_request(r) { r.failed = false; r.request = new XMLHttpRequest(); r.request.onerror = function(e) { r.failed = true; }; r.request.open("POST", r.request_info.url, true); r.request.send(get_my_code() + "|" + r.request_info.body); } // server URL is decided in C function set_server_url(p) { server_url = p; } // Returns an integer, a "handle" into the generation request. Takes in the string prompt, and api URL // the api url must start with `http://` or https. function make_generation_request(p, api) { cur_id += 1; let to_push = { "id": cur_id, "request": new XMLHttpRequest(), "retries_remaining": max_retries, "request_info": {"url": (' ' + api).slice(1), "body": p}, "failed": false, } console.log("Making generation request with id " + to_push.id); generation_requests.push(to_push) resend_request(to_push) return cur_id; } // returns 0 if not done yet, 1 if succeeded, 2 if failed after too many retries (i.e server down, or no internet) // -1 if it doesn't exist function get_generation_request_status(id) { for(let i = 0; i < generation_requests.length; i++) { if(generation_requests[i].id == id) { if(generation_requests[i].failed) { } else { let http_status = generation_requests[i].request.status; if(http_status == 200) { if(generation_requests[i].request.responseText[0] === "0") { show_paywall(); return 2; } else if(generation_requests[i].request.responseText[0] === "1") { return 1; // done, everything ok } else { console.log("Unknown body code " + generation_requests[i].request.responseText); } } else if(http_status == 0) { // not done yet return 0; } } { // errored if(generation_requests[i].retries_remaining > 0) { console.log("Retrying request"); generation_requests[i].retries_remaining -= 1; resend_request(generation_requests[i]); return 0; } else { return 2; // too many retries, failed } } } } return -1; } function done_with_generation_request(id) { console.log("Removing request with id " + id); let new_generation_requests = []; for(let i = 0; i < generation_requests.length; i++) { if(generation_requests[i].id == id) { } else { new_generation_requests.push(generation_requests[i]) } } generation_requests = new_generation_requests; } // doesn't fill string if not done yet, or the id doesn't exist function get_generation_request_content(id) { let to_return = ""; for(let i = 0; i < generation_requests.length; i++) { if(generation_requests[i].id == id) { to_return = generation_requests[i].request.responseText; break; } } return to_return.slice(1); // first character is a code that says if the request was successful or not } </script> {{{ SCRIPT }}} </body></html>