0xa2c3bdc9…c101sent to0x3a6e049b…7741·#25,827,698·view on Etherscan
if (audioStarted) return;
var Ctx = (typeof window !== "undefined") && (window.AudioContext || window.webkitAudioContext);
if (!Ctx) return;
AC = new Ctx();
master = AC.createGain(); master.gain.value = muted ? 0 : 0.5; master.connect(AC.destination);
try { var b = AC.createBuffer(1, 1, AC.sampleRate); var s = AC.createBufferSource(); s.buffer = b; s.connect(AC.destination); s.start(0); } catch (e) {}
buildSeq();
audioStarted = true;
var begin = function () { stepIdx = 0; visT0 = nowSec(); nextStepTime = AC.currentTime + 0.06; };
begin();
if (AC.state === "suspended" && AC.resume) { try { AC.resume().then(begin).catch(function () {}); } catch (e) {} }
schedTimer = setInterval(scheduler, TICK_MS);
}
function resumeAudio() { if (AC && AC.state === "suspended" && AC.resume) { try { AC.resume(); } catch (e) {} } }
function scheduler() {
if (!AC || !seq) return;
if (AC.state !== "running") { resumeAudio(); return; }
if (nextStepTime < AC.currentTime) nextStepTime = AC.currentTime + 0.02;
while (nextStepTime < AC.currentTime + LOOKAHEAD) {
playStep(stepIdx, nextStepTime);
nextStepTime += seq.stepSeconds;
stepIdx = (stepIdx + 1) % seq.steps;
}
}
function blip(freq, time, dur, type, gain) {
var o = AC.createOscillator(), g = AC.createGain();
o.type = type; o.frequency.setValueAtTime(freq, time);
g.gain.setValueAtTime(0.0001, time);
g.gain.linearRampToValueAtTime(gain, time + 0.006);
g.gain.exponentialRampToValueAtTime(0.0001, time + dur);
o.connect(g); g.connect(master); o.start(time); o.stop(time + dur + 0.02);
}
function kick(time) {
var o = AC.createOscillator(), g = AC.createGain();
o.type = "sine";
o.frequency.setValueAtTime(150, time);
o.frequency.exponentialRampToValueAtTime(45, time + 0.12);
g.gain.setValueAtTime(0.6, time);
g.gain.exponentialRampToValueAtTime(0.0001, time + 0.15);
o.connect(g); g.connect(master); o.start(time); o.stop(time + 0.17);
}
function noiseBurst(time, dur, hp, gain) {
var n = Math.floor(AC.sampleRate * dur);
var buf = AC.createBuffer(1, n, AC.sampleRate), d = buf.getChannelData(0);
for (var i = 0; i < n; i++) d[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / n, 2);
var src = AC.createBufferSource(); src.buffer = buf;
var f = AC.createBiquadFilter(); f.type = "highpass"; f.frequency.value = hp;
var g = AC.createGain(); g.gain.value = gain;
src.connect(f); f.connect(g); g.connect(master); src.start(time);
}
function playStep(i, time) {
var w = (seq.waves && seq.waves[i]) || seq.leadWave || "square";
var L = seq.lead[i]; if (L != null) blip(midiToFreq(L), time, seq.stepSeconds * 0.85, w, (seq.vel && seq.vel[i]) || 0.16);
var B = seq.bass[i]; if (B != null) blip(midiToFreq(B), time, seq.stepSeconds * 0.85, seq.bassWave || "square", 0.24);
var p = seq.perc[i];
if (p & 2) kick(time);
if (p & 1) noiseBurst(time, 0.025, 7000, 0.14);
if (p & 8) noiseBurst(time, 0.11, 5000, 0.13);
if (p & 4) noiseBurst(time, 0.12, 1400, 0.30);
}
function toggleMute() { muted = !muted; if (master) master.gain.value = muted ? 0 : 0.5; }
function playChime(freq) {
if (!AC || AC.state !== "running") return;
var t = AC.currentTime;
blip(freq || 880, t, 0.10, "square", 0.18);
blip((freq || 880) * 1.5, t + 0.06, 0.12, "square", 0.16);
}
function playBoom(count, colorHex) {
if (!AC || AC.state !== "running") return;
var t = AC.currentTime;
noiseBurst(t, 0.18, 700, 0.30);
var m = metaFor(colorHex), f = midiToFreq(m.note);
var o = AC.createOscillator(), g = AC.createGain();
o.type = m.wave;
o.frequency.setValueAtTime(f, t);
o.frequency.exponentialRampToValueAtTime(Math.max(40, f * 0.45), t + 0.16);
g.gain.setValueAtTime(0.26, t);
g.gain.exponentialRampToValueAtTime(0.0001, t + 0.2);
o.connect(g); g.connect(master); o.start(t); o.stop(t + 0.22);
if (count >= 3) for (var k = 0; k < 3; k++) blip(f * Math.pow(2, (k / 12) * 2), t + 0.05 + k * 0.06, 0.09, m.wave, 0.16);
}
/* ============================ GAME LOGIC ============================ */
function enterDemo(now) { roundState = "demo"; score = 0; multUntil = 0; flashUntil = 0; lastAuto = now; }
function startRound(now) { score = 0; multUntil = 0; flashUntil = 0; roundState = "run"; roundStart = now; lastInteraction = now; }
function nextPiece() { curHash = E.makeHash(); grid = E.buildGrid(curHash); if (audioStarted) buildSeq(); }
function findMatchCell() {
var cols = grid.cols, rows = grid.rows;
function col(r, c) { return grid.cells[r * cols + c].creature.colors.stroke; }
for (var r = 0; r < rows; r++) { var run = 1; for (var c = 1; c < cols; c++) { if (col(r, c) === col(r, c - 1)) { if (++run >= 3) return r * cols + c; } else run = 1; } }
for (var c2 = 0; c2 < cols; c2++) { var run2 = 1; for (var r2 = 1; r2 < rows; r2++) { if (col(r2, c2) === col(r2 - 1, c2)) { if (++run2 >= 3) return r2 * cols + c2; } else run2 = 1; } }
return -1;
}
function autoPlayStep(now) {
if (!grid) return;
var idx = (Math.random() < 0.6) ? findMatchCell() : -1;
if (idx < 0) idx = (Math.random() * grid.cells.length) | 0;
if (grid.cells[idx].fx) return;
popCell(idx, (idx / grid.cols) | 0, idx % grid.cols, now);
}
function explodeSet(indices, now) { var out = []; for (var i = 0; i < indices.length; i++) if (E.explode(grid, indices[i], now)) out.push(indices[i]); return out; }
function popCell(idx, row, col, now) {
var cr = grid.cells[idx].creature, bonus = cr.bonus, tapColor = cr.colors.stroke;
var mult = (now < multUntil) ? 2 : 1;
if (bonus) {
var NAMES = { bomb: "BOMB", gold: "GOLD", time: "TIME", x2: "X2", rainbow: "COLOR" };
flashText = NAMES[bonus] || bonus.toUpperCase();
flashUntil = now + 1.3;
flashCol = (E.BONUS && E.BONUS[bonus] && E.BONUS[bonus].col) || "#ffffff";
}
var done = [], extra = 0, i;
if (bonus === "bomb") {
var set = [];
for (var c = 0; c < grid.cols; c++) set.push(row * grid.cols + c);
for (var r = 0; r < grid.rows; r++) set.push(r * grid.cols + col);
done = explodeSet(set, now);
} else if (bonus === "rainbow") {
var color = cr.colors.stroke, set2 = [];
for (i = 0; i < grid.cells.length; i++) if (grid.cells[i].creature.colors.stroke === color) set2.push(i);
done = explodeSet(set2, now);
} else if (bonus === "gold") {
done = E.explodeMatch(grid, idx, now); extra = 100;
} else if (bonus === "time") {
roundStart += 4; done = E.explodeMatch(grid, idx, now); playChime(880);
} else if (bonus === "x2") {
multUntil = now + 6; done = E.explodeMatch(grid, idx, now); playChime(1046);
} else {
done = E.explodeMatch(grid, idx, now); if (!done.length) return;
}
score += mult * (scoreForCells(done) + extra);
playBoom(done.length, tapColor);
}
/* ============================ INPUT ============================ */
function onPointer(ev) {
startAudio(); resumeAudio();
if (!grid) return;
var now = nowSec();
var rect = canvas.getBoundingClientRect();
var cx = (typeof ev.clientX === "number") ? ev.clientX : (ev.touches && ev.touches[0] ? ev.touches[0].clientX : 0);
var cy = (typeof ev.clientY === "number") ? ev.clientY : (ev.touches && ev.touches[0] ? ev.touches[0].clientY : 0);
var mx = (cx - rect.left) * (canvas.width / rect.width);
var my = (cy - rect.top) * (canvas.height / rect.height);
var bx = (mx - dispOX) / dispScale, by = (my - dispOY) / dispScale;
lastInteraction = now;
if (roundState !== "run") startRound(now);
if (bx < 0 || by < 0 || bx >= CW || by >= CH) return;
var cwN = grid.caw * E.BLK, chN = grid.cah * E.BLK;
var col = Math.floor(bx / cwN), row = Math.floor(by / chN);
if (col < 0 || col >= grid.cols || row < 0 || row >= grid.rows) return;
popCell(row * grid.cols + col, row, col, now);
}
function onKey(e) {
startAudio(); resumeAudio();
lastInteraction = nowSec();
var k = (e.key || "").toLowerCase();
if (k === "m") toggleMute();
else if (k === "n") { nextPiece(); enterDemo(nowSec()); }
else if (k === "r") startRound(nowSec());
else if (k === "s") downloadImage();
else if (k === "p") shareScore();
}
if (typeof addEventListener !== "undefined") {
addEventListener("pointerdown", onPointer);
addEventListener("keydown", onKey);
}
if (typeof document !== "undefined" && document.addEventListener)
document.addEventListener("visibilitychange", function () { if (!document.hidden) resumeAudio(); });
/* ============================ SHARE / SAVE ============================ */
function makeShareImage() {
var scale = 3, pad = 10, titleH = 44, scoreH = 60;
var gW = CW * scale, W = gW + pad * 2, H = titleH + CH * scale + scoreH + pad * 2;
var cv = document.createElement("canvas"); cv.width = W; cv.height = H;
var c2 = cv.getContext("2d");
c2.fillStyle = "#05060f"; c2.fillRect(0, 0, W, H);
drawText(c2, "PXL BITS", W / 2, pad + 14, 3, "#00e5ff", "center");
c2.imageSmoothingEnabled = false;
c2.drawImage(off, pad, titleH, gW, CH * scale);
drawText(c2, score + " BITS", W / 2, titleH + CH * scale + pad + 22, 6, "#ffe600", "center");
return cv;
}
function downloadURL(url, name) {
try { var a = document.createElement("a"); a.href = url; a.download = name; (document.body || document.documentElement).appendChild(a); a.click(); try { a.remove(); } catch (e) {} } catch (e) {}
}
function downloadImage() {
var cv = makeShareImage(), name = "pxl-bits-" + score + "bits.png";
if (cv.toBlob) cv.toBlob(function (b) { if (b) downloadURL(URL.createObjectURL(b), name); else downloadURL(cv.toDataURL("image/png"), name); }, "image/png");
else downloadURL(cv.toDataURL("image/png"), name);
}
function shareScore() {
var cv = makeShareImage(), name = "pxl-bits-" + score + "bits.png";
var text = "PXL BITS — I scored " + score + " BITS";
var withBlob = function (blob) {
if (typeof window !== "undefined" && typeof window.ARTSUNAMI_SHARE === "function") { try { window.ARTSUNAMI_SHARE(blob, text); return; } catch (e) {} }
try {
var file = new File([blob], name, { type: "image/png" });
if (navigator.share && navigator.canShare && navigator.canShare({ files: [file] })) {
navigator.share({ files: [file], title: "PXL BITS", text }).catch(function () { downloadURL(URL.createObjectURL(blob), name); });
return;
}
} catch (e) {}
downloadURL(URL.createObjectURL(blob), name);
};
if (cv.toBlob) cv.toBlob(function (b) { if (b) withBlob(b); else downloadURL(cv.toDataURL("image/png"), name); }, "image/png");
else downloadURL(cv.toDataURL("image/png"), name);
}
/* ============================ RENDER LOOP ============================ */
function render() {
var rawT = nowSec();
var t = audioStarted ? (rawT - visT0) : rawT;
E.paint(fb, grid, t, rawT);
var src = fb.d, px = imgData.data;
for (var i = 0, j = 0; i < src.length; i++, j += 4) {
var col = src[i];
if (col) { var r = rgbOf(col); px[j] = r[0]; px[j + 1] = r[1]; px[j + 2] = r[2]; }
else { px[j] = BG[0]; px[j + 1] = BG[1]; px[j + 2] = BG[2]; }
px[j + 3] = 255;
}
offctx.putImageData(imgData, 0, 0);
var W = canvas.width, H = canvas.height, s = W / CW, headerPx = HDR * s;
ctx.fillStyle = "#05060f"; ctx.fillRect(0, 0, W, H);
ctx.imageSmoothingEnabled = false;
ctx.drawImage(off, 0, headerPx, W, CH * s);
dispScale = s; dispOX = 0; dispOY = headerPx;
var now = rawT;
if (roundState === "demo") { if (now - lastAuto > AUTO_EVERY) { autoPlayStep(now); lastAuto = now; } }
else if (roundState === "run") {
if (now - lastInteraction > DEMO_RETURN) enterDemo(now);
else if (now - roundStart >= ROUND) { roundStart = now; score = 0; multUntil = 0; flashUntil = 0; }
}
var remain = roundState === "run" ? Math.max(0, ROUND - (now - roundStart)) : ROUND;
var timeLabel = "" + Math.ceil(remain), scoreLabel = "" + score;
var ps = Math.max(2, Math.floor(headerPx * 0.52 / 5)), cy = headerPx / 2, pad = Math.max(1, Math.round(2 * s));
if (roundState === "run") {
drawText(ctx, scoreLabel, pad, cy, ps, "#ffe600", "left", "#0b1280", 2);
drawText(ctx, timeLabel, W - pad, cy, ps, remain <= 10 ? "#ff2740" : "#ffffff", "right", "#0b1280", 2);
}
if (now < flashUntil) drawText(ctx, flashText, W / 2, cy, ps, flashCol, "center", "#0b1280", 2);
else if (roundState === "run" && now < multUntil) drawText(ctx, "X2", W / 2, cy, ps, "#ff17c6", "center", "#0b1280", 2);
else if (roundState === "demo") drawText(ctx, "PXL BITS", W / 2, cy, ps, "#ffe600", "center", "#0b1280", 2);
(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : function (f) { setTimeout(f, 16); })(render);
}
render();