Mudanças entre as edições de "Widget:IbNolandPuzzle"

De Wiki Gla
Ir para navegação Ir para pesquisar
Linha 15: Linha 15:
<style>
<style>
   #ib-game-container { position: relative; display: inline-block; max-width: 100vw; }
   #ib-game-container { position: relative; display: inline-block; max-width: 100vw; }
   #ib-game-container canvas { display: block; image-rendering: pixelated; max-width: 100%; height: auto; }
   #ib-game-container canvas { display: block; image-rendering: pixelated; }
  #ib-gameCanvas { max-width: 100%; height: auto; }
 
  #ib-game-container:fullscreen,
  #ib-game-container:-webkit-full-screen {
    display: flex;
    width: 100vw;
    height: 100vh;
    align-items: center;
    justify-content: center;
    background: #000;
  }
 
  #ib-game-container:fullscreen #ib-gameCanvas,
  #ib-game-container:-webkit-full-screen #ib-gameCanvas {
    width: auto;
    height: 100vh;
    max-width: 100vw;
  }
 
   #ib-mobile-controls {
   #ib-mobile-controls {
     display: none; position: absolute; bottom: 0; left: 0;
     display: none; position: absolute; bottom: 0; left: 0;

Edição das 21h39min de 9 de abril de 2026

   <button id="ib-play-btn">Play</button>

You Win

 <canvas id="ib-gameCanvas"></canvas>
   <canvas id="ib-joystick-canvas" width="192" height="192"></canvas>
   <canvas id="ib-action-sprite" width="128" height="128"></canvas>

<style>

 #ib-game-container { position: relative; display: inline-block; max-width: 100vw; }
 #ib-game-container canvas { display: block; image-rendering: pixelated; }
 #ib-gameCanvas { max-width: 100%; height: auto; }
 #ib-game-container:fullscreen,
 #ib-game-container:-webkit-full-screen {
   display: flex;
   width: 100vw;
   height: 100vh;
   align-items: center;
   justify-content: center;
   background: #000;
 }
 #ib-game-container:fullscreen #ib-gameCanvas,
 #ib-game-container:-webkit-full-screen #ib-gameCanvas {
   width: auto;
   height: 100vh;
   max-width: 100vw;
 }
 #ib-mobile-controls {
   display: none; position: absolute; bottom: 0; left: 0;
   width: 100%; justify-content: space-between; align-items: flex-end;
   padding: 16px; box-sizing: border-box; pointer-events: none;
 }
 #ib-joystick-canvas, #ib-action-sprite { pointer-events: all; opacity: 0.8; }
 #ib-start-screen {
   position: absolute; inset: 0; display: flex;
   justify-content: center; align-items: center;
   background: #1a1a2e; z-index: 10;
 }
 #ib-end-screen {
   position: absolute; inset: 0; display: none;
   justify-content: center; align-items: center;
   background: rgba(0,0,0,0.85); z-index: 10;
   color: #e2e2e2; font-size: 64px; letter-spacing: 8px;
 }
 #ib-play-btn {
   padding: 16px 48px; font-size: 24px; cursor: pointer;
   background: #16213e; color: #e2e2e2;
   border: 2px solid #e2e2e2; letter-spacing: 4px;
 }

</style>

