(???) / TODO rotate board instead of tetromino?
 (???) onst blocks =
 (???)    'defgaeimdefgaeimdefjaeibdhijibfjdhefaeijhifjabfjeifjeifj' +
 (???)    'eifjeifjheifaefjheifaefjdeifaeifheijebfjdeijeibfdeijeibf'
 (???) onst randomBlock = () => new Date() % 7
 (???) onst [W, H] = [10, 25]
 (???) et score = 0, lines = 0, block = 0, rotation = 0,
 (???)    next = randomBlock(),
 (???)    X = 3, Y = 0,
 (???)    timer = -1
 (???) onst Bucket = document.getElementById('t')
 (???) onst NextBucket = document.getElementById('n')
 (???) onst addRow = (bucket, width) => {
 (???)    for (let r = bucket.insertRow(0), x = width; x--;) {
 (???)        r.insertCell(0).appendChild(document.createTextNode('\u00a0'))
 (???)    }
 (???) 
 (???) onst iterate = (block, rotation, callback) => {
 (???)    // TODO yield
 (???)    for (let i = 4; i--;) {
 (???)        const q = blocks.charCodeAt(block * 16 + rotation * 4 + i) - 96
 (???)        callback(q % 4, q >> 2)
 (???)    }
 (???) 
 (???) onst Draw = (color, X, Y, block, rotation, bucket = Bucket) => {
 (???)    iterate(block, rotation, (x, y) => {
 (???)        const p = ' #' + (color ? '0ff00f808'.substring(block, block + 3) : 'fff')
 (???)        const v = bucket.rows[Y + y].cells[X + x]
 (???)        const o = v.style
 (???)        o.background = p
 (???)        o.border = (color ? 'outset' : 'solid') + p
 (???)        v.s = color
 (???)    })
 (???) 
 (???) onst Move = (dx, dy, drotation) => {
 (???)    Draw(0, X, Y, block, rotation)
 (???)    let c = true
 (???)    iterate(block, drotation, (x, y) => {
 (???)        x += X + dx
 (???)        y += Y + dy
 (???)        if (x < 0 || x >= W || y >= H || Bucket.rows[y].cells[x].s) {
 (???)            c = false
 (???)        }
 (???)    })
 (???)    if (c) {
 (???)        X += dx
 (???)        Y += dy
 (???)        rotation = drotation
 (???)    }
 (???)    Draw(1, X, Y, block, rotation)
 (???)    return c
 (???) 
 (???) onst Clear = () => {
 (???)    X = 3
 (???)    // full lines?
 (???)    let p = 0
 (???)    for (let y = Y; y < H; y++) {
 (???)        let f = 0
 (???)        let x = 0
 (???)        for (f = 0, x = W; x--;) f |= !Bucket.rows[y].cells[x].s
 (???)        if (!f) {
 (???)            Bucket.deleteRow(y)
 (???)            addRow(Bucket, W)
 (???)            p++
 (???)        }
 (???)    }
 (???)    document.getElementById('s').innerHTML = '\nScore: ' + (score += 9 << p) + '\n\nLines: ' + (lines += p)
 (???)    clearInterval(timer)
 (???)    timer = setInterval(Down, 650 - lines)
 (???)    // clear current next
 (???)    Draw(0, 1, 1, next, 0, NextBucket)
 (???)    // choose next
 (???)    block = next
 (???)    next = randomBlock()
 (???)    Draw(-1, 1, 1, next, 0, NextBucket)
 (???)    Y = 0
 (???)    rotation = 0
 (???)    // game over?
 (???)    if (!Move(0, 1, rotation)) {
 (???)        clearInterval(timer)
 (???)        alert('Game over!')
 (???)    }
 (???) 
 (???) onst Down = () => {
 (???)    if (!Move(0, 1, rotation)) {
 (???)        Clear()
 (???)    }
 (???) 
 (???) onst scrollTable = (table, direction) => {
 (???)    for (let y = 0; y < table.rows.length; y++) {
 (???)        const row = table.rows[y];
 (???)        if (direction == 1) {
 (???)            const orgCell = row.cells[0];
 (???)            const cell = row.insertCell(row.cells.length);
 (???)            cell.innerHTML = orgCell.innerHTML;
 (???)            cell.style.border = orgCell.style.border;
 (???)            cell.style.backgroundColor = orgCell.style.backgroundColor;
 (???)            cell.s = orgCell.s;
 (???)            row.deleteCell(0);
 (???)        } else {
 (???)            const orgCell = row.cells[row.cells.length - 1];
 (???)            const cell = row.insertCell(0);
 (???)            cell.innerHTML = orgCell.innerHTML;
 (???)            cell.style.border = orgCell.style.border;
 (???)            cell.style.backgroundColor = orgCell.style.backgroundColor;
 (???)            cell.s = orgCell.s;
 (???)            row.deleteCell(row.cells.length - 1);
 (???)        }
 (???)    }
 (???) 
 (???) onst k = e => {
 (???)    switch (e.code) {
 (???)        case 'ArrowLeft':
 (???)        case 'KeyJ':
 (???)            Draw(0, X, Y, block, rotation)
 (???)            // if (Move(0, 0, r)) {
 (???)            scrollTable(Bucket, -1);
 (???)            // }
 (???)            Draw(1, X, Y, block, rotation)
 (???)            break
 (???)        case 'ArrowUp':
 (???)        case 'KeyK':
 (???)            Move(0, 0, (rotation + 1) % 4)
 (???)            break
 (???)        case 'ArrowRight':
 (???)        case 'KeyL':
 (???)            Draw(0, X, Y, block, rotation)
 (???)            // if (Move(0, 0, r)) {
 (???)            scrollTable(Bucket, 1);
 (???)            // }
 (???)            Draw(1, X, Y, block, rotation)
 (???)            break
 (???)        case 'Space':
 (???)            clearInterval(timer)
 (???)            timer = setInterval(Down, 9)
 (???)        // fallthrough
 (???)        case 'ArrowDown':
 (???)        case 'KeyM':
 (???)        case 'KeyI':
 (???)            Down()
 (???)    }
 (???) 
 (???) or (let y = H; y--;) {
 (???)    addRow(Bucket, W)
 (???) 
 (???) or (let y = 6; y--;) {
 (???)    addRow(NextBucket, 6)
 (???) 
 (???) lear()
 (???) ddEventListener('keydown', k)