From 25690fa6e79a7e4a7d0a30060c0600313cf95671 Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Fri, 6 Jul 2018 17:36:02 +0300 Subject: [PATCH] Make Tetris sample more readable, add config reader. (#1773) --- samples/tetris/src/main/kotlin/Config.kt | 55 ++ .../tetris/src/main/kotlin/SDL_Visualizer.kt | 475 +++++++++++++++++ samples/tetris/src/main/kotlin/Tetris.kt | 483 +----------------- samples/tetris/src/main/kotlin/main.kt | 39 ++ samples/tetris/src/main/resources/config.txt | 3 + 5 files changed, 576 insertions(+), 479 deletions(-) create mode 100644 samples/tetris/src/main/kotlin/Config.kt create mode 100644 samples/tetris/src/main/kotlin/SDL_Visualizer.kt create mode 100644 samples/tetris/src/main/kotlin/main.kt create mode 100644 samples/tetris/src/main/resources/config.txt diff --git a/samples/tetris/src/main/kotlin/Config.kt b/samples/tetris/src/main/kotlin/Config.kt new file mode 100644 index 00000000000..17280f72a3e --- /dev/null +++ b/samples/tetris/src/main/kotlin/Config.kt @@ -0,0 +1,55 @@ +/** + * Copyright 2010-2018 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tetris + +import platform.posix.* +import kotlinx.cinterop.* + +object Config { + var width: Int = 10 + private set + var height: Int = 20 + private set + var startLevel = 0 + private set + + init { + val file = fopen("config.txt", "r") + if (file != null) { + try { + memScoped { + val bufferLength = 2 * 1024 + val buffer = allocArray(bufferLength) + while (true) { + val nextLine = fgets(buffer, bufferLength, file)?.toKString() + if (nextLine == null || nextLine.isEmpty()) break + val records = nextLine.split('=') + if (records.size != 2) continue + val key = records[0].trim() + val value = records[1].trim() + when (key) { + "width" -> width = value.toInt() + "height" -> height = value.toInt() + "startLevel" -> startLevel = value.toInt() + } + } + } + } finally { + fclose(file) + } + } + } +} \ No newline at end of file diff --git a/samples/tetris/src/main/kotlin/SDL_Visualizer.kt b/samples/tetris/src/main/kotlin/SDL_Visualizer.kt new file mode 100644 index 00000000000..44e311f5e46 --- /dev/null +++ b/samples/tetris/src/main/kotlin/SDL_Visualizer.kt @@ -0,0 +1,475 @@ +/** + * Copyright 2010-2018 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tetris + +import kotlinx.cinterop.* +import platform.posix.* +import sdl.* + +fun get_SDL_Error() = SDL_GetError()!!.toKString() + +fun sleep(millis: Int) { + SDL_Delay(millis) +} + +class SDL_Visualizer(val width: Int, val height: Int): GameFieldVisualizer, UserInput { + private val CELL_SIZE = 20 + private val COLORS = 10 + private val CELLS_WIDTH = COLORS * CELL_SIZE + private val CELLS_HEIGHT = 3 * CELL_SIZE + 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 LINES_LABEL_WIDTH = 104 + private val SCORE_LABEL_WIDTH = 107 + private val LEVEL_LABEL_WIDTH = 103 + private val NEXT_LABEL_WIDTH = 85 + private val TETRISES_LABEL_WIDTH = 162 + + private val ratio: Float + + private fun stretch(value: Int) = (value.toFloat() * ratio + 0.5).toInt() + + inner class GamePadButtons(width: Int, height: Int, gamePadHeight: Int) { + val MOVE_BUTTON_SIZE = 50 + val ROTATE_BUTTON_SIZE = 80 + val BUTTONS_MARGIN = 25 + + val arena = Arena() + val leftRect: SDL_Rect + val rightRect: SDL_Rect + val downRect: SDL_Rect + val dropRect: SDL_Rect + val rotateRect: SDL_Rect + + init { + val moveButtonsWidth = 3 * MOVE_BUTTON_SIZE + 2 * BUTTONS_MARGIN + BUTTONS_MARGIN + val x = (width - moveButtonsWidth - ROTATE_BUTTON_SIZE) / 2 - MOVE_BUTTON_SIZE + val y2 = (gamePadHeight - 2 * MOVE_BUTTON_SIZE - BUTTONS_MARGIN) / 2 + leftRect = arena.alloc() + leftRect.w = MOVE_BUTTON_SIZE + leftRect.h = MOVE_BUTTON_SIZE + leftRect.x = x + leftRect.y = height - gamePadHeight + y2 + MOVE_BUTTON_SIZE + BUTTONS_MARGIN + + downRect = arena.alloc() + downRect.w = MOVE_BUTTON_SIZE + downRect.h = MOVE_BUTTON_SIZE + downRect.x = x + MOVE_BUTTON_SIZE + BUTTONS_MARGIN + downRect.y = leftRect.y + + dropRect = arena.alloc() + dropRect.w = MOVE_BUTTON_SIZE + dropRect.h = MOVE_BUTTON_SIZE + dropRect.x = downRect.x + dropRect.y = height - gamePadHeight + y2 + + rightRect = arena.alloc() + rightRect.w = MOVE_BUTTON_SIZE + rightRect.h = MOVE_BUTTON_SIZE + rightRect.x = x + 2 * MOVE_BUTTON_SIZE + 2 * BUTTONS_MARGIN + rightRect.y = height - gamePadHeight + y2 + MOVE_BUTTON_SIZE + BUTTONS_MARGIN + + rotateRect = arena.alloc() + rotateRect.w = ROTATE_BUTTON_SIZE + rotateRect.h = ROTATE_BUTTON_SIZE + rotateRect.x = x + moveButtonsWidth + rotateRect.y = height - gamePadHeight + y2 - BUTTONS_MARGIN + } + + fun getCommandAt(x: Int, y: Int): UserCommand? { + return when { + inside(leftRect, x, y) -> UserCommand.LEFT + inside(rightRect, x, y) -> UserCommand.RIGHT + inside(downRect, x, y) -> UserCommand.DOWN + inside(dropRect, x, y) -> UserCommand.DROP + inside(rotateRect, x, y) -> UserCommand.ROTATE + else -> null + } + } + + private fun inside(rect: SDL_Rect, x: Int, y: Int): Boolean { + return x >= stretch(rect.x) && x <= stretch(rect.x + rect.w) + && y >= stretch(rect.y) && y <= stretch(rect.y + rect.h) + } + + fun destroy() { + arena.clear() + } + } + + 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 var displayWidth: Int = 0 + private var displayHeight: Int = 0 + private val fieldWidth: Int + private val fieldHeight: Int + private val windowX: Int + private val windowY: Int + private val window: CPointer + private val renderer: CPointer + private val texture: CPointer + private val gamePadButtons: GamePadButtons? + private val platform: String + + init { + if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { + println("SDL_Init Error: ${get_SDL_Error()}") + throw Error() + } + + platform = SDL_GetPlatform()!!.toKString() + + memScoped { + val displayMode = alloc() + if (SDL_GetCurrentDisplayMode(0, displayMode.ptr.reinterpret()) != 0) { + println("SDL_GetCurrentDisplayMode Error: ${get_SDL_Error()}") + SDL_Quit() + throw Error() + } + displayWidth = displayMode.w + displayHeight = displayMode.h + } + fieldWidth = width * (CELL_SIZE + MARGIN) + MARGIN + BORDER_WIDTH * 2 + fieldHeight = height * (CELL_SIZE + MARGIN) + MARGIN + BORDER_WIDTH * 2 + var windowWidth = fieldWidth + INFO_SPACE_WIDTH + var windowHeight: Int + if (platform == "iOS") { + val gamePadHeight = (displayHeight * windowWidth - fieldHeight * displayWidth) / displayWidth + windowHeight = fieldHeight + gamePadHeight + gamePadButtons = GamePadButtons(windowWidth, windowHeight, gamePadHeight) + windowX = 0 + windowY = 0 + ratio = displayHeight.toFloat() / windowHeight + windowWidth = displayWidth + windowHeight = displayHeight + } else { + windowHeight = fieldHeight + gamePadButtons = null + windowX = (displayWidth - windowWidth) / 2 + windowY = (displayHeight - windowHeight) / 2 + ratio = 1.0f + } + val window = SDL_CreateWindow("Tetris", windowX, windowY, windowWidth, windowHeight, SDL_WINDOW_SHOWN) + if (window == null) { + println("SDL_CreateWindow Error: ${get_SDL_Error()}") + SDL_Quit() + throw Error() + } + this.window = window + + val renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED or SDL_RENDERER_PRESENTVSYNC) + if (renderer == null) { + SDL_DestroyWindow(window) + println("SDL_CreateRenderer Error: ${get_SDL_Error()}") + SDL_Quit() + throw Error() + } + this.renderer = renderer + + texture = loadImage(window, renderer, "tetris_all.bmp") + } + + 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_RW Error: ${get_SDL_Error()}") + 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: ${get_SDL_Error()}") + 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) + drawField() + drawInfo() + drawNextPiece() + drawGamePad() + SDL_RenderPresent(renderer) + } + + private fun drawBorder(topLeftX: Int, topLeftY: Int, width: Int, height: Int) { + // Upper-left corner. + var srcX = CELLS_WIDTH + var srcY = 0 + var destX = topLeftX + var destY = topLeftY + copyRect(srcX, srcY, destX, destY, BORDER_WIDTH + MARGIN, BORDER_WIDTH) + + // Upper margin. + srcX += BORDER_WIDTH + MARGIN + destX += BORDER_WIDTH + MARGIN + for (i in 0..width - 1) { + copyRect(srcX, srcY, destX, destY, CELL_SIZE + MARGIN, BORDER_WIDTH) + destX += CELL_SIZE + MARGIN + } + + // Upper-right corner. + srcX += CELL_SIZE + MARGIN + copyRect(srcX, srcY, destX, destY, BORDER_WIDTH, BORDER_WIDTH + MARGIN) + + // Right margin. + srcY += BORDER_WIDTH + MARGIN + destY += BORDER_WIDTH + MARGIN + for (j in 0..height - 1) { + copyRect(srcX, srcY, destX, destY, BORDER_WIDTH, CELL_SIZE + MARGIN) + destY += CELL_SIZE + MARGIN + } + + // Left margin. + srcX = CELLS_WIDTH + srcY = BORDER_WIDTH + destX = topLeftX + destY = topLeftY + BORDER_WIDTH + for (j in 0..height - 1) { + copyRect(srcX, srcY, destX, destY, BORDER_WIDTH, CELL_SIZE + MARGIN) + destY += CELL_SIZE + MARGIN + } + + // Left-down corner. + srcY += CELL_SIZE + MARGIN + copyRect(srcX, srcY, destX, destY, BORDER_WIDTH, BORDER_WIDTH + MARGIN) + + // Down marign. + srcX += BORDER_WIDTH + srcY += MARGIN + destX += BORDER_WIDTH + destY += MARGIN + for (i in 0..width - 1) { + copyRect(srcX, srcY, destX, destY, CELL_SIZE + MARGIN, BORDER_WIDTH) + destX += CELL_SIZE + MARGIN + + } + // Right-down corner. + srcX += CELL_SIZE + MARGIN + copyRect(srcX, srcY, destX, destY, BORDER_WIDTH + MARGIN, BORDER_WIDTH) + } + + private fun drawField() { + drawField(field = field, + topLeftX = 0, + topLeftY = 0, + width = width, + height = height) + } + + private fun drawNextPiece() { + drawInt(labelSrcX = LEVEL_LABEL_WIDTH, + labelSrcY = CELLS_HEIGHT + SYMBOL_SIZE, + labelDestX = fieldWidth + SYMBOL_SIZE, + labelDestY = getInfoY(5), + labelWidth = NEXT_LABEL_WIDTH, + totalDigits = 0, + value = 0) + drawField(field = nextPieceField, + topLeftX = fieldWidth + SYMBOL_SIZE, + topLeftY = getInfoY(6), + width = 4, + height = 4) + } + + private fun drawField(field: Field, topLeftX: Int, topLeftY: Int, width: Int, height: Int) { + drawBorder(topLeftX = topLeftX, + topLeftY = topLeftY, + width = width, + height = height) + for (i in 0..height - 1) + for (j in 0..width - 1) { + val cell = field[i][j].toInt() + if (cell == 0) continue + copyRect(srcX = (level % COLORS) * CELL_SIZE, + srcY = (3 - cell) * CELL_SIZE, + destX = topLeftX + BORDER_WIDTH + MARGIN + j * (CELL_SIZE + MARGIN), + destY = topLeftY + BORDER_WIDTH + MARGIN + i * (CELL_SIZE + MARGIN), + width = CELL_SIZE, + height = CELL_SIZE) + } + } + + private fun drawInfo() { + drawInt(labelSrcX = LINES_LABEL_WIDTH, + labelSrcY = CELLS_HEIGHT, + labelDestX = fieldWidth + SYMBOL_SIZE, + labelDestY = getInfoY(0), + labelWidth = SCORE_LABEL_WIDTH, + totalDigits = 6, + value = score) + drawInt(labelSrcX = 0, + labelSrcY = CELLS_HEIGHT, + labelDestX = fieldWidth + SYMBOL_SIZE, + labelDestY = getInfoY(1), + labelWidth = LINES_LABEL_WIDTH, + totalDigits = 3, + value = linesCleared) + drawInt(labelSrcX = 0, + labelSrcY = CELLS_HEIGHT + SYMBOL_SIZE, + labelDestX = fieldWidth + SYMBOL_SIZE, + labelDestY = getInfoY(2), + labelWidth = LEVEL_LABEL_WIDTH, + totalDigits = 2, + value = level) + drawInt(labelSrcX = 0, + labelSrcY = CELLS_HEIGHT + SYMBOL_SIZE * 2, + labelDestX = fieldWidth + SYMBOL_SIZE, + labelDestY = getInfoY(3), + labelWidth = TETRISES_LABEL_WIDTH, + totalDigits = 2, + value = tetrises) + } + + private fun getInfoY(line: Int): Int { + return SYMBOL_SIZE * (2 * line + 1) + INFO_MARGIN * line + } + + private fun drawInt(labelSrcX: Int, labelSrcY: Int, labelDestX: Int, labelDestY: Int, + labelWidth: Int, totalDigits: Int, value: Int) { + copyRect(srcX = labelSrcX, + srcY = labelSrcY, + destX = labelDestX, + destY = labelDestY, + width = labelWidth, + height = SYMBOL_SIZE) + val digits = IntArray(totalDigits) + var x = value + for (i in 0..totalDigits - 1) { + digits[totalDigits - 1 - i] = x % 10 + x = x / 10 + } + for (i in 0..totalDigits - 1) { + copyRect(srcX = digits[i] * SYMBOL_SIZE, + srcY = CELLS_HEIGHT + 3 * SYMBOL_SIZE, + destX = labelDestX + SYMBOL_SIZE + i * SYMBOL_SIZE, + destY = labelDestY + SYMBOL_SIZE, + width = SYMBOL_SIZE, + height = SYMBOL_SIZE) + } + } + + private fun drawGamePad() { + if (gamePadButtons == null) return + SDL_SetRenderDrawColor(renderer, 127, 127, 127, SDL_ALPHA_OPAQUE.toByte()) + fillRect(gamePadButtons.leftRect) + fillRect(gamePadButtons.downRect) + fillRect(gamePadButtons.dropRect) + fillRect(gamePadButtons.rightRect) + fillRect(gamePadButtons.rotateRect) + SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE.toByte()) + } + + private fun fillRect(rect: SDL_Rect) { + memScoped { + val stretchedRect = alloc() + stretchedRect.w = stretch(rect.w) + stretchedRect.h = stretch(rect.h) + stretchedRect.x = stretch(rect.x) + stretchedRect.y = stretch(rect.y) + SDL_RenderFillRect(renderer, stretchedRect.ptr.reinterpret()) + } + } + + private fun copyRect(srcX: Int, srcY: Int, destX: Int, destY: Int, width: Int, height: Int) { + memScoped { + val srcRect = alloc() + val destRect = alloc() + srcRect.w = width + srcRect.h = height + srcRect.x = srcX + srcRect.y = srcY + destRect.w = stretch(width) + destRect.h = stretch(height) + destRect.x = stretch(destX) + destRect.y = stretch(destY) + SDL_RenderCopy(renderer, texture, srcRect.ptr.reinterpret(), destRect.ptr.reinterpret()) + } + } + + override fun readCommands(): List { + val commands = mutableListOf() + memScoped { + val event = alloc() + while (SDL_PollEvent(event.ptr.reinterpret()) != 0) { + val eventType = event.type + when (eventType) { + SDL_QUIT -> commands.add(UserCommand.EXIT) + SDL_KEYDOWN -> { + val keyboardEvent = event.ptr.reinterpret().pointed + when (keyboardEvent.keysym.scancode) { + SDL_SCANCODE_LEFT -> commands.add(UserCommand.LEFT) + SDL_SCANCODE_RIGHT -> commands.add(UserCommand.RIGHT) + SDL_SCANCODE_DOWN -> commands.add(UserCommand.DOWN) + SDL_SCANCODE_Z, SDL_SCANCODE_SPACE -> commands.add(UserCommand.ROTATE) + SDL_SCANCODE_UP -> commands.add(UserCommand.DROP) + SDL_SCANCODE_ESCAPE -> commands.add(UserCommand.EXIT) + } + } + SDL_MOUSEBUTTONDOWN -> if (gamePadButtons != null) { + val mouseEvent = event.ptr.reinterpret().pointed + val x = mouseEvent.x + val y = mouseEvent.y + val command = gamePadButtons.getCommandAt(x, y) + if (command != null) + commands.add(command) + } + } + } + } + return commands + } + + fun destroy() { + SDL_DestroyTexture(texture) + SDL_DestroyRenderer(renderer) + SDL_DestroyWindow(window) + SDL_Quit() + gamePadButtons?.destroy() + } +} \ No newline at end of file diff --git a/samples/tetris/src/main/kotlin/Tetris.kt b/samples/tetris/src/main/kotlin/Tetris.kt index 870b0f3f550..10abbc51adf 100644 --- a/samples/tetris/src/main/kotlin/Tetris.kt +++ b/samples/tetris/src/main/kotlin/Tetris.kt @@ -1,5 +1,5 @@ -/* - * Copyright 2010-2017 JetBrains s.r.o. +/** + * Copyright 2010-2018 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,10 @@ * limitations under the License. */ +package tetris + import kotlinx.cinterop.* import platform.posix.* -import sdl.* typealias Field = Array @@ -240,10 +241,6 @@ interface UserInput { fun readCommands(): List } -fun sleep(millis: Int) { - SDL_Delay(millis) -} - class GameField(val width: Int, val height: Int, val visualizer: GameFieldVisualizer) { private val MARGIN = 4 @@ -503,475 +500,3 @@ class Game(width: Int, height: Int, val visualizer: GameFieldVisualizer, val use } -fun get_SDL_Error() = SDL_GetError()!!.toKString() - -class SDL_Visualizer(val width: Int, val height: Int): GameFieldVisualizer, UserInput { - private val CELL_SIZE = 20 - private val COLORS = 10 - private val CELLS_WIDTH = COLORS * CELL_SIZE - private val CELLS_HEIGHT = 3 * CELL_SIZE - 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 LINES_LABEL_WIDTH = 104 - private val SCORE_LABEL_WIDTH = 107 - private val LEVEL_LABEL_WIDTH = 103 - private val NEXT_LABEL_WIDTH = 85 - private val TETRISES_LABEL_WIDTH = 162 - - private val ratio: Float - - private fun stretch(value: Int) = (value.toFloat() * ratio + 0.5).toInt() - - inner class GamePadButtons(width: Int, height: Int, gamePadHeight: Int) { - val MOVE_BUTTON_SIZE = 50 - val ROTATE_BUTTON_SIZE = 80 - val BUTTONS_MARGIN = 25 - - val arena = Arena() - val leftRect: SDL_Rect - val rightRect: SDL_Rect - val downRect: SDL_Rect - val dropRect: SDL_Rect - val rotateRect: SDL_Rect - - init { - val moveButtonsWidth = 3 * MOVE_BUTTON_SIZE + 2 * BUTTONS_MARGIN + BUTTONS_MARGIN - val x = (width - moveButtonsWidth - ROTATE_BUTTON_SIZE) / 2 - MOVE_BUTTON_SIZE - val y2 = (gamePadHeight - 2 * MOVE_BUTTON_SIZE - BUTTONS_MARGIN) / 2 - leftRect = arena.alloc() - leftRect.w = MOVE_BUTTON_SIZE - leftRect.h = MOVE_BUTTON_SIZE - leftRect.x = x - leftRect.y = height - gamePadHeight + y2 + MOVE_BUTTON_SIZE + BUTTONS_MARGIN - - downRect = arena.alloc() - downRect.w = MOVE_BUTTON_SIZE - downRect.h = MOVE_BUTTON_SIZE - downRect.x = x + MOVE_BUTTON_SIZE + BUTTONS_MARGIN - downRect.y = leftRect.y - - dropRect = arena.alloc() - dropRect.w = MOVE_BUTTON_SIZE - dropRect.h = MOVE_BUTTON_SIZE - dropRect.x = downRect.x - dropRect.y = height - gamePadHeight + y2 - - rightRect = arena.alloc() - rightRect.w = MOVE_BUTTON_SIZE - rightRect.h = MOVE_BUTTON_SIZE - rightRect.x = x + 2 * MOVE_BUTTON_SIZE + 2 * BUTTONS_MARGIN - rightRect.y = height - gamePadHeight + y2 + MOVE_BUTTON_SIZE + BUTTONS_MARGIN - - rotateRect = arena.alloc() - rotateRect.w = ROTATE_BUTTON_SIZE - rotateRect.h = ROTATE_BUTTON_SIZE - rotateRect.x = x + moveButtonsWidth - rotateRect.y = height - gamePadHeight + y2 - BUTTONS_MARGIN - } - - fun getCommandAt(x: Int, y: Int): UserCommand? { - return when { - inside(leftRect, x, y) -> UserCommand.LEFT - inside(rightRect, x, y) -> UserCommand.RIGHT - inside(downRect, x, y) -> UserCommand.DOWN - inside(dropRect, x, y) -> UserCommand.DROP - inside(rotateRect, x, y) -> UserCommand.ROTATE - else -> null - } - } - - private fun inside(rect: SDL_Rect, x: Int, y: Int): Boolean { - return x >= stretch(rect.x) && x <= stretch(rect.x + rect.w) - && y >= stretch(rect.y) && y <= stretch(rect.y + rect.h) - } - - fun destroy() { - arena.clear() - } - } - - 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 var displayWidth: Int = 0 - private var displayHeight: Int = 0 - private val fieldWidth: Int - private val fieldHeight: Int - private val windowX: Int - private val windowY: Int - private val window: CPointer - private val renderer: CPointer - private val texture: CPointer - private val gamePadButtons: GamePadButtons? - private val platform: String - - init { - if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { - println("SDL_Init Error: ${get_SDL_Error()}") - throw Error() - } - - platform = SDL_GetPlatform()!!.toKString() - - memScoped { - val displayMode = alloc() - if (SDL_GetCurrentDisplayMode(0, displayMode.ptr.reinterpret()) != 0) { - println("SDL_GetCurrentDisplayMode Error: ${get_SDL_Error()}") - SDL_Quit() - throw Error() - } - displayWidth = displayMode.w - displayHeight = displayMode.h - } - fieldWidth = width * (CELL_SIZE + MARGIN) + MARGIN + BORDER_WIDTH * 2 - fieldHeight = height * (CELL_SIZE + MARGIN) + MARGIN + BORDER_WIDTH * 2 - var windowWidth = fieldWidth + INFO_SPACE_WIDTH - var windowHeight: Int - if (platform == "iOS") { - val gamePadHeight = (displayHeight * windowWidth - fieldHeight * displayWidth) / displayWidth - windowHeight = fieldHeight + gamePadHeight - gamePadButtons = GamePadButtons(windowWidth, windowHeight, gamePadHeight) - windowX = 0 - windowY = 0 - ratio = displayHeight.toFloat() / windowHeight - windowWidth = displayWidth - windowHeight = displayHeight - } else { - windowHeight = fieldHeight - gamePadButtons = null - windowX = (displayWidth - windowWidth) / 2 - windowY = (displayHeight - windowHeight) / 2 - ratio = 1.0f - } - val window = SDL_CreateWindow("Tetris", windowX, windowY, windowWidth, windowHeight, SDL_WINDOW_SHOWN) - if (window == null) { - println("SDL_CreateWindow Error: ${get_SDL_Error()}") - SDL_Quit() - throw Error() - } - this.window = window - - val renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED or SDL_RENDERER_PRESENTVSYNC) - if (renderer == null) { - SDL_DestroyWindow(window) - println("SDL_CreateRenderer Error: ${get_SDL_Error()}") - SDL_Quit() - throw Error() - } - this.renderer = renderer - - texture = loadImage(window, renderer, "tetris_all.bmp") - } - - 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_RW Error: ${get_SDL_Error()}") - 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: ${get_SDL_Error()}") - 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) - drawField() - drawInfo() - drawNextPiece() - drawGamePad() - SDL_RenderPresent(renderer) - } - - private fun drawBorder(topLeftX: Int, topLeftY: Int, width: Int, height: Int) { - // Upper-left corner. - var srcX = CELLS_WIDTH - var srcY = 0 - var destX = topLeftX - var destY = topLeftY - copyRect(srcX, srcY, destX, destY, BORDER_WIDTH + MARGIN, BORDER_WIDTH) - - // Upper margin. - srcX += BORDER_WIDTH + MARGIN - destX += BORDER_WIDTH + MARGIN - for (i in 0..width - 1) { - copyRect(srcX, srcY, destX, destY, CELL_SIZE + MARGIN, BORDER_WIDTH) - destX += CELL_SIZE + MARGIN - } - - // Upper-right corner. - srcX += CELL_SIZE + MARGIN - copyRect(srcX, srcY, destX, destY, BORDER_WIDTH, BORDER_WIDTH + MARGIN) - - // Right margin. - srcY += BORDER_WIDTH + MARGIN - destY += BORDER_WIDTH + MARGIN - for (j in 0..height - 1) { - copyRect(srcX, srcY, destX, destY, BORDER_WIDTH, CELL_SIZE + MARGIN) - destY += CELL_SIZE + MARGIN - } - - // Left margin. - srcX = CELLS_WIDTH - srcY = BORDER_WIDTH - destX = topLeftX - destY = topLeftY + BORDER_WIDTH - for (j in 0..height - 1) { - copyRect(srcX, srcY, destX, destY, BORDER_WIDTH, CELL_SIZE + MARGIN) - destY += CELL_SIZE + MARGIN - } - - // Left-down corner. - srcY += CELL_SIZE + MARGIN - copyRect(srcX, srcY, destX, destY, BORDER_WIDTH, BORDER_WIDTH + MARGIN) - - // Down marign. - srcX += BORDER_WIDTH - srcY += MARGIN - destX += BORDER_WIDTH - destY += MARGIN - for (i in 0..width - 1) { - copyRect(srcX, srcY, destX, destY, CELL_SIZE + MARGIN, BORDER_WIDTH) - destX += CELL_SIZE + MARGIN - - } - // Right-down corner. - srcX += CELL_SIZE + MARGIN - copyRect(srcX, srcY, destX, destY, BORDER_WIDTH + MARGIN, BORDER_WIDTH) - } - - private fun drawField() { - drawField(field = field, - topLeftX = 0, - topLeftY = 0, - width = width, - height = height) - } - - private fun drawNextPiece() { - drawInt(labelSrcX = LEVEL_LABEL_WIDTH, - labelSrcY = CELLS_HEIGHT + SYMBOL_SIZE, - labelDestX = fieldWidth + SYMBOL_SIZE, - labelDestY = getInfoY(5), - labelWidth = NEXT_LABEL_WIDTH, - totalDigits = 0, - value = 0) - drawField(field = nextPieceField, - topLeftX = fieldWidth + SYMBOL_SIZE, - topLeftY = getInfoY(6), - width = 4, - height = 4) - } - - private fun drawField(field: Field, topLeftX: Int, topLeftY: Int, width: Int, height: Int) { - drawBorder(topLeftX = topLeftX, - topLeftY = topLeftY, - width = width, - height = height) - for (i in 0..height - 1) - for (j in 0..width - 1) { - val cell = field[i][j].toInt() - if (cell == 0) continue - copyRect(srcX = (level % COLORS) * CELL_SIZE, - srcY = (3 - cell) * CELL_SIZE, - destX = topLeftX + BORDER_WIDTH + MARGIN + j * (CELL_SIZE + MARGIN), - destY = topLeftY + BORDER_WIDTH + MARGIN + i * (CELL_SIZE + MARGIN), - width = CELL_SIZE, - height = CELL_SIZE) - } - } - - private fun drawInfo() { - drawInt(labelSrcX = LINES_LABEL_WIDTH, - labelSrcY = CELLS_HEIGHT, - labelDestX = fieldWidth + SYMBOL_SIZE, - labelDestY = getInfoY(0), - labelWidth = SCORE_LABEL_WIDTH, - totalDigits = 6, - value = score) - drawInt(labelSrcX = 0, - labelSrcY = CELLS_HEIGHT, - labelDestX = fieldWidth + SYMBOL_SIZE, - labelDestY = getInfoY(1), - labelWidth = LINES_LABEL_WIDTH, - totalDigits = 3, - value = linesCleared) - drawInt(labelSrcX = 0, - labelSrcY = CELLS_HEIGHT + SYMBOL_SIZE, - labelDestX = fieldWidth + SYMBOL_SIZE, - labelDestY = getInfoY(2), - labelWidth = LEVEL_LABEL_WIDTH, - totalDigits = 2, - value = level) - drawInt(labelSrcX = 0, - labelSrcY = CELLS_HEIGHT + SYMBOL_SIZE * 2, - labelDestX = fieldWidth + SYMBOL_SIZE, - labelDestY = getInfoY(3), - labelWidth = TETRISES_LABEL_WIDTH, - totalDigits = 2, - value = tetrises) - } - - private fun getInfoY(line: Int): Int { - return SYMBOL_SIZE * (2 * line + 1) + INFO_MARGIN * line - } - - private fun drawInt(labelSrcX: Int, labelSrcY: Int, labelDestX: Int, labelDestY: Int, - labelWidth: Int, totalDigits: Int, value: Int) { - copyRect(srcX = labelSrcX, - srcY = labelSrcY, - destX = labelDestX, - destY = labelDestY, - width = labelWidth, - height = SYMBOL_SIZE) - val digits = IntArray(totalDigits) - var x = value - for (i in 0..totalDigits - 1) { - digits[totalDigits - 1 - i] = x % 10 - x = x / 10 - } - for (i in 0..totalDigits - 1) { - copyRect(srcX = digits[i] * SYMBOL_SIZE, - srcY = CELLS_HEIGHT + 3 * SYMBOL_SIZE, - destX = labelDestX + SYMBOL_SIZE + i * SYMBOL_SIZE, - destY = labelDestY + SYMBOL_SIZE, - width = SYMBOL_SIZE, - height = SYMBOL_SIZE) - } - } - - private fun drawGamePad() { - if (gamePadButtons == null) return - SDL_SetRenderDrawColor(renderer, 127, 127, 127, SDL_ALPHA_OPAQUE.toByte()) - fillRect(gamePadButtons.leftRect) - fillRect(gamePadButtons.downRect) - fillRect(gamePadButtons.dropRect) - fillRect(gamePadButtons.rightRect) - fillRect(gamePadButtons.rotateRect) - SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE.toByte()) - } - - private fun fillRect(rect: SDL_Rect) { - memScoped { - val stretchedRect = alloc() - stretchedRect.w = stretch(rect.w) - stretchedRect.h = stretch(rect.h) - stretchedRect.x = stretch(rect.x) - stretchedRect.y = stretch(rect.y) - SDL_RenderFillRect(renderer, stretchedRect.ptr.reinterpret()) - } - } - - private fun copyRect(srcX: Int, srcY: Int, destX: Int, destY: Int, width: Int, height: Int) { - memScoped { - val srcRect = alloc() - val destRect = alloc() - srcRect.w = width - srcRect.h = height - srcRect.x = srcX - srcRect.y = srcY - destRect.w = stretch(width) - destRect.h = stretch(height) - destRect.x = stretch(destX) - destRect.y = stretch(destY) - SDL_RenderCopy(renderer, texture, srcRect.ptr.reinterpret(), destRect.ptr.reinterpret()) - } - } - - override fun readCommands(): List { - val commands = mutableListOf() - memScoped { - val event = alloc() - while (SDL_PollEvent(event.ptr.reinterpret()) != 0) { - val eventType = event.type - when (eventType) { - SDL_QUIT -> commands.add(UserCommand.EXIT) - SDL_KEYDOWN -> { - val keyboardEvent = event.ptr.reinterpret().pointed - when (keyboardEvent.keysym.scancode) { - SDL_SCANCODE_LEFT -> commands.add(UserCommand.LEFT) - SDL_SCANCODE_RIGHT -> commands.add(UserCommand.RIGHT) - SDL_SCANCODE_DOWN -> commands.add(UserCommand.DOWN) - SDL_SCANCODE_Z, SDL_SCANCODE_SPACE -> commands.add(UserCommand.ROTATE) - SDL_SCANCODE_UP -> commands.add(UserCommand.DROP) - SDL_SCANCODE_ESCAPE -> commands.add(UserCommand.EXIT) - } - } - SDL_MOUSEBUTTONDOWN -> if (gamePadButtons != null) { - val mouseEvent = event.ptr.reinterpret().pointed - val x = mouseEvent.x - val y = mouseEvent.y - val command = gamePadButtons.getCommandAt(x, y) - if (command != null) - commands.add(command) - } - } - } - } - return commands - } - - fun destroy() { - SDL_DestroyTexture(texture) - SDL_DestroyRenderer(renderer) - SDL_DestroyWindow(window) - SDL_Quit() - gamePadButtons?.destroy() - } -} - -fun main(args: Array) { - var startLevel = 0 - var width = 10 - var height = 20 - when (args.size) { - 1 -> startLevel = args[0].toInt() - 2 -> { - width = args[0].toInt() - height = args[1].toInt() - } - 3 -> { - width = args[0].toInt() - height = args[1].toInt() - startLevel = args[2].toInt() - } - } - val visualizer = SDL_Visualizer(width, height) - val game = Game(width, height, visualizer, visualizer) - game.startNewGame(startLevel) - - return -} diff --git a/samples/tetris/src/main/kotlin/main.kt b/samples/tetris/src/main/kotlin/main.kt new file mode 100644 index 00000000000..1093cd51e88 --- /dev/null +++ b/samples/tetris/src/main/kotlin/main.kt @@ -0,0 +1,39 @@ +/** + * Copyright 2010-2018 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import tetris.* + +fun main(args: Array) { + var startLevel = Config.startLevel + var width = Config.width + var height = Config.height + when (args.size) { + 1 -> startLevel = args[0].toInt() + 2 -> { + width = args[0].toInt() + height = args[1].toInt() + } + 3 -> { + width = args[0].toInt() + height = args[1].toInt() + startLevel = args[2].toInt() + } + } + 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/samples/tetris/src/main/resources/config.txt b/samples/tetris/src/main/resources/config.txt new file mode 100644 index 00000000000..da8f7d7d673 --- /dev/null +++ b/samples/tetris/src/main/resources/config.txt @@ -0,0 +1,3 @@ +width = 10 +height = 30 +startLevel = 0 \ No newline at end of file