class SceneManager { constructor() { this.scene = null; this.preload = false; } load(scene, data=null) { this.unload(); UI.rebuild(); if(scene !== null) { // Prepare new scene this.scene = new scene(); // Preload scene if defined if(this.scene.preload !== undefined) { if(this.scene.preload(data, null)) { this.preload = true; } else { this.scene.load(SCENES.Browse); } } // Otherwise, load scene else { this.preload = false; this.scene.load(data); UI.update_status(); } } } unload() { if(!this.preload && this.scene !== null && this.scene.unload !== undefined) { this.scene.unload(); } } load_url() { let parts = window.location.pathname.split("/"); if(parts.length > 1) { switch(parts[1]) { case "continue": this.load(SCENES.Continue); return; case "live": this.load(SCENES.Live); return; case "history": this.load(SCENES.History); return; case "practice": this.load(SCENES.GamePractice); return; case "guide": this.load(SCENES.Guide); return; case "about": this.load(SCENES.About); return; case "challenge": this.load(SCENES.Challenge); return; case "extras": this.load(SCENES.Extras); return; case "game": { if(parts[2]) { let token = UNPACK.base64(parts[2] + "="); this.load(SCENES.Game, { token:token, mode:INTERFACE.Mode.Review, }); return; } } break; case "u": { if(parts[2]) { this.load(SCENES.Profile, { handle:parts[2], }); return; } } break; } } this.load(SCENES.Browse); } disconnect() { if(this.scene !== null && this.scene.disconnect !== undefined) { this.scene.disconnect(); } } message(msg) { if(this.scene !== null) { // Handle preloading scene if(this.preload) { // Send message to preloader let result = this.scene.load(msg); // Finalize scene if load successful if(result === true) { this.preload = false; UI.update_status(); } // Load default scene if preload failed else if(result === false) { this.load(SCENES.Browse); } } // Handle loaded scene else { this.scene.message(msg); } } } } let SCENE = new SceneManager(); const SCENES = { Init: class{ constructor() { } load() { SCENE.load(SCENES.Offline); RECONNECT(); return true; } }, Offline:class{ constructor() { } load() { UI.nav([ UI.button(LANG("reconnect"), () => { RECONNECT(); }), UI.button(LANG("practice"), () => { SCENE.load(SCENES.GamePractice); }), ], []); return true; } message(msg) { switch(msg.code) { case OpCode.Hello: { console.log("CLIENT VERSION: '" + CONFIG_VERSION + "'"); console.log("SERVER VERSION: '" + msg.data.version + "'"); if(CONFIG_VERSION == msg.data.version) { RESUME(); } else { location.reload(); } } break; } } }, Register:class{ constructor() { } load() { if(sessionStorage.getItem("auth") !== null) return false; UI.mainmenu("register"); UI.mainnav([], [], { auth:true }); let container = document.createElement("section"); let form = document.createElement("form"); let tb_handle = UI.textbox("handle", "user"); tb_handle.setAttribute("maxlength", 24); tb_handle.addEventListener("input", (event) => { let value = event.target.value; if(value.length == 0 || VALID.username(value)) { event.target.removeAttribute("class"); } else { event.target.setAttribute("class", "error"); } }) form.appendChild(UI.table(null, [ [ UI.label(LANG("handle"), "handle"), tb_handle ], [ UI.label(LANG("secret"), "secret"), UI.password("secret") ], [ UI.label(LANG("invitation"), "code"), UI.textbox("code", "####-####-####") ], ])); let button = UI.submit(LANG("register")); button.setAttribute("id", "submit"); form.appendChild(button); form.addEventListener("submit", (event) => { event.preventDefault(); let handle = document.getElementById("handle"); let secret = document.getElementById("secret"); let code = document.getElementById("code"); handle.removeAttribute("class"); secret.removeAttribute("class"); code.removeAttribute("class"); event.target.removeAttribute("class"); let invitation = code.value; invitation = invitation.replace(/\-/g, ''); if(VALID.username(handle.value) && secret.value.length > 0 && invitation.length > 0) { event.target.setAttribute("disabled", ""); let enc = new TextEncoder(); let enc_handle = enc.encode(handle.value); let enc_secret = enc.encode(secret.value); let enc_code = enc.encode(invitation); MESSAGE_COMPOSE([ PACK.u16(OpCode.Register), PACK.u16(enc_handle.length), enc_handle, PACK.u16(enc_secret.length), enc_secret, PACK.u16(enc_code.length), enc_code, ]); } else { if(handle.value.length == 0 || handle.value.length > 24) { handle.setAttribute("class", "error"); } if(secret.value.length == 0) { secret.setAttribute("class", "error"); } if(code.value.length == 0) { code.setAttribute("class", "error"); } } }); container.appendChild(form); MAIN.appendChild(container); MAIN.setAttribute("class", "form"); document.getElementById("handle").focus(); return true; } message(msg) { if(msg.code == OpCode.Register && msg.data !== null) { let submit = document.getElementById("submit"); switch(msg.data.status) { case Status.Ok: { let b64_token = PACK.base64(msg.data.token); let b64_secret = PACK.base64(msg.data.secret); sessionStorage.setItem("auth", b64_token); sessionStorage.setItem("auth_secret", b64_secret); SCENE.load_url(); } break; default: { submit.removeAttribute("disabled"); switch(msg.data.status) { case Status.BadHandle: { document.getElementById("handle").setAttribute("class", "error"); submit.setAttribute("class", "error"); } break; case Status.BadSecret: { document.getElementById("secret").setAttribute("class", "error"); submit.setAttribute("class", "error"); } break; case Status.BadCode: { document.getElementById("code").setAttribute("class", "error"); submit.setAttribute("class", "error"); } break; } } } } } disconnect() { SCENE.load(SCENES.Offline); } }, Authenticate:class{ constructor() { } load() { if(sessionStorage.getItem("auth") !== null) return false; UI.mainmenu("authenticate"); UI.mainnav([], [], { auth:true }); let container = document.createElement("section"); let form = document.createElement("form"); let tb_handle = UI.textbox("handle", "user"); tb_handle.setAttribute("maxlength", 24); form.appendChild(UI.table(null, [ [ UI.label(LANG("handle"), "handle"), tb_handle ], [ UI.label(LANG("secret"), "secret"), UI.password("secret") ], ])); let button = UI.submit(LANG("login")); button.setAttribute("id", "submit"); form.appendChild(button); form.addEventListener("submit", (event) => { event.preventDefault(); let handle = document.getElementById("handle"); let secret = document.getElementById("secret"); handle.removeAttribute("class"); secret.removeAttribute("class"); event.target.removeAttribute("class"); if(handle.value.length > 0 && handle.value.length <= 24 && secret.value.length > 0) { event.target.setAttribute("disabled", ""); let enc = new TextEncoder(); let enc_handle = enc.encode(handle.value); let enc_secret = enc.encode(secret.value); MESSAGE_COMPOSE([ PACK.u16(OpCode.Authenticate), PACK.u16(enc_handle.length), enc_handle, PACK.u16(enc_secret.length), enc_secret, ]); } else { if(handle.value.length == 0 || handle.value.length > 24) { handle.setAttribute("class", "error"); } if(secret.value.length == 0) { secret.setAttribute("class", "error"); } } }); container.appendChild(form); MAIN.appendChild(container); MAIN.setAttribute("class", "form"); document.getElementById("handle").focus(); return true; } message(msg) { if(msg.code == OpCode.Authenticate && msg.data !== null) { let submit = document.getElementById("submit"); switch(msg.data.status) { case Status.Ok: { sessionStorage.setItem("auth", PACK.base64(msg.data.token)); sessionStorage.setItem("auth_secret", PACK.base64(msg.data.secret)); CONTEXT.Auth = { handle: msg.data.handle, }; SCENE.load_url(); } break; case Status.Error: { submit.removeAttribute("disabled"); document.getElementById("handle").setAttribute("class", "error"); document.getElementById("secret").setAttribute("class", "error"); submit.setAttribute("class", "error"); } } } } disconnect() { SCENE.load(SCENES.Offline); } }, Browse:class{ constructor() { this.data = null; this.page = 1; this.pages = 1; } preload() { this.refresh(); return true; } load(msg) { if(msg.code != OpCode.SessionList) { return null; } else { this.data = msg.data.records; } if(SOCKET !== null) { UI.mainmenu("browse"); let indicator_page = UI.div([]); indicator_page.setAttribute("id", "indicator-page"); UI.mainnav( [ ], [ indicator_page, UI.button("◀", () => { if(this.page < this.pages) { this.page += 1; } this.refresh(); }), UI.button("▶", () => { if(this.page > 1) { this.page -= 1; } this.refresh(); }), UI.button(LANG("refresh"), () => { this.referesh(); }), ], { auth:true, session:true } ); let table = document.createElement("table"); table.setAttribute("id", "content"); table.setAttribute("class", "list session"); table.appendChild(UI.session_table(this.data)); UI.maincontent(table); UI.page_indicator((this.data.length > 0)? 1 : 0, this.data.length, this.data.length); history.pushState(null, "Dzura", "/"); } else { SCENE.load(SCENES.Offline); } return true; } refresh() { MESSAGE_SESSION_LIST(this.page, [ filter(FilterCode.IsComplete, false), ]); } /*message(code, data) { switch(code) { case OpCode.SessionList: { let table = document.getElementById("content"); UI.clear(table); if(data !== null) { UI.page_indicator((data.records.length > 0)? 1 : 0, data.records.length, data.records.length); table.appendChild(UI.session_table(data.records)); } } break; } }*/ disconnect() { SCENE.load(SCENES.Offline); } }, Continue:class{ constructor() { this.data = null; this.page = 1; this.pages = 1; } preload() { this.refresh(); return true; } load(msg) { if(msg.code != OpCode.SessionList) { return null; } else { this.data = msg.data.records; } if(sessionStorage.getItem("auth") === null) return false; UI.mainmenu("continue"); let indicator_page = UI.div([]); indicator_page.setAttribute("id", "indicator-page"); UI.mainnav( [ ], [ indicator_page, UI.button("◀", () => { if(this.page < this.pages) { this.page += 1; } this.refresh(); }), UI.button("▶", () => { if(this.page > 1) { this.page -= 1; } this.refresh(); }), UI.button(LANG("refresh"), () => { this.referesh(); }), ], { auth:true, session:true } ); let table = document.createElement("table"); table.setAttribute("id", "content"); table.setAttribute("class", "list session"); table.appendChild(UI.session_table_resume(this.data)); UI.maincontent(table); UI.page_indicator((this.data.length > 0)? 1 : 0, this.data.length, this.data.length); history.pushState(null, "Dzura - Continue", "/continue/"); return true; } refresh() { MESSAGE_SESSION_LIST(this.page, [ filter(FilterCode.IsComplete, false), filter(FilterCode.IsPlayer, true), ]); } /*message(code, data) { switch(code) { case OpCode.SessionList: { let table = document.getElementById("content"); UI.clear(table); if(data !== null) { UI.page_indicator((data.records.length > 0)? 1 : 0, data.records.length, data.records.length); table.appendChild(UI.session_table_resume(data.records)); } } break; } }*/ disconnect() { SCENE.load(SCENES.Offline); } }, Live:class{ constructor() { this.data = null; this.page = 1; this.pages = 1; } preload() { this.refresh(); return true; } load(msg) { if(msg.code != OpCode.SessionList) { return null; } else { this.data = msg.data.records; } UI.mainmenu("live"); let indicator_page = UI.div([]); indicator_page.setAttribute("id", "indicator-page"); UI.mainnav( [ ], [ indicator_page, UI.button("◀", () => { if(this.page < this.pages) { this.page += 1; } this.refresh(); }), UI.button("▶", () => { if(this.page > 1) { this.page -= 1; } this.refresh(); }), UI.button(LANG("refresh"), () => { this.referesh(); }), ], { auth:true, session:true } ); let table = document.createElement("table"); table.setAttribute("id", "content"); table.setAttribute("class", "list session"); table.appendChild(UI.session_table(this.data)); UI.maincontent(table); UI.page_indicator((this.data.length > 0)? 1 : 0, this.data.length, this.data.length); history.pushState(null, "Dzura - Live", "/live/"); return true; } refresh() { MESSAGE_SESSION_LIST(this.page, [ filter(FilterCode.IsComplete, false), filter(FilterCode.IsLive, true), ]); } /*message(code, data) { switch(code) { case OpCode.SessionList: { let table = document.getElementById("content"); UI.clear(table); if(data !== null) { UI.page_indicator((data.records.length > 0)? 1 : 0, data.records.length, data.records.length); table.appendChild(UI.session_table(data.records)); } } break; } }*/ disconnect() { SCENE.load(SCENES.Offline); } }, History:class{ constructor() { this.data = null; this.page = 1; this.pages = 1; } preload() { this.refresh(); return true; } load(msg) { if(msg.code != OpCode.SessionList) { return null; } else { this.data = msg.data.records; } UI.mainmenu("history"); let indicator_page = UI.div([]); indicator_page.setAttribute("id", "indicator-page"); UI.mainnav( [ ], [ indicator_page, UI.button("◀", () => { if(this.page < this.pages) { this.page += 1; } this.refresh(); }), UI.button("▶", () => { if(this.page > 1) { this.page -= 1; } this.refresh(); }), UI.button(LANG("refresh"), () => { this.referesh(); }), ], { auth:true, session:true } ); let table = document.createElement("table"); table.setAttribute("id", "content"); table.setAttribute("class", "list session"); table.appendChild(UI.session_table_history(this.data)); UI.maincontent(table); UI.page_indicator((this.data.length > 0)? 1 : 0, this.data.length, this.data.length); history.pushState(null, "Dzura - History", "/history/"); return true; } refresh() { MESSAGE_SESSION_LIST(this.page, [ filter(FilterCode.IsComplete, true), ]); } message(code, data) { switch(code) { case OpCode.SessionList: { let table = document.getElementById("content"); UI.clear(table); if(data !== null) { UI.page_indicator((data.records.length > 0)? 1 : 0, data.records.length, data.records.length); table.appendChild(UI.session_table_history(data.records)); } } break; } } disconnect() { SCENE.load(SCENES.Offline); } }, Guide:class{ constructor() { } load() { UI.mainmenu("guide"); UI.mainnav([ UI.button("Game", () => { SCENE.refresh("game.html"); }), UI.button("Pieces", () => { SCENE.refresh("pieces.html"); }), UI.button("Interface", () => { SCENE.refresh("interface.html"); }), ], []); let body = document.createElement("article"); body.setAttribute("id", "article"); body.setAttribute("class", "text"); UI.maincontent(body); this.refresh("game.html"); history.pushState(null, "Dzura - Guide", "/guide/"); return true; } refresh(page) { fetch("/guide/" + page) .then((response) => { return response.text(); }) .then((text) => { let parser = new DOMParser(); let body = parser.parseFromString(text, "text/html"); document.getElementById("article").innerHTML = body.body.innerHTML; }); } }, About:class{ constructor() { } load() { UI.mainmenu("about"); UI.mainnav([], []); let body = document.createElement("article"); body.setAttribute("id", "article"); body.setAttribute("class", "text"); UI.maincontent(body); this.refresh("main.html"); history.pushState(null, "Dzura - About", "/about/"); return true; } refresh(page) { fetch("/about/" + page) .then((response) => { return response.text(); }) .then((text) => { let parser = new DOMParser(); let body = parser.parseFromString(text, "text/html"); document.getElementById("article").innerHTML = body.body.innerHTML; }); } }, Extras:class{ constructor() { } load() { UI.mainmenu("extras"); history.pushState(null, "Dzura - About", "/extras/"); return true; } }, Profile:class{ constructor() { this.handle = ""; this.is_online = false; this.stats = { games_played: 0, }; this.history = [ ]; } preload(data) { this.handle = data.handle; MESSAGE_COMPOSE([ PACK.u16(OpCode.UserProfile), PACK.string(this.handle, PACK.u8), ]); return true; } load(msg) { if(msg.code == OpCode.UserProfile) { if(msg.data.status != Status.Ok) { return false; } } else { return null; } this.handle = msg.data.handle; this.is_online = msg.data.is_online; this.history = msg.data.history; UI.mainmenu_account(this.handle, "profile"); // Left Buttons let buttons_left = [ ]; // Right Buttons let buttons_right = [ ]; UI.mainnav(buttons_left, buttons_right); // Main Content MAIN.setAttribute("class", "profile"); let header = document.createElement("header"); let title = document.createElement("h1"); if(this.is_online) { title.innerText = this.handle + " ●"; title.setAttribute("class", "online"); } else { title.innerText = this.handle + " ○"; } header.appendChild(title); MAIN.appendChild(header); let table_stats = document.createElement("table"); let container_history = document.createElement("section"); container_history.setAttribute("class", "history"); let table_history = document.createElement("table"); table_history.setAttribute("id", "history"); table_history.setAttribute("class", "list session"); table_history.appendChild(UI.session_table_history(this.history)); container_history.appendChild(table_history); MAIN.appendChild(container_history); history.pushState(null, "Dzura - About", "/u/" + this.handle); return true; } }, Account:class{ constructor() { } load(data) { if(sessionStorage.getItem("auth") === null) return false; UI.mainmenu_account(data, "account"); // Left Buttons let buttons_left = [ ]; // Right Buttons let buttons_right = [ ]; buttons_right.push(UI.button(LANG("logout"), () => { BADGE_UPDATE(false); MESSAGE_COMPOSE([ PACK.u16(OpCode.Deauthenticate), ]); sessionStorage.clear(); CONTEXT.Auth = null; SCENE.load(SCENES.Browse); })); UI.mainnav(buttons_left, buttons_right); // Main Content let container = document.createElement("section"); let form = document.createElement("form"); form.appendChild(UI.table(null, [ [ UI.label(LANG("secret"), "secret"), UI.password("secret") ], ])); let button = UI.submit(LANG("auth")); button.setAttribute("id", "submit"); form.appendChild(button); form.addEventListener("submit", (event) => { event.preventDefault(); /*let secret = document.getElementById("secret"); secret.removeAttribute("class"); event.target.removeAttribute("class"); if(handle.value.length > 0 && handle.value.length <= 24 && secret.value.length > 0) { event.target.setAttribute("disabled", ""); let enc = new TextEncoder(); let enc_secret = enc.encode(secret.value); MESSAGE_COMPOSE([ PACK.u16(OpCode.Authenticate), PACK.u16(enc_handle.length), enc_handle, PACK.u16(enc_secret.length), enc_secret, ]); } else { if(handle.value.length == 0 || handle.value.length > 24) { handle.setAttribute("class", "error"); } if(secret.value.length == 0) { secret.setAttribute("class", "error"); } }*/ }); container.appendChild(form); MAIN.appendChild(container); MAIN.setAttribute("class", "form"); document.getElementById("secret").focus(); history.pushState(null, "Dzura - Account", "/u/" + CONTEXT.Auth.handle); return true; } }, Subscription:class{ constructor() { } load(data) { if(sessionStorage.getItem("auth") === null) return false; UI.mainmenu_account(null, "subscription"); // Left Buttons let buttons_left = [ ]; // Right Buttons let buttons_right = [ ]; buttons_right.push(UI.button("Cancel", () => { })); UI.mainnav(buttons_left, buttons_right); // Main Content history.pushState(null, "Dzura - About", "/u/" + CONTEXT.Auth.handle); return true; } }, Invitations:class{ constructor() { this.data = null; } preload(data) { if(sessionStorage.getItem("auth") === null) return false; MESSAGE_COMPOSE([ PACK.u16(OpCode.InviteList), ]); return true; } load(msg) { if(msg.code == OpCode.InviteList) { this.data = msg.data.records; } else { return null; } UI.mainmenu_account(null, "invitations"); // Left Buttons let buttons_left = [ UI.button("Acquire", () => { MESSAGE_COMPOSE([ PACK.u16(OpCode.InviteAcquire), ]) }), ]; // Right Buttons let buttons_right = [ ]; UI.mainnav(buttons_left, buttons_right); // Main Content let table = document.createElement("table"); table.setAttribute("id", "content"); table.setAttribute("class", "list invite"); table.appendChild(UI.invite_table(this.data)); UI.maincontent(table); history.pushState(null, "Dzura - About", "/u/" + CONTEXT.Auth.handle); return true; } message(msg) { switch(msg.code) { case OpCode.InviteAcquire: { MESSAGE_COMPOSE([ PACK.u16(OpCode.InviteList), ]); } break; case OpCode.InviteList: { this.data = msg.data.records; let table = document.getElementById("content"); UI.clear(table); table.appendChild(UI.invite_table(this.data)); } break; } } }, Game:class{ constructor() { this.mode = null; this.token = null; this.turn = null; this.rotate = 0; this.game = null; } preload(data) { this.mode = data.mode; this.token = data.token; if(data.rotate !== undefined) { this.rotate = data.rotate; } if(data.turn !== undefined) { this.turn = data.turn; } MESSAGE_SESSION_VIEW(this.token, this.mode == INTERFACE.Mode.Player); return true; } load(msg) { if(msg.code == OpCode.SessionView) { if(msg.data.status != Status.Ok) { return false; } } else { return null; } // Bottom Buttons let buttons_bottom = [ ]; if(this.mode == INTERFACE.Mode.Player) { let button_undo = UI.button(LANG("undo"), () => { INTERFACE.undo(); }); button_undo.setAttribute("id", "button-undo"); buttons_bottom.push(button_undo); let button_resign = UI.button(LANG("resign"), () => { INTERFACE.resign(); }); button_resign.setAttribute("id", "button-resign"); buttons_bottom.push(button_resign); } else { let button_react = UI.button("Clap", () => { INTERFACE.react(); }); buttons_bottom.push(button_react); } buttons_bottom.push(UI.button(LANG("back"), () => { SCENE.load(SCENES.Browse); })); // Standard Top Buttons let buttons_top = [ UI.button(LANG("rotate"), () => { INTERFACE.rotate(); }), UI.button(LANG("mirror"), () => { INTERFACE.mirror(); }), ]; // Resume / Review Buttons if(msg.data.player != 0) { if(this.mode == INTERFACE.Mode.Review) { if(!msg.data.is_complete) { let callback_resume = function() { SCENE.load(SCENES.Game, { token:this.token, mode:INTERFACE.Mode.Player, rotate:2 + ((INTERFACE_DATA.player & 1) ^ INTERFACE_DATA.rotate), }); } callback_resume = callback_resume.bind({ token: this.token, }); buttons_top.push(UI.button(LANG("resume"), callback_resume)); } } else { let callback_review = function() { SCENE.load(SCENES.Game, { token:this.token, mode:INTERFACE.Mode.Review, rotate:2 + ((INTERFACE_DATA.player & 1) ^ INTERFACE_DATA.rotate), }); } callback_review = callback_review.bind({ token: this.token, }); buttons_top.push(UI.button(LANG("review"), callback_review)); } } // Practice Button if(this.mode == INTERFACE.Mode.Review) { let play_callback = function() { let turn = INTERFACE_DATA.Replay.turn; if(INTERFACE_DATA.Game.history.length > 0) { if(INTERFACE_DATA.Game.history[turn - 1].source == 0xF) { turn -= 1; } } SCENE.load(SCENES.GamePractice, { token:this.token, history:INTERFACE_DATA.Game.history, turn:turn, rotate:(INTERFACE_DATA.player & 1) ^ INTERFACE_DATA.rotate, }); } play_callback = play_callback.bind({ token: this.token, }); buttons_top.push(UI.button(LANG("practice"), play_callback)); } UI.nav(buttons_top, buttons_bottom); // Turn Indicators, Scroll Bar if(this.mode == INTERFACE.Mode.Review) { let ind_turn = UI.div([UI.text("0 / 0")]); ind_turn.setAttribute("id", "indicator-turn"); UI.mainnav( [ ], [ UI.button(LANG("auto"), () => { INTERFACE.replay_toggle_auto(); }), UI.button("◀", () => { INTERFACE.replay_off(); INTERFACE.replay_first(); }), UI.button("◁", () => { INTERFACE.replay_off(); INTERFACE.replay_prev(); }), ind_turn, UI.button("▷", () => { INTERFACE.replay_off(); INTERFACE.replay_next(true); }), UI.button("▶", () => { INTERFACE.replay_off(); INTERFACE.replay_last(); }), ] ); let slider = UI.slider("turn-slider", (event) => { INTERFACE.replay_off(); INTERFACE.replay_jump(event.target.value, true); }); slider.setAttribute("min", "0"); slider.setAttribute("max", "0"); MAIN.appendChild(UI.div([ slider ], "turn-slider-padding")); } // Canvas let canvas = document.createElement("canvas"); canvas.setAttribute("id", "game"); MAIN.appendChild(canvas); // Interface INTERFACE.init(this.token, this.mode, { rotate:this.rotate, }); if(this.turn !== null) { INTERFACE_DATA.Replay.turn = this.turn; } history.pushState(null, "Dzura - Game", "/game/" + PACK.base64(this.token).slice(0, -1)); return true; } unload() { INTERFACE.uninit(); } message(msg) { INTERFACE.message(msg); } disconnect() { SCENE.load(SCENES.Offline); } }, GamePractice:class{ constructor() { this.game = null; } load(data) { let buttons_top = [ UI.button(LANG("rotate"), () => { INTERFACE.rotate(); }), UI.button(LANG("mirror"), () => { INTERFACE.mirror(); }), UI.button(LANG("cpu"), () => { INTERFACE.auto(); }), ]; if(data !== null) { let callback_review = function() { SCENE.load(SCENES.Game, { token:this.token, mode:INTERFACE.Mode.Review, turn:INTERFACE_DATA.Game.history_begin.length, rotate:2 + INTERFACE_DATA.rotate, }); } callback_review = callback_review.bind({ token: data.token, }); buttons_top.push(UI.button(LANG("review"), callback_review)); } let buttons_bottom = [ ]; buttons_bottom.push(UI.button(LANG("undo"), () => { INTERFACE.undo(); })); buttons_bottom.push(UI.button(LANG("reset"), () => { INTERFACE.reset(); })); buttons_bottom.push(UI.button(LANG("back"), () => { SCENE.load(SCENES.Browse); })); UI.nav(buttons_top, buttons_bottom); let canvas = document.createElement("canvas"); canvas.setAttribute("id", "game"); MAIN.appendChild(canvas); let params = {}; if(data !== null) { params = { rotate:data.rotate !== undefined? data.rotate : 0, }; } INTERFACE.init(data, INTERFACE.Mode.Local, params); if(data !== null) { for(let i = 0; i < data.turn; ++i) { INTERFACE_DATA.Game.history_begin.push(data.history[i]); } INTERFACE.reset(); } history.pushState(null, "Dzura - Practice", "/practice/"); return true; } unload() { INTERFACE.uninit(); } }, Users:class{ constructor() { this.page = 0; } preload() { this.refresh(); return true; } load(msg) { if(msg.code != OpCode.UserList) { return null; } if(sessionStorage.getItem("auth") === null) return false; CONTEXT.Data = { page:0, records:[], }; UI.mainmenu("browse"); UI.mainnav( [ ], [ UI.div([UI.text(LANG_PAGEOF(0, 0, 0))]), UI.button("◀", null), UI.button("▶", null), UI.button(LANG("refresh"), null), ], { auth:true, session:true } ); let table = document.createElement("table"); table.setAttribute("id", "content"); table.setAttribute("class", "list challenge"); // Insert table rows let rows = [ ]; for(let r = 0; r < msg.data.users.length; ++r) { let buttons = [ ]; let callback = function(event) { MESSAGE_CHALLENGE(this.handle); event.target.innerText = "Sent"; event.target.setAttribute("class", "highlight"); event.target.setAttribute("disabled", ""); }; callback = callback.bind({handle: msg.data.users[r].handle}); buttons.push(UI.button(LANG("challenge"), callback)); let handle = UI.text(msg.data.users[r].handle); if(!msg.data.users[r].is_online) { handle = UI.span([handle], "text-system"); } rows.push([ handle, UI.text(LANG("unranked")), buttons, ]); } let tbody = UI.table_content( [ LANG("handle"), LANG("rank"), "" ], rows, ); table.appendChild(tbody); UI.maincontent(table); history.pushState(null, "Dzura", "/challenge/"); return true; } refresh() { MESSAGE_COMPOSE([ PACK.u16(OpCode.UserList), ]); } disconnect() { SCENE.load(SCENES.Offline); } }, Requests:class{ constructor() { } preload() { this.refresh(); return true; } load(msg) { if(msg.code != OpCode.ChallengeList) { return null; } if(sessionStorage.getItem("auth") === null) return false; CONTEXT.Data = { page:0, records:[], }; UI.mainmenu("browse"); UI.mainnav( [ ], [ UI.div([UI.text(LANG_PAGEOF(0, 0, 0))]), UI.button("◀", null), UI.button("▶", null), UI.button(LANG("refresh"), null), ], { auth:true, session:true } ); let table = document.createElement("table"); table.setAttribute("id", "content"); table.setAttribute("class", "list challenge"); let rows = [ ]; for(let r = 0; r < msg.data.challenges.length; ++r) { let buttons = [ ]; let callback_accept = function() { MESSAGE_CHALLENGE_ANSWER(this.handle, true); }; callback_accept = callback_accept.bind({handle: msg.data.challenges[r].handle}); buttons.push(UI.button(LANG("accept"), callback_accept)); let callback_decline = function() { MESSAGE_CHALLENGE_ANSWER(this.handle, false); }; callback_decline = callback_decline.bind({handle: msg.data.challenges[r].handle}); buttons.push(UI.button(LANG("decline"), callback_decline)); rows.push([ UI.text(msg.data.challenges[r].handle), UI.text(LANG("unranked")), buttons, ]); } let tbody = UI.table_content( [ LANG("handle"), LANG("rank"), "" ], rows, ); table.appendChild(tbody); UI.maincontent(table); history.pushState(null, "Dzura", "/challenge/"); return true; } refresh() { MESSAGE_COMPOSE([ PACK.u16(OpCode.ChallengeList), ]); } message(msg) { switch(msg.code) { case OpCode.ChallengeList: { let table = document.getElementById("content"); UI.clear(table); let rows = [ ]; for(let r = 0; r < msg.data.challenges.length; ++r) { let buttons = [ ]; let callback_accept = function() { MESSAGE_CHALLENGE_ANSWER(this.handle, true); }; callback_accept = callback_accept.bind({handle: msg.data.challenges[r].handle}); buttons.push(UI.button(LANG("accept"), callback_accept)); let callback_decline = function() { MESSAGE_CHALLENGE_ANSWER(this.handle, false); }; callback_decline = callback_decline.bind({handle: msg.data.challenges[r].handle}); buttons.push(UI.button(LANG("decline"), callback_decline)); rows.push([ UI.text(msg.data.challenges[r].handle), UI.text(LANG("unranked")), buttons, ]); } let tbody = UI.table_content( [ LANG("handle"), LANG("rank"), "" ], rows, ); table.appendChild(tbody); } break; case OpCode.ChallengeAnswer: { if(msg.data.status == Status.Ok) { SCENE.load(SCENES.Game, { token:msg.data.token, mode:INTERFACE.Mode.Player, }); } else { this.refresh(); } } break; } } disconnect() { SCENE.load(SCENES.Offline); } }, };