| 1 | <!doctype html> |
| 2 | <html lang="en"> |
| 3 | <head> |
| 4 | <meta charset="utf-8"> |
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> |
| 6 | <title>ARC-AGI-3 replay</title> |
| 7 | <style> |
| 8 | |
| 9 | |
| 10 | html, body { margin: 0; height: 100%; background: #0d0d0d; color: #e6e6e6; font: 13px ui-monospace, Menlo, Consolas, monospace; } |
| 11 | body { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; } |
| 12 | .head { display: flex; gap: 18px; align-items: baseline; } |
| 13 | .head b { font-size: 16px; letter-spacing: .08em; } |
| 14 | canvas { image-rendering: pixelated; width: min(78vh, 92vw); height: min(78vh, 92vw); background: #000; } |
| 15 | .ctl { display: flex; gap: 8px; align-items: center; } |
| 16 | button { font: inherit; color: #e6e6e6; background: #222; border: 1px solid #444; border-radius: 4px; padding: 3px 10px; cursor: pointer; } |
| 17 | input[type=range] { width: 300px; } |
| 18 | </style> |
| 19 | </head> |
| 20 | <body> |
| 21 | <div class="head"><b id="title">ARC-AGI-3</b><span id="lv"></span><span id="act"></span></div> |
| 22 | <canvas id="c" width="64" height="64"></canvas> |
| 23 | <div class="ctl"><button id="play">Pause</button><input id="at" type="range" min="0" value="0" aria-label="Action"><span id="n"></span></div> |
| 24 | <script src="bridge.js"></script> |
| 25 | <script> |
| 26 | const PALETTE = ['#FFFFFF', '#CCCCCC', '#999999', '#666666', '#333333', '#000000', '#E53AA3', '#FF7BCC', '#F93C31', '#1E93FF', '#88D8F1', '#FFDC00', '#FF851B', '#921231', '#4FCC30', '#A356D6'] |
| 27 | const RGB = PALETTE.map(h => [1, 3, 5].map(i => parseInt(h.slice(i, i + 2), 16))) |
| 28 | const NAME = ['RESET', 'UP', 'DOWN', 'LEFT', 'RIGHT', 'INTERACT', 'CLICK', 'UNDO'] |
| 29 | const cv = document.getElementById('c'), g = cv.getContext('2d'), img = g.createImageData(64, 64), at = document.getElementById('at') |
| 30 | let R = null, t = 0, timer = 0 |
| 31 | function draw() { |
| 32 | const s = R.steps[t], f = s.f |
| 33 | for (let i = 0; i < 4096; i++) { const c = RGB[parseInt(f[i] || '5', 16)]; img.data.set([c[0], c[1], c[2], 255], i * 4) } |
| 34 | g.putImageData(img, 0, 0) |
| 35 | if (s.s === 'GAME_OVER' || s.s === 'WIN') { g.fillStyle = s.s === 'WIN' ? 'rgba(79,204,48,.45)' : 'rgba(249,60,49,.45)'; g.fillRect(0, 0, 64, 64) } |
| 36 | const act = typeof s.a === 'number' ? NAME[s.a] || `ACTION${s.a}` : String(s.a) |
| 37 | if ((act === 'ACTION6' || act === 'CLICK') && s.d && s.d.x != null) { g.fillStyle = '#FFDC00'; g.fillRect(s.d.x, s.d.y, 1, 1) } |
| 38 | document.getElementById('lv').textContent = `level ${s.l} / ${R.levels}` |
| 39 | document.getElementById('act').textContent = t ? act : 'start' |
| 40 | document.getElementById('n').textContent = `action ${t} / ${R.steps.length - 1}` |
| 41 | at.value = t |
| 42 | } |
| 43 | function go(n) { t = Math.max(0, Math.min(R.steps.length - 1, n)); draw() } |
| 44 | function run() { timer = setInterval(() => (t >= R.steps.length - 1 ? stop() : go(t + 1)), Math.max(60, 1000 / (R.fps || 10))); document.getElementById('play').textContent = 'Pause' } |
| 45 | function stop() { clearInterval(timer); timer = 0; document.getElementById('play').textContent = 'Play' } |
| 46 | document.getElementById('play').onclick = () => (timer ? stop() : run()) |
| 47 | at.oninput = () => { stop(); go(+at.value) } |
| 48 | shoalReplay(text => { R = JSON.parse(text); document.getElementById('title').textContent = R.title; at.max = R.steps.length - 1; go(0); run() }) |
| 49 | </script> |
| 50 | </body> |
| 51 | </html> |