<!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, "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 >