version with sound:
// CMXMTRIS v.1 — Wall Kicks Fiery Explosions Sound
// ↠→ move | ↓ soft drop | ↑ or W rotate (with wall kicks) | Space hard drop | Shift hold
(async () => {
const playfield = new Uint8Array(200);
let px = 3, py = 0, ptype = 0;
let matrix = [];
let hold = -1, canHold = true;
let score = 0, linesTotal = 0, level = 1;
let lastDrop = 0;
let dropSpeed = 600;
let keys = new Set();
let particles = [];
const shapes = [
[[1,1,1,1]], [[1,1],[1,1]], [[0,1,0],[1,1,1]],
[[0,1,1],[1,1,0]], [[1,1,0],[0,1,1]],
[[1,0,0],[1,1,1]], [[0,0,1],[1,1,1]]
];
const colors = ['
#00ffff', '
#0022ff', '
#00ff00', '
#ffff00', '
#ff8800', '
#0088ff', '
#ff0088'];
// Audio
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function playTone(freq, duration, type = 'sine', volume = 0.3) {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
gain.gain.value = volume;
osc.connect(gain).connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime duration);
}
document.documentElement.innerHTML = '';
const canvas = document.createElement('canvas');
canvas.width = innerWidth;
canvas.height = innerHeight;
canvas.style.cssText = 'position:fixed;inset:0;background:#000;image-rendering:pixelated';
document.documentElement.appendChild(canvas);
const ctx = canvas.getContext('2d', {alpha:false});
// Wall kick data (SRS simplified)
const wallKicks = [
[[0,0], [-1,0], [-1,1], [0,-2], [-1,-2]],
[[0,0], [1,0], [1,-1], [0,2], [1,2]],
[[0,0], [1,0], [1,1], [0,-2], [1,-2]],
[[0,0], [-1,0], [-1,-1],[0,2], [-1,2]]
];
function spawn() {
ptype = Math.floor(Math.random()*7);
matrix = shapes[ptype].map(r => r.slice());
px = 3; py = 0; canHold = true;
}
function draw() {
ctx.fillStyle = '
#0a0011';
ctx.fillRect(0,0,canvas.width,canvas.height);
const bs = Math.floor(Math.min(canvas.width/14, canvas.height/24));
const ox = canvas.width/2 - bs*5;
// grid
ctx.strokeStyle = '#112233';
ctx.lineWidth = 2;
for (let i=0;i<=10;i ) { ctx.beginPath(); ctx.moveTo(ox i*bs,bs*1.5); ctx.lineTo(ox i*bs,bs*1.5 bs*20); ctx.stroke(); }
for (let i=0;i<=20;i ) { ctx.beginPath(); ctx.moveTo(ox,bs*1.5 i*bs); ctx.lineTo(ox bs*10,bs*1.5 i*bs); ctx.stroke(); }
// locked blocks
for (let i=0;i<200;i ) if (playfield[i]) {
ctx.shadowBlur = 30;
ctx.shadowColor = colors[playfield[i]-1];
ctx.fillStyle = colors[playfield[i]-1];
ctx.fillRect(ox (i)*bs, bs*1.5 Math.floor(i/10)*bs, bs-2, bs-2);
}
// ghost
let gy = py;
while (!collides(px, gy 1, matrix)) gy ;
ctx.globalAlpha = 0.25;
ctx.shadowBlur = 35;
ctx.shadowColor = colors[ptype];
for (let y=0;y<matrix.length;y ) for (let x=0;x<matrix[y].length;x ) if (matrix[y][x]) {
ctx.fillRect(ox (px x)*bs, bs*1.5 (gy y)*bs, bs-2, bs-2);
}
ctx.globalAlpha = 1;
// current piece
ctx.shadowBlur = 50;
ctx.shadowColor = colors[ptype];
for (let y=0;y<matrix.length;y ) for (let x=0;x<matrix[y].length;x ) if (matrix[y][x]) {
ctx.fillStyle = colors[ptype];
ctx.fillRect(ox (px x)*bs, bs*1.5 (py y)*bs, bs-2, bs-2);
}
// fiery explosion particles
for (let i=particles.length-1;i>=0;i--) {
const p = particles[i];
ctx.shadowBlur = 25;
ctx.shadowColor = p.c;
ctx.fillStyle = p.c;
ctx.globalAlpha = p.a;
ctx.fillRect(p.x, p.y, 10, 10);
p.x = p.vx;
p.y = p.vy;
p.a -= 0.035;
p.vy = 0.45;
if (p.a <= 0) particles.splice(i,1);
}
ctx.globalAlpha = 1;
// HUD
ctx.fillStyle = '
#00ffff';
ctx.font = 'bold 34px monospace';
ctx.shadowBlur = 15;
ctx.shadowColor = '
#00ffff';
ctx.fillText('SCORE', 50, 90);
ctx.fillText(score.toString().padStart(7,'0'), 50, 145);
ctx.fillText('LEVEL', 50, 240);
ctx.fillText(level.toString(), 50, 290);
}
function collides(tx, ty, mat) {
for (let y=0;y<mat.length;y ) for (let x=0;x<mat[y].length;x ) if (mat[y][x]) {
const nx = tx x, ny = ty y;
if (nx<0||nx>=10||ny>=20) return true;
if (ny<0) continue;
if (playfield[ny*10 nx]) return true;
}
return false;
}
function tryRotate() {
const rotated = rotateMatrix(matrix);
const kicks = wallKicks[ptype % 4];
for (let [kx, ky] of kicks) {
if (!collides(px kx, py ky, rotated)) {
px = kx;
py = ky;
matrix = rotated;
return true;
}
}
return false;
}
function lock() {
for (let y=0;y<matrix.length;y ) for (let x=0;x<matrix[y].length;x ) if (matrix[y][x]) {
const idx = (py y)*10 (px x);
if (idx >= 0 && idx < 200) playfield[idx] = ptype 1;
}
clearLines();
spawn();
}
function clearLines() {
let cleared = 0;
let y = 19;
while (y >= 0) {
if (playfield.slice(y*10, y*10 10).every(v => v)) {
cleared ;
// Fiery explosion
for (let i=0;i<10;i ) {
for (let k=0;k<45;k ) {
particles.push({
x: canvas.width/2 - 140 i*28 Math.random()*40,
y: 90 y*28 Math.random()*30,
vx: (Math.random()-0.5)*32,
vy: -Math.random()*25 - 8,
a: 1.4,
c: Math.random() > 0.6 ? '
#ff8800' : '
#ffff00'
});
}
}
for (let r=y; r>0; r--) playfield.set(playfield.slice((r-1)*10, r*10), r*10);
playfield.set(new Uint8Array(10), 0);
} else y--;
}
if (cleared) {
score = cleared * 200 * level;
linesTotal = cleared;
level = Math.floor(linesTotal / 7) 1;
playTone(800 cleared * 300, 0.18, 'sawtooth', 0.5);
}
}
function loop(t) {
if (!lastDrop) lastDrop = t;
const dt = t - lastDrop;
if (keys.has('ArrowLeft') && !collides(px-1,py,matrix)) { px--; keys.delete('ArrowLeft'); playTone(600, 0.02); }
if (keys.has('ArrowRight') && !collides(px 1,py,matrix)) { px ; keys.delete('ArrowRight'); playTone(600, 0.02); }
if (keys.has('ArrowDown')) if (!collides(px,py 1,matrix)) { py ; playTone(400, 0.01); }
if (keys.has('ArrowUp') || keys.has('w')) {
if (tryRotate()) playTone(900, 0.06, 'square');
keys.delete('ArrowUp'); keys.delete('w');
}
if (keys.has('Shift') && canHold) {
if (hold === -1) { hold = ptype; spawn(); }
else { [ptype, hold] = [hold, ptype]; matrix = shapes[ptype].map(r=>r.slice()); px=3; py=0; }
canHold = false;
keys.delete('Shift');
playTone(700, 0.08);
}
if (keys.has(' ')) {
while (!collides(px,py 1,matrix)) py ;
lock();
keys.delete(' ');
playTone(300, 0.12, 'sawtooth');
}
if (dt > dropSpeed / level) {
if (!collides(px,py 1,matrix)) py ;
else lock();
lastDrop = t;
}
draw();
requestAnimationFrame(loop);
}
function rotateMatrix(mat) {
const rows = mat.length;
const cols = mat[0].length;
const rotated = Array.from({length: cols}, () => Array(rows).fill(0));
for (let i = 0; i < rows; i ) {
for (let j = 0; j < cols; j ) {
rotated[j][rows - 1 - i] = mat[i][j];
}
}
return rotated;
}
document.addEventListener('keydown', e => keys.add(e.key));
document.addEventListener('keyup', e => keys.delete(e.key));
spawn();
requestAnimationFrame(loop);
console.log('ÌMXMTRIS v.1 loaded — Wall Kicks Fiery Explosions Sound', 'color:
#00ffff;font-size:20px;font-family:monospace');
})();