juegos

Juegos de terminal de Pancho: Catan (TUI, GUI, web, servidor, PicoCalc), ajedrez, calculadora y minijuegos
git clone https://git.lu3dhn.xyz/juegos.git
Log | Files | Refs

shell.html (7889B)


      1 <!doctype html>
      2 <!-- shell.html - pagina de la version web de Catan (emscripten la completa con el
      3      script del modulo en {{{ SCRIPT }}}). El canvas llena la pantalla; la app en C
      4      decide el layout (ancho o vertical) segun la forma que le queda. -->
      5 <html lang="es">
      6 <head>
      7 <meta charset="utf-8">
      8 <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no">
      9 <meta name="theme-color" content="#1c5696">
     10 <meta name="apple-mobile-web-app-capable" content="yes">
     11 <meta name="mobile-web-app-capable" content="yes">
     12 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
     13 <meta name="apple-mobile-web-app-title" content="Catan">
     14 <link rel="manifest" href="manifest.json">
     15 <link rel="icon" href="icon-192.png">
     16 <link rel="apple-touch-icon" href="icon-192.png">
     17 <title>Catan</title>
     18 <style>
     19   html, body { margin: 0; padding: 0; height: 100%; background: #1c5696; overflow: hidden; overscroll-behavior: none; }
     20   body { font-family: system-ui, sans-serif; color: #e6e6e6; }
     21   #canvas { display: block; width: 100vw; height: 100dvh; border: 0; padding: 0; margin: 0; touch-action: none;
     22             image-rendering: pixelated; outline: none; }
     23   #status { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center;
     24             flex-direction: column; gap: 12px; background: #1c5696; z-index: 5; }
     25   #status.hidden { display: none; }
     26   #status p { margin: 0; opacity: .8; }
     27   /* entrada de texto (chat, nombre, contrasena): el navegador no muestra el teclado
     28      tactil para el canvas, asi que se usa un campo HTML encima */
     29   #gk-input { position: fixed; left: 0; right: 0; top: 0; padding: 10px; background: #222630; z-index: 1000;
     30               display: none; box-shadow: 0 2px 8px rgba(0,0,0,.5); }
     31   #gk-input.show { display: block; }
     32   #gk-input label { display: block; font-size: 14px; margin-bottom: 6px; color: #ffc83c; }
     33   #gk-input .row { display: flex; gap: 8px; }
     34   #gk-input input { flex: 1; font-size: 18px; padding: 8px; border: 1px solid #8c92a0; border-radius: 4px;
     35                     background: #303644; color: #fff; min-width: 0; }
     36   #gk-input button { font-size: 16px; padding: 8px 14px; border: 0; border-radius: 4px; background: #ffc83c; color: #181410; }
     37   #gk-input button.cancel { background: #8c92a0; }
     38 </style>
     39 </head>
     40 <body>
     41 <div id="status"><p id="status-text">Cargando Catan…</p></div>
     42 <div id="gk-input">
     43   <label id="gk-label" for="gk-field"></label>
     44   <div class="row">
     45     <input id="gk-field" type="text" autocomplete="off" autocapitalize="off" enterkeyhint="done" placeholder="tocá acá para escribir">
     46     <button id="gk-ok">OK</button>
     47     <button id="gk-cancel" class="cancel">✕</button>
     48   </div>
     49 </div>
     50 <canvas id="canvas" tabindex="-1" oncontextmenu="event.preventDefault()"></canvas>
     51 <script>
     52   // Si otra pestana tiene el candado, no se arranca el modulo (ver mas abajo)
     53   var _origLoad = null;
     54   // ---- entrada de texto para la app en C (gk_text_input en comun/linux/guikit.c)
     55   var gkText = { done: 0, value: '' };
     56   var gkBox = document.getElementById('gk-input'), gkField = document.getElementById('gk-field');
     57   function gkTextOpen(title, init, max) {
     58     // SDL (FILL_DOCUMENT) esconde en un div oculto todo lo que habia en el body al crear
     59     // la ventana: el cuadro se vuelve a colgar del body para que se vea encima del canvas
     60     if (gkBox.parentNode !== document.body) document.body.appendChild(gkBox);
     61     document.getElementById('gk-label').textContent = title;
     62     gkField.value = init;
     63     gkField.maxLength = max > 0 ? max : 200;
     64     gkField.type = /contrase|password/i.test(title) ? 'password' : 'text';
     65     gkText.done = 0;
     66     gkBox.classList.add('show');
     67     gkField.focus();
     68     gkField.select();
     69     setTimeout(function () { if (gkText.done === 0) gkField.focus(); }, 50);   // por si el canvas se lo queda
     70   }
     71   // mientras el cuadro esta abierto, el foco vuelve al campo si algo se lo lleva
     72   gkField.addEventListener('focusout', function () {
     73     if (gkText.done === 0 && gkBox.classList.contains('show')) setTimeout(function () { gkField.focus(); }, 0);
     74   });
     75   function gkTextClose(ok) {
     76     gkText.value = gkField.value;
     77     gkText.done = ok ? 1 : -1;
     78     gkBox.classList.remove('show');
     79     gkField.blur();
     80   }
     81   function gkTextDone() { return gkText.done; }
     82   function gkTextValue() { return gkText.value; }
     83   document.getElementById('gk-ok').onclick = function () { gkTextClose(true); };
     84   document.getElementById('gk-cancel').onclick = function () { gkTextClose(false); };
     85   gkField.addEventListener('keydown', function (e) {
     86     if (e.key === 'Enter') { e.preventDefault(); gkTextClose(true); }
     87     else if (e.key === 'Escape') { e.preventDefault(); gkTextClose(false); }
     88     e.stopPropagation();          // que SDL no vea estas teclas
     89   });
     90   gkField.addEventListener('keyup', function (e) { e.stopPropagation(); });
     91   gkField.addEventListener('keypress', function (e) { e.stopPropagation(); });
     92 
     93   // ---- el modulo emscripten
     94   var wsParam = new URLSearchParams(location.search).get('ws');
     95   var Module = {
     96     noStart: false,
     97     // el candado de instancia se pide arriba de forma asincronica: antes de correr
     98     // main se espera a saber si lo tenemos
     99     preInit: [function () {
    100       addRunDependency('lock');
    101       var tries = 0;
    102       (function wait() {
    103         if (Module.noStart) return;                  // nunca se libera: la pagina queda en el aviso
    104         if (Module.lockDone || !(navigator.locks && navigator.locks.request) || ++tries > 50) removeRunDependency('lock');
    105         else setTimeout(wait, 20);
    106       })();
    107     }],
    108     canvas: document.getElementById('canvas'),
    109     // los sockets BSD del programa van por este WebSocket: /ws de la misma pagina
    110     // (Caddy lo pasa al catan-server), o ?ws=ws://localhost:7373 para probar local
    111     websocket: {
    112       url: wsParam || ((location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + '/ws'),
    113       subprotocol: 'binary'
    114     },
    115     setStatus: function (t) {
    116       var el = document.getElementById('status-text');
    117       if (el) el.textContent = t || '';
    118     },
    119     onRuntimeInitialized: function () { document.getElementById('status').classList.add('hidden'); },
    120     onAbort: function (what) {
    121       var st = document.getElementById('status');
    122       if (st.parentNode !== document.body) document.body.appendChild(st);
    123       Module.setStatus('Error: ' + what);
    124       st.classList.remove('hidden');
    125     },
    126     printErr: function (t) { console.warn(t); }
    127   };
    128   if ('serviceWorker' in navigator && location.protocol === 'https:') {
    129     // la app sale de la cache (cache-first); cuando una version nueva toma el control,
    130     // recargar si recien se abrio (todavia en el menu), para no cortar una partida.
    131     // En la primera visita no habia controlador: ahi el SW nuevo no es una actualizacion.
    132     var hadSW = !!navigator.serviceWorker.controller;
    133     navigator.serviceWorker.register('sw.js').catch(function (e) { console.warn('sw', e); });
    134     navigator.serviceWorker.addEventListener('controllerchange', function () {
    135       if (hadSW && performance.now() < 15000) location.reload();
    136     });
    137   }
    138   // Una sola instancia: dos pestanas (o la app instalada y una pestana) tienen cada una
    139   // su copia de /data y la que guarda ultima pisa a la otra. Con Web Locks la segunda
    140   // no arranca; en navegadores sin locks, arranca igual.
    141   if (navigator.locks && navigator.locks.request) {
    142     navigator.locks.request('catan', { ifAvailable: true }, function (lock) {
    143       if (lock) { Module.lockDone = true; return new Promise(function () {}); }   // se tiene mientras viva la pagina
    144       document.getElementById('status-text').textContent = 'Catan ya está abierto en otra pestaña o ventana. Cerrá esa y recargá esta.';
    145       Module.noStart = true;
    146       return null;
    147     });
    148   }
    149 </script>
    150 {{{ SCRIPT }}}
    151 </body>
    152 </html>