diff --git a/sdl_test/sdl.def b/sdl_test/sdl.def new file mode 100644 index 00000000000..c68646d6ec7 --- /dev/null +++ b/sdl_test/sdl.def @@ -0,0 +1,5 @@ +headers = /Library/Frameworks/SDL2.framework/Headers/SDL.h stdio.h string.h unistd.h stdlib.h time.h +excludedFunctions = _IO_flockfile _IO_funlockfile _IO_ftrylockfile _IO_cleanup_region_start _IO_cleanup_region_end \ + mkstemp_dprotected_np + +compilerOpts = -D_POSIX_SOURCE -mxsave -mavx -mf16c diff --git a/sdl_test/tetris.kt b/sdl_test/tetris.kt new file mode 100644 index 00000000000..bc8f3520e15 --- /dev/null +++ b/sdl_test/tetris.kt @@ -0,0 +1,817 @@ +import kotlinx.cinterop.* +import konan.internal.* +import sdl.* + +typealias Field = Array + +enum class Move { + Left, + Right, + Down, + Rotate +} + +enum class PlacementResult(val linesCleared: Int = 0, val bonus: Int = 0) { + Nothing, + GameOver, + // For values of bonuses see https://tetris.wiki/Scoring + Single(1, 40), + Double(2, 100), + Triple(3, 300), + Tetris(4, 1200) +} + +val EMPTY: Byte = 0 +val BRICK: Byte = -1 + +class Point(var x: Int, var y: Int) + +operator fun Point.plus(other: Point): Point { + return Point(x + other.x, y + other.y) +} + +class PiecePosition(piece: Piece, private val origin: Point) { + private var p = piece.origin + val x get() = p.x + origin.x + val y get() = p.y + origin.y + + var state: Int get private set + val numberOfStates = piece.numberOfStates + + init { + state = 0 + } + + fun makeMove(move: Move) { + when (move) { + Move.Left -> --p.y + Move.Right -> ++p.y + Move.Down -> ++p.x + Move.Rotate -> state = (state + 1) % numberOfStates + } + } + + fun unMakeMove(move: Move) { + when (move) { + Move.Left -> ++p.y + Move.Right -> --p.y + Move.Down -> --p.x + Move.Rotate -> state = (state + numberOfStates - 1) % numberOfStates + } + } +} + +/* + * We use Nintendo Rotation System, right-handed version. + * See https://tetris.wiki/Nintendo_Rotation_System + */ +enum class Piece(private val origin_: Point, vararg val states: Field) { + T(Point(-1, -2), + arrayOf( + byteArrayOf(0, 0, 0), + byteArrayOf(1, 1, 1), + byteArrayOf(0, 1, 0)), + arrayOf( + byteArrayOf(0, 1, 0), + byteArrayOf(1, 1, 0), + byteArrayOf(0, 1, 0)), + arrayOf( + byteArrayOf(0, 1, 0), + byteArrayOf(1, 1, 1), + byteArrayOf(0, 0, 0)), + arrayOf( + byteArrayOf(0, 1, 0), + byteArrayOf(0, 1, 1), + byteArrayOf(0, 1, 0)) + ), + J(Point(-1, -2), + arrayOf( + byteArrayOf(0, 0, 0), + byteArrayOf(2, 2, 2), + byteArrayOf(0, 0, 2)), + arrayOf( + byteArrayOf(0, 2, 0), + byteArrayOf(0, 2, 0), + byteArrayOf(2, 2, 0)), + arrayOf( + byteArrayOf(2, 0, 0), + byteArrayOf(2, 2, 2), + byteArrayOf(0, 0, 0)), + arrayOf( + byteArrayOf(0, 2, 2), + byteArrayOf(0, 2, 0), + byteArrayOf(0, 2, 0)) + ), + Z(Point(-1, -2), + arrayOf( + byteArrayOf(0, 0, 0), + byteArrayOf(3, 3, 0), + byteArrayOf(0, 3, 3)), + arrayOf( + byteArrayOf(0, 0, 3), + byteArrayOf(0, 3, 3), + byteArrayOf(0, 3, 0)) + ), + O(Point(0, -2), + arrayOf( + byteArrayOf(1, 1), + byteArrayOf(1, 1)) + ), + S(Point(-1, -2), + arrayOf( + byteArrayOf(0, 0, 0), + byteArrayOf(0, 2, 2), + byteArrayOf(2, 2, 0)), + arrayOf( + byteArrayOf(0, 2, 0), + byteArrayOf(0, 2, 2), + byteArrayOf(0, 0, 2)) + ), + L(Point(-1, -2), + arrayOf( + byteArrayOf(0, 0, 0), + byteArrayOf(3, 3, 3), + byteArrayOf(3, 0, 0)), + arrayOf( + byteArrayOf(3, 3, 0), + byteArrayOf(0, 3, 0), + byteArrayOf(0, 3, 0)), + arrayOf( + byteArrayOf(0, 0, 3), + byteArrayOf(3, 3, 3), + byteArrayOf(0, 0, 0)), + arrayOf( + byteArrayOf(0, 3, 0), + byteArrayOf(0, 3, 0), + byteArrayOf(0, 3, 3)) + ), + I(Point(-2, -2), + arrayOf( + byteArrayOf(0, 0, 0, 0), + byteArrayOf(0, 0, 0, 0), + byteArrayOf(1, 1, 1, 1), + byteArrayOf(0, 0, 0, 0)), + arrayOf( + byteArrayOf(0, 0, 1, 0), + byteArrayOf(0, 0, 1, 0), + byteArrayOf(0, 0, 1, 0), + byteArrayOf(0, 0, 1, 0)) + ); + + val origin get() = Point(origin_.x, origin_.y) + val numberOfStates: Int = states.size + + fun canBePlaced(field: Field, position: PiecePosition): Boolean { + val piece = states[position.state] + val x = position.x + val y = position.y + for (i in piece.indices) { + val pieceRow = piece[i] + val boardRow = field[x + i] + for (j in pieceRow.indices) { + if (pieceRow[j] != EMPTY && boardRow[y + j] != EMPTY) + return false + } + } + return true + } + + fun place(field: Field, position: PiecePosition) { + val piece = states[position.state] + val x = position.x + val y = position.y + for (i in piece.indices) { + val pieceRow = piece[i] + for (j in pieceRow.indices) { + if (pieceRow[j] != EMPTY) field[x + i][y + j] = pieceRow[j] + } + } + } + + fun unPlace(field: Field, position: PiecePosition) { + val piece = states[position.state] + val x = position.x + val y = position.y + for (i in piece.indices) { + val pieceRow = piece[i] + for (j in pieceRow.indices) { + if (pieceRow[j] != EMPTY) field[x + i][y + j] = EMPTY + } + } + } +} + +interface GameFieldVisualizer { + fun drawCell(x: Int, y: Int, cell: Byte) + fun drawNextPieceCell(x: Int, y: Int, cell: Byte) + fun setInfo(linesCleared: Int, level: Int, score: Int, tetrises: Int) + fun refresh() +} + +enum class UserCommand { + LEFT, + RIGHT, + DOWN, + RELEASE, + ROTATE, + EXIT +} + +interface UserInput { + fun readCommands(): List +} + +fun sleep(millis: Int) { + SDL_Delay(millis) +} + +class GameField(val width: Int, val height: Int, val visualizer: GameFieldVisualizer) { + val MARGIN = 4 + + private val field: Field + private val origin: Point + private val nextPieceField: Field + + init { + field = Array(height + MARGIN * 2) { ByteArray(width + MARGIN * 2) } + for (i in field.indices) { + val row = field[i] + for (j in row.indices) { + if (i >= (MARGIN + height) // Bottom (field is flipped over). + || (j < MARGIN) // Left + || (j >= MARGIN + width)) // Right + row[j] = BRICK + } + } + // Coordinates are relative to the central axis and top of the field. + origin = Point(MARGIN, MARGIN + (width + 1) / 2) + nextPieceField = Array(4) { ByteArray(4) } + } + + lateinit var currentPiece: Piece + lateinit var nextPiece: Piece + lateinit var currentPosition: PiecePosition + + fun reset() { + for (i in 0..height - 1) + for (j in 0..width - 1) + field[i + MARGIN][j + MARGIN] = 0 + srand(time(null).toInt()) + nextPiece = getNextPiece(false) + switchCurrentPiece() + } + + private fun randInt() = (rand() and 32767) or ((rand() and 32767) shl 15) + + private fun getNextPiece(denyPrevious: Boolean): Piece { + val pieces = Piece.values() + if (!denyPrevious) + return pieces[randInt() % pieces.size] + while (true) { + val nextPiece = pieces[randInt() % pieces.size] + if (nextPiece != currentPiece) return nextPiece + } + } + + private fun switchCurrentPiece() { + currentPiece = nextPiece + nextPiece = getNextPiece(denyPrevious = true) // Forbid repeating the same piece for better distribution. + currentPosition = PiecePosition(currentPiece, origin) + } + + fun makeMove(move: Move): Boolean { + currentPosition.makeMove(move) + if (currentPiece.canBePlaced(field, currentPosition)) + return true + currentPosition.unMakeMove(move) + return false + } + + /** + * Places current piece at its current location. + */ + fun place(): PlacementResult { + currentPiece.place(field, currentPosition) + val linesCleared = clearLines() + if (isOutOfBorders()) return PlacementResult.GameOver + switchCurrentPiece() + if (!currentPiece.canBePlaced(field, currentPosition)) + return PlacementResult.GameOver + when (linesCleared) { + 1 -> return PlacementResult.Single + 2 -> return PlacementResult.Double + 3 -> return PlacementResult.Triple + 4 -> return PlacementResult.Tetris + else -> return PlacementResult.Nothing + } + } + + private fun clearLines(): Int { + val clearedLines = mutableListOf() + for (i in 0..height - 1) { + val row = field[i + MARGIN] + if ((0..width - 1).all { j -> row[j + MARGIN] != EMPTY }) { + clearedLines.add(i + MARGIN) + (0..width - 1).forEach { j -> row[j + MARGIN] = EMPTY } + } + } + if (clearedLines.size == 0) return 0 + draw(false) + visualizer.refresh() + sleep(500) + for (i in clearedLines) { + for (k in i - 1 downTo 1) + for (j in 0..width - 1) + field[k + 1][j + MARGIN] = field[k][j + MARGIN] + } + draw(false) + visualizer.refresh() + return clearedLines.size + } + + private fun isOutOfBorders(): Boolean { + for (i in 0..MARGIN - 1) + for (j in 0..width - 1) + if (field[i][j + MARGIN] != EMPTY) + return true + return false + } + + fun draw() { + draw(true) + drawNextPiece() + } + + private fun drawNextPiece() { + for (i in 0..3) + for (j in 0..3) + nextPieceField[i][j] = 0 + nextPiece.place(nextPieceField, PiecePosition(nextPiece, Point(1, 2))) + for (i in 0..3) + for (j in 0..3) + visualizer.drawNextPieceCell(i, j, nextPieceField[i][j]) + } + + private fun draw(drawCurrentPiece: Boolean) { + if (drawCurrentPiece) + currentPiece.place(field, currentPosition) + for (i in 0..height - 1) + for (j in 0..width - 1) + visualizer.drawCell(i, j, field[i + MARGIN][j + MARGIN]) + if (drawCurrentPiece) + currentPiece.unPlace(field, currentPosition) + } +} + +class Game(width: Int, height: Int, val visualizer: GameFieldVisualizer, val userInput: UserInput) { + private val field = GameField(width, height, visualizer) + + private var gameOver = true + private var startLevel = 0 + private var leveledUp = false + private var level = 0 + private var linesClearedAtCurrentLevel = 0 + private var linesCleared = 0 + private var tetrises = 0 + private var score = 0 + + /* + * For speed constants and level up thresholds see https://tetris.wiki/Tetris_(NES,_Nintendo) + */ + private val speeds = intArrayOf(48, 43, 38, 33, 28, 23, 18, 13, 8, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2) + private val levelUpThreshold + get() = + if (leveledUp) 10 + else minOf(startLevel * 10 + 10, maxOf(100, startLevel * 10 - 50)) + private val speed get() = if (level < 29) speeds[level] else 1 + + private var ticks = 0 + + fun startNewGame(level: Int) { + gameOver = false + startLevel = level + leveledUp = false + this.level = level + linesClearedAtCurrentLevel = 0 + linesCleared = 0 + tetrises = 0 + score = 0 + ticks = 0 + field.reset() + + visualizer.setInfo(linesCleared, level, score, tetrises) + field.draw() + visualizer.refresh() + + mainLoop() + } + + private fun placePiece() { + val placementResult = field.place() + ticks = 0 + println(placementResult.toString()) + when (placementResult) { + PlacementResult.Nothing -> return + PlacementResult.GameOver -> { + gameOver = true + return + } + else -> { + linesCleared += placementResult.linesCleared + linesClearedAtCurrentLevel += placementResult.linesCleared + score += placementResult.bonus * (level + 1) + if (placementResult == PlacementResult.Tetris) + ++tetrises + val levelUpThreshold = levelUpThreshold + if (linesClearedAtCurrentLevel >= levelUpThreshold) { + ++level + linesClearedAtCurrentLevel -= levelUpThreshold + leveledUp = true + } + + visualizer.setInfo(linesCleared, level, score, tetrises) + } + } + } + + /* + * Number of additional gravity shifts before locking a piece landed on the ground. + * This is needed in order to let user to move a piece to the left/right before locking. + */ + private val LOCK_DELAY = 1 + + private fun mainLoop() { + var prevCmd: UserCommand? = null + var das = false + var dasTicks = 0 + var attemptsToLock = 0 + while (!gameOver) { + sleep(1000 / 60) // Refresh rate - 60 frames per second. + //sleep(200) + val commands = userInput.readCommands() + for (cmd in commands) { + val success: Boolean + when (cmd) { + UserCommand.EXIT -> return + UserCommand.LEFT -> success = field.makeMove(Move.Left) + UserCommand.RIGHT -> success = field.makeMove(Move.Right) + UserCommand.ROTATE -> success = field.makeMove(Move.Rotate) + UserCommand.DOWN -> { + success = field.makeMove(Move.Down) + if (!success) placePiece() + } + UserCommand.RELEASE -> { + while (field.makeMove(Move.Down)) { + } + success = true + placePiece() + } + } + if (success) { + field.draw() + visualizer.refresh() + } + } + ++ticks + if (ticks < speed) continue + if (!field.makeMove(Move.Down)) { + if (++attemptsToLock >= LOCK_DELAY) { + placePiece() + attemptsToLock = 0 + } + } + field.draw() + visualizer.refresh() + ticks -= speed + } + } + +} + +fun main(args: Array) { + SDL_main(args) +} + +fun print_SDL_Error() { + externals.printf(SDL_GetError()!!.rawValue) +} + +class SDL_Visualizer(val width: Int, val height: Int): GameFieldVisualizer, UserInput { + private val CELL_SIZE = 20 + private val SYMBOL_SIZE = 21 + private val INFO_MARGIN = 10 + private val MARGIN = 2 + private val BORDER_WIDTH = 18 + private val INFO_SPACE_WIDTH = SYMBOL_SIZE * (2 + 8) + + private val field: Field = Array(height) { ByteArray(width) } + private val nextPieceField: Field = Array(4) { ByteArray(4) } + private var linesCleared: Int = 0 + private var level: Int = 0 + private var score: Int = 0 + private var tetrises: Int = 0 + + private val fieldWidth: Int + private val fieldHeight: Int + private val window: CPointer + private val renderer: CPointer + private val cells: CPointer + private val texts: CPointer + + init { + if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { + //println("SDL_Init error: ${SDL_GetError()}") + print("SDL_CreateWindow Error: ") + print_SDL_Error() + println() + throw Error() + } + + fieldWidth = width * (CELL_SIZE + MARGIN) + MARGIN + BORDER_WIDTH * 2 + fieldHeight = height * (CELL_SIZE + MARGIN) + MARGIN + BORDER_WIDTH * 2 + val win = SDL_CreateWindow("Tetris", 100, 100, fieldWidth + INFO_SPACE_WIDTH, + fieldHeight, SDL_WindowFlags.SDL_WINDOW_SHOWN.value) + if (win == null) { + //println("SDL_CreateWindow Error: ${SDL_GetError()!!.asCString()}") + print("SDL_CreateWindow Error: ") + print_SDL_Error() + println() + SDL_Quit() + throw Error() + } + window = win + + val ren = SDL_CreateRenderer(win, -1, (SDL_RendererFlags.SDL_RENDERER_ACCELERATED or SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC).value) + if (ren == null) { + SDL_DestroyWindow(win) + //println("SDL_CreateRenderer Error: ${SDL_GetError()!!.asCString()}") + println("SDL_CreateRenderer Error: ") + print_SDL_Error() + println() + SDL_Quit() + throw Error() + } + renderer = ren + + // TODO: merge together. + cells = loadImage(win, ren, "tetris_all.bmp") + texts = loadImage(win, ren, "tetris_cells_texts.bmp") +// if (SDL_SetTextureAlphaMod(tex, 63) != 0) { +// println("Unable to set alpha mod") +// print_SDL_Error() +// println() +// } +// if (SDL_SetTextureColorMod(tex, 127, 127, 127) != 0) { +// println("Unable to set color mod") +// print_SDL_Error() +// println() +// } + } + + private fun loadImage(win: CPointer, ren: CPointer, imagePath: String): CPointer { + val bmp = SDL_LoadBMP_RW(SDL_RWFromFile(imagePath, "rb"), 1); + if (bmp == null) { + SDL_DestroyRenderer(ren) + SDL_DestroyWindow(win) + //println("SDL_LoadBMP Error: ${SDL_GetError()!!.asCString()}") + println("SDL_LoadBMP Error: ") + print_SDL_Error() + println() + SDL_Quit() + throw Error() + } + + val tex = SDL_CreateTextureFromSurface(ren, bmp) + SDL_FreeSurface(bmp) + if (tex == null) { + SDL_DestroyRenderer(ren) + SDL_DestroyWindow(win) + //println("SDL_CreateTextureFromSurface Error: ${SDL_GetError()!!.asCString()}") + println("SDL_CreateTextureFromSurface Error: ") + print_SDL_Error() + println() + SDL_Quit() + throw Error() + } + return tex + } + + override fun drawCell(x: Int, y: Int, cell: Byte) { + field[x][y] = cell + } + + override fun drawNextPieceCell(x: Int, y: Int, cell: Byte) { + nextPieceField[x][y] = cell + } + + override fun setInfo(linesCleared: Int, level: Int, score: Int, tetrises: Int) { + this.linesCleared = linesCleared + this.level = level + this.score = score + this.tetrises = tetrises + } + + override fun refresh() { + SDL_RenderClear(renderer) + drawBorder() + drawField() + drawInfo() + drawNextPiece() + SDL_RenderPresent(renderer) + } + + private fun drawBorder() { + memScoped { + val rect = alloc() + rect.w.value = fieldWidth + rect.h.value = fieldHeight + rect.x.value = 0 + rect.y.value = 0 + SDL_SetRenderDrawColor(renderer, -128, 0, -128, -128) + SDL_RenderFillRect(renderer, rect.ptr.reinterpret()) + rect.w.value = fieldWidth - BORDER_WIDTH * 2 + rect.h.value = fieldHeight - BORDER_WIDTH * 2 + rect.x.value = BORDER_WIDTH + rect.y.value = BORDER_WIDTH + SDL_SetRenderDrawColor(renderer, 0, 0, 0, -128) + SDL_RenderFillRect(renderer, rect.ptr.reinterpret()) + } + } + + private fun drawField() { + drawField(field, width, height, MARGIN + BORDER_WIDTH, MARGIN + BORDER_WIDTH) + } + + private fun drawNextPiece() { + drawInt(103, 60 + SYMBOL_SIZE, fieldWidth + SYMBOL_SIZE, SYMBOL_SIZE * 11 + INFO_MARGIN * 6, 85, 0, 0) + drawField(nextPieceField, 4, 4, + fieldWidth + SYMBOL_SIZE + MARGIN + CELL_SIZE + MARGIN, + SYMBOL_SIZE * 12 + INFO_MARGIN * 7 + MARGIN) + } + + private fun drawField(f: Field, w: Int, h: Int, x: Int, y: Int) { + memScoped { + val srcRect = alloc() + val destRect = alloc() + srcRect.w.value = CELL_SIZE + srcRect.h.value = CELL_SIZE + destRect.w.value = CELL_SIZE + destRect.h.value = CELL_SIZE + for (i in 0..h - 1) + for (j in 0..w - 1) { + val cell = f[i][j].toInt() + if (cell == 0) continue + srcRect.x.value = (cell - 1) * CELL_SIZE + srcRect.y.value = (level % 10) * CELL_SIZE + destRect.x.value = x + j * (CELL_SIZE + MARGIN) + destRect.y.value = y + i * (CELL_SIZE + MARGIN) + SDL_RenderCopy(renderer, cells, srcRect.ptr.reinterpret(), destRect.ptr.reinterpret()) + } + } + } + + private fun drawInfo() { + drawInt(104, 60, fieldWidth + SYMBOL_SIZE, SYMBOL_SIZE, 107, 6, score) + drawInt(0, 60, fieldWidth + SYMBOL_SIZE, SYMBOL_SIZE * 3 + INFO_MARGIN, 104, 3, linesCleared) + drawInt(0, 60 + SYMBOL_SIZE, fieldWidth + SYMBOL_SIZE, SYMBOL_SIZE * 5 + INFO_MARGIN * 2, 103, 2, level) + drawInt(0, 60 + SYMBOL_SIZE * 2, fieldWidth + SYMBOL_SIZE, SYMBOL_SIZE * 7 + INFO_MARGIN * 3, 162, 2, tetrises) + } + + private fun drawInt(labelSrcX: Int, labelSrcY: Int, labelDestX: Int, labelDestY: Int, + labelWidth: Int, totalDigits: Int, value: Int) { + memScoped { + val srcRect = alloc() + val destRect = alloc() + + srcRect.w.value = labelWidth + srcRect.h.value = SYMBOL_SIZE + srcRect.x.value = labelSrcX + srcRect.y.value = labelSrcY + destRect.w.value = labelWidth + destRect.h.value = SYMBOL_SIZE + destRect.x.value = labelDestX + destRect.y.value = labelDestY + + SDL_RenderCopy(renderer, texts, srcRect.ptr.reinterpret(), destRect.ptr.reinterpret()) + + val digits = IntArray(totalDigits) + var x = value + for (i in 0..totalDigits - 1) { + digits[totalDigits - 1 - i] = x % 10 + x = x / 10 + } + srcRect.w.value = SYMBOL_SIZE + srcRect.h.value = SYMBOL_SIZE + destRect.w.value = SYMBOL_SIZE + destRect.h.value = SYMBOL_SIZE + for (i in 0..totalDigits - 1) { + srcRect.x.value = digits[i] * SYMBOL_SIZE + srcRect.y.value = 123 // TODO: constant. + destRect.x.value = labelDestX + SYMBOL_SIZE + i * SYMBOL_SIZE + destRect.y.value = labelDestY + SYMBOL_SIZE + SDL_RenderCopy(renderer, texts, srcRect.ptr.reinterpret(), destRect.ptr.reinterpret()) + } + } + } + + private val LEFT_PRESSED = 1 + private val RIGHT_PRESSED = 2 + private val DOWN_PRESSED = 4 + private val UP_PRESSED = 8 + private val Z_PRESSED = 16 + private val ESC_PRESSED = 32 + private var pressedKeys = 0 + private var stickedKeys = 0 + + private fun keyToCommand(key: Int): UserCommand? = + when (key) { + LEFT_PRESSED -> (UserCommand.LEFT) + RIGHT_PRESSED -> (UserCommand.RIGHT) + DOWN_PRESSED -> (UserCommand.DOWN) + Z_PRESSED -> (UserCommand.ROTATE) + UP_PRESSED -> (UserCommand.RELEASE) + ESC_PRESSED -> (UserCommand.EXIT) + else -> null + } + + override fun readCommands(): List { + val commands = mutableListOf() + memScoped { + val event = alloc() + var currentCommands = 0 + while (SDL_PollEvent(event.ptr.reinterpret()) != 0) { + if (event.type.value == SDL_EventType.SDL_KEYDOWN.value + || event.type.value == SDL_EventType.SDL_KEYUP.value) { + val keyboardEvent = event.ptr.reinterpret().pointed + val scancode = when (keyboardEvent.keysym.scancode.value) { + SDL_Scancode.SDL_SCANCODE_LEFT -> LEFT_PRESSED + SDL_Scancode.SDL_SCANCODE_RIGHT -> RIGHT_PRESSED + SDL_Scancode.SDL_SCANCODE_DOWN -> DOWN_PRESSED + SDL_Scancode.SDL_SCANCODE_Z -> Z_PRESSED + SDL_Scancode.SDL_SCANCODE_UP -> UP_PRESSED + SDL_Scancode.SDL_SCANCODE_ESCAPE -> ESC_PRESSED + else -> 0 + } + if (keyboardEvent.state.value.toInt() == SDL_PRESSED) { + if (pressedKeys and scancode != 0) + stickedKeys = stickedKeys or scancode + currentCommands = currentCommands or scancode + pressedKeys = pressedKeys or scancode + } + else if (keyboardEvent.state.value.toInt() == SDL_RELEASED) { + pressedKeys = pressedKeys and scancode.inv() + stickedKeys = stickedKeys and scancode.inv() + } + } + } + currentCommands = currentCommands or stickedKeys + var temp = currentCommands + while (temp != 0) { + val next = temp and (temp - 1) + val curKey = temp - next + when (curKey) { + LEFT_PRESSED -> commands.add(UserCommand.LEFT) + RIGHT_PRESSED -> commands.add(UserCommand.RIGHT) + DOWN_PRESSED -> commands.add(UserCommand.DOWN) + Z_PRESSED -> commands.add(UserCommand.ROTATE) + UP_PRESSED -> commands.add(UserCommand.RELEASE) + ESC_PRESSED -> commands.add(UserCommand.EXIT) + } + temp = next + } + } + return commands + } + + fun destroy() { + SDL_DestroyTexture(cells) + SDL_DestroyRenderer(renderer) + SDL_DestroyWindow(window) + SDL_Quit() + } +} + +@ExportForCppRuntime("SDL_main") +fun SDL_main(args: Array) { + var startLevel = 0 + var width = 10 + var height = 20 + when (args.size) { + 1 -> startLevel = atoi(args[0]) + 2 -> { + width = atoi(args[0]) + height = atoi(args[1]) + } + 3 -> { + width = atoi(args[0]) + height = atoi(args[1]) + startLevel = atoi(args[2]) + } + } + val visualizer = SDL_Visualizer(width, height) + val game = Game(width, height, visualizer, visualizer) + game.startNewGame(startLevel) + + return +} \ No newline at end of file diff --git a/sdl_test/tetris_all.bmp b/sdl_test/tetris_all.bmp new file mode 100755 index 00000000000..ae4d33ba51c Binary files /dev/null and b/sdl_test/tetris_all.bmp differ diff --git a/sdl_test/tetris_cells_texts.bmp b/sdl_test/tetris_cells_texts.bmp new file mode 100755 index 00000000000..058d090e350 Binary files /dev/null and b/sdl_test/tetris_cells_texts.bmp differ