<script> (function() {

 const COLS = 7, ROWS = 5, TILE_SIZE = 128;
 const canvas = document.getElementById('ib-gameCanvas');
 const ctx = canvas.getContext('2d');
 canvas.width = COLS * TILE_SIZE;
 canvas.height = ROWS * TILE_SIZE;
 const DIRS = {
   ArrowRight: [1,  0, 0],
   ArrowLeft:  [-1, 0, 1],
   ArrowUp:    [0, -1, 2],
   ArrowDown:  [0,  1, 3]
 };
 const KEY_MAP = { w: 'ArrowUp', a: 'ArrowLeft', s: 'ArrowDown', d: 'ArrowRight' };
 const MOVE_DURATION = 24;
 const DIAG_DURATION = Math.round(MOVE_DURATION * Math.SQRT2);
 const DIAG = [
   ['ArrowUp',   'ArrowLeft',  -1, -1, 2],
   ['ArrowDown', 'ArrowLeft',  -1,  1, 1],
   ['ArrowDown', 'ArrowRight',  1,  1, 0],
   ['ArrowUp',   'ArrowRight',  1, -1, 2],
 ];
 const keys = {};
 const keyTimes = {};
 const DIR_DX = [1, -1, 0, 0];
 const DIR_DY = [0,  0, -1, 1];
 function keyActive(k) { return keys[k] || performance.now() - (keyTimes[k] || 0) < 120; }
 document.addEventListener('keydown', e => {
   const key = KEY_MAP[e.key] || e.key;
   if (key === '1') { player.interact(); return; }
   if (!(key in DIRS)) return;
   e.preventDefault();
   if (e.ctrlKey) { player.direction = DIRS[key][2]; return; }
   keyTimes[key] = performance.now(); keys[key] = true;
 });
 document.addEventListener('keyup', e => { keys[KEY_MAP[e.key] || e.key] = false; });
 const OBJECT_POS = [[1,0],[3,0],[5,0],[6,2],[5,4],[3,4],[1,4],[0,2]];
 const ARTIFACT_COL = 3, ARTIFACT_ROW = 2;
 const OBJECT_TILES = new Set([...OBJECT_POS.map(([c,r]) => c * 10 + r), ARTIFACT_COL * 10 + ARTIFACT_ROW]);
 const mainSprite = new Image();
 mainSprite.src = 'https://wiki.gla.com.br/images/3/30/Ib_noland_sprite.png';
 const tileImage = new Image();
 tileImage.src = 'https://wiki.gla.com.br/images/9/98/Ib_basic_tile.png';
 const spriteCache = new Map();
 function getSprite(col, row) {
   const key = col * 10 + row;
   if (spriteCache.has(key)) return spriteCache.get(key);
   const c = document.createElement('canvas');
   c.width = c.height = TILE_SIZE;
   c.getContext('2d').drawImage(mainSprite, col * TILE_SIZE, row * TILE_SIZE, TILE_SIZE, TILE_SIZE, 0, 0, TILE_SIZE, TILE_SIZE);
   spriteCache.set(key, c); return c;
 }
 function bakeSprites() {
   for (let c = 0; c < 5; c++) for (let r = 0; r < 6; r++) getSprite(c, r);
 }
 class Character {
   constructor(tileX, tileY) {
     this.tileX = tileX; this.tileY = tileY;
     this.x = tileX * TILE_SIZE; this.y = tileY * TILE_SIZE;
     this.prevX = this.x; this.prevY = this.y;
     this.direction = 0; this.animFrame = 0;
     this.moving = false; this.moveFrame = 0;
     this.interactFrame = 0; this.cooldown = 0;
   }
   _startMove(dx, dy, dir, duration = MOVE_DURATION) {
     const nx = this.tileX + dx, ny = this.tileY + dy;
     if (nx < 0 || nx >= COLS || ny < 0 || ny >= ROWS || OBJECT_TILES.has(nx * 10 + ny)) return false;
     this.direction = dir; this.tileX = nx; this.tileY = ny;
     this.startX = this.x; this.startY = this.y;
     this.targetX = nx * TILE_SIZE; this.targetY = ny * TILE_SIZE;
     this.moving = true; this.moveFrame = 0; this.moveDuration = duration;
     return true;
   }
   tryMove() {
     for (let i = 0; i < DIAG.length; i++) {
       const [k1, k2, dx, dy, dir] = DIAG[i];
       if (keyActive(k1) && keyActive(k2) && this._startMove(dx, dy, dir, DIAG_DURATION)) return;
     }
     for (const key in DIRS)
       if (keys[key] && this._startMove(...DIRS[key])) break;
   }
   _finishMove() {
     this.x = this.targetX; this.y = this.targetY;
     this.moving = false; this.animFrame = 0;
   }
   interact() {
     if (this.cooldown > 0 || this.moving || this.interactFrame > 0) return;
     const dx = DIR_DX[this.direction], dy = DIR_DY[this.direction];
     const tx = this.tileX + dx, ty = this.tileY + dy;
     if (tx === ARTIFACT_COL && ty === ARTIFACT_ROW) { if (artifact.open) endGame(); return; }
     this.interactFrame = 1; this.cooldown = 60;
     triggerPedestal(tx, ty);
   }
   _updateMove() {
     this.moveFrame++;
     const t = this.moveFrame / this.moveDuration;
     this.x = this.startX + (this.targetX - this.startX) * t;
     this.y = this.startY + (this.targetY - this.startY) * t;
     this.animFrame = (Math.floor(this.moveFrame / 8) % 2) + 1;
     if (this.moveFrame >= this.moveDuration) this._finishMove();
   }
   update() {
     this.prevX = this.x; this.prevY = this.y;
     if (this.cooldown > 0) this.cooldown--;
     if (this.interactFrame > 0 && ++this.interactFrame > 24) this.interactFrame = 0;
     if (this.interactFrame > 0) return;
     if (!this.moving) { this.tryMove(); return; }
     this._updateMove();
   }
   draw(ctx, alpha) {
     const { direction: d, animFrame: f, interactFrame: i } = this;
     const rx = this.prevX + (this.x - this.prevX) * alpha;
     const ry = this.prevY + (this.y - this.prevY) * alpha;
     ctx.drawImage(getSprite(i > 0 ? 3 : f, d), Math.round(rx), Math.round(ry));
   }
 }
 class Artifact {
   constructor() { this.tileX = ARTIFACT_COL; this.tileY = ARTIFACT_ROW; this.open = false; }
   draw(ctx) {
     ctx.drawImage(getSprite(this.open ? 1 : 0, 4), this.tileX * TILE_SIZE, this.tileY * TILE_SIZE);
   }
 }
 class GameObject {
   constructor(tileX, tileY) { this.tileX = tileX; this.tileY = tileY; this.active = false; }
   draw(ctx) {
     const col = this.active ? (Math.floor(pedestalTick / 8) % 4 + 1) : 0;
     ctx.drawImage(getSprite(col, 5), this.tileX * TILE_SIZE, this.tileY * TILE_SIZE);
   }
 }
 const offscreen = document.createElement('canvas');
 offscreen.width = canvas.width;
 offscreen.height = canvas.height;
 const offCtx = offscreen.getContext('2d');
 const artifact = new Artifact();
 const player = new Character(3, 3);
 const objects = OBJECT_POS.map(([c,r]) => new GameObject(c, r));
 function bakeBackground() {
   for (let r = 0; r < ROWS; r++)
     for (let c = 0; c < COLS; c++)
       offCtx.drawImage(tileImage, c * TILE_SIZE, r * TILE_SIZE, TILE_SIZE, TILE_SIZE);
 }
 function drawStats() {
   ctx.font = '16px monospace';
   ctx.fillStyle = '#fff';
   ctx.fillText(`FPS: ${fps}  UPS: ${ups}`, 8, 20);
 }
 function render(alpha) {
   ctx.drawImage(offscreen, 0, 0);
   artifact.draw(ctx);
   for (let i = 0; i < objects.length; i++) objects[i].draw(ctx);
   player.draw(ctx, alpha);
   drawStats();
 }
 const MS_PER_UPDATE = 1000 / 60;
 let lastTime = 0, lag = 0, pedestalTick = 0;
 let fps = 0, ups = 0, fCount = 0, uCount = 0, statTime = 0;
 function gameLoop(timestamp) {
   lag += Math.min(timestamp - lastTime, 100);
   lastTime = timestamp;
   while (lag >= MS_PER_UPDATE) { player.update(); pedestalTick++; uCount++; lag -= MS_PER_UPDATE; }
   fCount++;
   if (timestamp - statTime >= 1000) { fps = fCount; ups = uCount; fCount = 0; uCount = 0; statTime = timestamp; }
   render(lag / MS_PER_UPDATE);
   requestAnimationFrame(gameLoop);
 }
 function triggerPedestal(col, row) {
   if (artifact.open) return;
   const idx = objects.findIndex(o => o.tileX === col && o.tileY === row);
   if (idx === -1) return;
   const n = objects.length;
   [idx, (idx + 1) % n, (idx + n - 1) % n].forEach(i => { objects[i].active = !objects[i].active; });
   if (objects.every(o => !o.active)) artifact.open = true;
 }
 function endGame() {
   document.getElementById('ib-end-screen').style.display = 'flex';
   setTimeout(() => location.reload(), 2000);
 }
 function randomizeObjects() {
   objects.forEach(o => { o.active = Math.random() < 0.5; });
   if (objects.every(o => !o.active)) objects[0].active = true;
 }
 function isMobile() { return navigator.maxTouchPoints > 0; }
 let joyX = 0, joyY = 0;
 function setJoystickKeys(dx, dy) {
   keys.ArrowRight = keys.ArrowLeft = keys.ArrowUp = keys.ArrowDown = false;
   if (Math.hypot(dx, dy) < 20) return;
   const a = Math.atan2(dy, dx), s = Math.PI / 8;
   keys.ArrowRight = a > -3*s && a < 3*s;
   keys.ArrowLeft  = a >  5*s || a < -5*s;
   keys.ArrowDown  = a >    s && a <  7*s;
   keys.ArrowUp    = a <   -s && a > -7*s;
 }
 function drawJoystick(ox, oy) {
   const c = document.getElementById('ib-joystick-canvas');
   const jc = c.getContext('2d');
   jc.clearRect(0, 0, 192, 192);
   jc.drawImage(getSprite(1, 7), 32 + ox, 32 + oy);
 }
 function onJoystickStart(e) { e.preventDefault(); joyX = e.touches[0].clientX; joyY = e.touches[0].clientY; drawJoystick(0, 0); }
 function onJoystickMove(e) {
   e.preventDefault();
   const dx = e.touches[0].clientX - joyX, dy = e.touches[0].clientY - joyY;
   const dist = Math.hypot(dx, dy), maxR = 40, s = dist > maxR ? maxR / dist : 1;
   setJoystickKeys(dx, dy); drawJoystick(dx * s, dy * s);
 }
 function onJoystickEnd(e) { e.preventDefault(); keys.ArrowRight = keys.ArrowLeft = keys.ArrowUp = keys.ArrowDown = false; drawJoystick(0, 0); }
 function onActionStart(e) { e.preventDefault(); player.interact(); }
 function addJoystickListeners(el) {
   el.addEventListener('touchstart', onJoystickStart, { passive: false });
   el.addEventListener('touchmove',  onJoystickMove,  { passive: false });
   el.addEventListener('touchend',   onJoystickEnd,   { passive: false });
 }
 function drawMobileSprites() {
   drawJoystick(0, 0);
   document.getElementById('ib-action-sprite').getContext('2d').drawImage(getSprite(0, 7), 0, 0);
 }
 function initMobileControls() {
   if (!isMobile()) return;
   document.getElementById('ib-mobile-controls').style.display = 'flex';
   drawMobileSprites();
   addJoystickListeners(document.getElementById('ib-joystick-canvas'));
   document.getElementById('ib-action-sprite').addEventListener('touchstart', onActionStart, { passive: false });
 }
 let started = false, playClicked = false;
 function tryStart() {
   if (started || !playClicked || !tileImage.complete || !mainSprite.complete) return;
   started = true;
   bakeSprites(); bakeBackground(); initMobileControls();
   lastTime = performance.now();
   requestAnimationFrame(gameLoop);
 }
 tileImage.onload = tryStart;
 mainSprite.onload = tryStart;
 document.getElementById('ib-play-btn').addEventListener('click', () => {
   document.getElementById('ib-start-screen').style.display = 'none';
   if (isMobile()) {
     document.getElementById('ib-game-container').requestFullscreen().catch(() => {});
     screen.orientation?.lock('landscape').catch(() => {});
   }
   randomizeObjects(); playClicked = true; tryStart();
 });

})(); </script>