Widget:IbNolandPuzzle
Revisão de 21h34min de 9 de abril de 2026 por Igor is back (discussão | contribs)
<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; max-width: 100%; height: auto; }
#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>