logo

I code dreams

kotlintris

Download
// TODO scoring
// TODO readable block definitions

package kotlintris

import kotlin.browser.*
import kotlin.math.*
import org.w3c.dom.*
import org.w3c.dom.events.KeyboardEvent

class Tetris {
    private val height = 25
    private val width = 10

    private var tetromino = 0
    private var nextTetromino = 0
    private var rotation = 0

    private var column = 0
    private var row = 0

    private var score = 0
    private var lines = 0
    private var speed = 650     // ms

    private var timer = 0

    private val blockDefinitions = "defgaeimdefgaeim" +
                                   "defjaeibdhijibfj" +
                                   "dhefaeijhifjabfj" +
                                   "eifjeifjeifjeifj" +
                                   "heifaefjheifaefj" +
                                   "deifaeifheijebfj" +
                                   "deijeibfdeijeibf"

    init {
        val main = drawBucket(width, height, "main")
        drawBucket(4, 4, "next")

        newTetronimo()

        timer = window.setInterval({ moveDown(main) }, speed)

        document.body?.addEventListener("keydown", { event -> keydown(event as KeyboardEvent) } )
    }

    private fun keydown(event: KeyboardEvent) {
        val main = document.querySelector("table.bucket.main") as HTMLTableElement
        when (event.which) {
            74, 37 -> moveTetromino(main, -1, 0, tetromino, rotation)               // left
            75, 38 -> moveTetromino(main, 0, 0, tetromino, (rotation + 1) % 4)      // rotate
            76, 39 -> moveTetromino(main, 1, 0, tetromino, rotation)                // right
            32 -> {         // drop
                if(timer > 0) {     // game not over?
                    window.clearInterval(timer)
                    timer = window.setInterval({ moveDown(main) }, 9)
                }
            }
            73, 40, 77 -> moveDown(main)        // move down
        }
    }

    private fun drawBucket(width: Int, height: Int, className: String): HTMLTableElement {
        val bucket = document.createElement("table") as HTMLTableElement
        bucket.classList.add("bucket")
        bucket.classList.add(className)

        for (y in 1..height) {
            val row = bucket.insertRow()
            for (x in 1..width) {
                row.insertCell()
            }
        }

        document.body?.appendChild(bucket)

        return bucket
    }

    private fun newTetronimo() {
        val next = document.querySelector("table.bucket.next") as HTMLTableElement
        val main = document.querySelector("table.bucket.main") as HTMLTableElement

        // clear next
        drawTetromino(next.rows, 0, 0, nextTetromino, 0, true)

        tetromino = nextTetromino
        rotation = 0

        column = width / 2 - 2
        row = 0

        // draw next
        
        nextTetromino = (0 until blockDefinitions.length / 7 / 4).random()
        drawTetromino(next.rows, 0, 0, nextTetromino, 0)

        if (!moveTetromino(main, 0, 1, tetromino, rotation)) {
            window.clearInterval(timer)
            timer = 0
            window.alert(":-(")
        }
   }

    private fun drawTetromino(rows: HTMLCollection, column: Int, row: Int, block: Int, rotation: Int, clear: Boolean = false) {
        var colors = arrayOf(
            "cyan",
            "blue",
            "orange",
            "yellow",
            "green",
            "purple",
            "red"
        )

        val color = colors[block]

        for(i in 0..3) {
            val code = blockDefinitions[block * 16 + rotation * 4 + i].toInt() - 96
            val x = code % 4
            val y = code / 4

            var cell = (rows[row + y] as HTMLTableRowElement).cells[column + x]
            cell?.className = ""
            if(!clear) {
                cell?.classList?.add(color)
            }
        }
    }

    private fun moveTetromino(bucket: HTMLTableElement, deltaX: Int, deltaY: Int, tetromino: Int, rotation: Int): Boolean {
        // clear tetromino
        drawTetromino(bucket.rows, this.column, this.row, this.tetromino, this.rotation, true)

        var free = testTetromino(bucket, this.column + deltaX, this.row + deltaY, tetromino, rotation)
        if(free) {
            this.column += deltaX
            this.row += deltaY
            this.rotation = rotation
        }

        // draw tetromino
        drawTetromino(bucket.rows, this.column, this.row, this.tetromino, this.rotation)

        return free
    }

    private fun testTetromino(bucket: HTMLTableElement, column: Int, row: Int, tetromino: Int, rotation: Int): Boolean {
        for(i in 0..3) {
            val code = blockDefinitions[tetromino * 16 + rotation * 4 + i].toInt() - 96
            val x = code % 4
            val y = code / 4

            if (column + x < 0 || column + x >= width || row + y >= height) {
                return false
            }

            var cell = (bucket.rows[row + y] as HTMLTableRowElement).cells[column + x]
            if(cell?.className != "") {
                return false
            }
        }

        return true
    }

    private fun moveDown(bucket: HTMLTableElement) {
        // cannot place at bottom? -> clear lines and new tetromino
        if(!moveTetromino(bucket, 0, 1, tetromino, rotation)) {
            window.clearInterval(timer)

            clearLines(bucket)
            newTetronimo()

            val main = document.querySelector("table.bucket.main") as HTMLTableElement
            timer = window.setInterval({ moveDown(main) }, speed)
        }
    }

    private fun clearLines(bucket: HTMLTableElement) {
        for(y in this.row..height - 1) {
            var row = bucket.rows[y] as HTMLTableRowElement
            var freeCount = 0
            for(x in 0..row.cells.length - 1) {
                var cell = row.cells[x]
                if(cell?.className == "") {
                    freeCount++
                }
            }
            if(freeCount == 0) {
                bucket.deleteRow(y)
                var row = bucket.insertRow(0)
                for (x in 1..width) {
                    row.insertCell()
                }
            }
        }
/*        for (p = 0, y = Y; y < H; y++) {
            for (f = 0, x = W; x--;) f |= !S[y].cells[x].s
            if (!f) {
                B.deleteRow(y)
                a()
                p++
            }
        }
        */
    }

}