Kinda badly made tui games in kotlin native because riley is cool

paws

sery 72f6fc48 6c2b3303

Changed files
+94 -54
src
nativeMain
kotlin
uwu
serenity
tuipong
+1 -1
build.gradle.kts
··· 30 30 } 31 31 32 32 compilerOptions { 33 - freeCompilerArgs.addAll("-Xcontext-parameters", "-XXLanguage:+UnnamedLocalVariables") 33 + freeCompilerArgs.addAll("-Xcontext-parameters", "-XXLanguage:+UnnamedLocalVariables", "-XXLanguage:+ValueClasses") 34 34 } 35 35 }
+15
flake.nix
··· 1 + { 2 + description = "A very basic flake"; 3 + 4 + inputs = { 5 + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; 6 + }; 7 + 8 + outputs = { self, nixpkgs }: { 9 + 10 + packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello; 11 + 12 + packages.x86_64-linux.default = self.packages.x86_64-linux.hello; 13 + 14 + }; 15 + }
+3 -6
src/nativeMain/kotlin/Main.kt
··· 9 9 import uwu.serenity.tuipong.GlobalOptions 10 10 import uwu.serenity.tuipong.START_MARK 11 11 import uwu.serenity.tuipong.games.maze.MazeSetupScreen 12 + import uwu.serenity.tuipong.games.pong.PongSetupScreen 12 13 import uwu.serenity.tuipong.globalOptions 13 14 import uwu.serenity.tuipong.render.Renderer 14 15 import uwu.serenity.tuipong.terminal.Console ··· 22 23 fun main(args: Array<String>) { 23 24 START_MARK 24 25 val options = Properties.decodeFromStringMap<GlobalOptions>(parseArgs(args)) 25 - val ctx = when (options.threads) { 26 - 1 -> EmptyCoroutineContext 27 - -1 -> Dispatchers.Default 28 - else -> newFixedThreadPoolContext(options.threads, "Main") 29 - } 30 26 globalOptions = options 31 27 Console().use { 32 28 it.hideCursor() 33 29 Renderer.init(it) 34 30 Renderer.frameLimit = if (options.unlimited) 0 else options.framelimit 35 31 Renderer.setScreen(MazeSetupScreen()) 36 - runBlocking { mainJob = launch(ctx) { Renderer.run() } } 32 + Dispatchers.Default 33 + runBlocking { mainJob = launch { Renderer.run() } } 37 34 } 38 35 } 39 36
-1
src/nativeMain/kotlin/uwu/serenity/tuipong/games/maze/Maze.kt
··· 113 113 114 114 private inner class Torch(override val x: Int, override val y: Int) : Renderable, LightSource { 115 115 116 - 117 116 var ticksToLive: Int = 0 118 117 119 118 override var needsRedraw: Boolean = false
+2 -1
src/nativeMain/kotlin/uwu/serenity/tuipong/games/maze/MazeLoadingScreen.kt
··· 8 8 import uwu.serenity.tuipong.render.Renderer 9 9 import uwu.serenity.tuipong.render.Screen 10 10 import kotlin.math.min 11 + import kotlin.random.Random 11 12 12 13 class MazeLoadingScreen(private val options: MazeOptions) : Screen { 13 14 ··· 19 20 val size = min(Renderer.width, Renderer.height) - 2 20 21 val startPoint = 0 21 22 22 - val seed = parseSeed(options.seed) ?: START_MARK.elapsedNow().inWholeNanoseconds 23 + val seed = parseSeed(options.seed) ?: Random.nextLong() 23 24 val gen = MazeGenerator(MazeGenerator.Cell(startPoint, startPoint), size / 2, seed) 24 25 25 26 val scope = CoroutineScope(BACKGROUND + SupervisorJob())
+26 -37
src/nativeMain/kotlin/uwu/serenity/tuipong/games/pong/Pong.kt
··· 6 6 import uwu.serenity.tuipong.terminal.Ansi 7 7 import uwu.serenity.tuipong.terminal.Formatting 8 8 import uwu.serenity.tuipong.terminal.TerminalColor 9 - import kotlin.math.pow 9 + import uwu.serenity.tuipong.utils.Vec2 10 10 import kotlin.math.roundToInt 11 - import kotlin.math.sqrt 12 11 13 12 class Pong(val options: PongOptions) : Screen { 14 13 15 14 private val leftPaddle = Paddle(false) 16 15 private val rightPaddle: Paddle? = if (!options.singleplayer) Paddle(true) else null 17 - private val ball = Ball(Renderer.width / 2.0, Renderer.height / 2.0) 16 + private val ball = Ball(Vec2(Renderer.width / 2.0, Renderer.height / 2.0)) 18 17 private val score = Score() 19 18 private var lScore = 0 20 19 set(value) { ··· 43 42 override suspend fun tick() { 44 43 val ball = ball 45 44 46 - if (ball.xPos > Renderer.width - 2) 45 + if (ball.pos.x > Renderer.width - 2) 47 46 if (!options.singleplayer) { 48 47 scored(false) 49 48 lScore++ 50 - } else ball.xVelocity = -ball.xVelocity 49 + } else ball.velocity = ball.velocity.with(x = -ball.velocity.x) 51 50 52 - if (ball.xPos < 1) { 51 + if (ball.pos.x < 1) { 53 52 scored(true) 54 53 rScore++ 55 54 } 56 55 57 - val li = leftPaddle.instersect(ball.xPos, ball.yPos, ball.xVelocity, ball.yVelocity) 58 - val ri = rightPaddle?.instersect(ball.xPos, ball.yPos, ball.xVelocity, ball.yVelocity) 56 + val li = leftPaddle.instersect(ball.pos, ball.velocity) 57 + val ri = rightPaddle?.instersect(ball.pos, ball.velocity) 59 58 ?: Paddle.NO_INTERSECTION 60 59 61 60 if (li != Paddle.NO_INTERSECTION || ri != Paddle.NO_INTERSECTION) { 62 61 val n = ri != Paddle.NO_INTERSECTION 63 62 val i = if (n) ri else li 64 63 65 - val xs = if (n) -5.0 else 5.0 66 - val ys = i 67 - val l = 1.0 / sqrt(xs.pow(2) + ys.pow(2)) 68 - ball.xVelocity = (xs * l) * (options.speed * 2.0) 69 - ball.yVelocity = -((ys * l) * (options.speed * 2.0)) 64 + ball.velocity = Vec2(if (n) -5.0 else 5.0, -i).normalize() * (options.speed * 2.0) 70 65 } 71 66 72 - if (ball.yPos > Renderer.height - 2 || ball.yPos < 1.0) 73 - ball.yVelocity = -ball.yVelocity 67 + if (ball.pos.y > Renderer.height - 2 || ball.pos.y < 1.0) 68 + ball.velocity = ball.velocity.with(y = -ball.velocity.y) 74 69 75 - val lastXPos = ball.xPos.roundToInt() 76 - val lastYPos = ball.yPos.roundToInt() 70 + val lastPos = ball.pos.roundToInt() 77 71 78 - ball.xPos += ball.xVelocity 79 - ball.yPos += ball.yVelocity 72 + ball.pos += ball.velocity 80 73 81 - if (lastXPos != ball.xPos.roundToInt() || lastYPos != ball.yPos.roundToInt()) 82 - ball.needsRedraw = true 74 + if (lastPos != ball.pos.roundToInt()) ball.needsRedraw = true 83 75 } 84 76 85 77 private fun scored(positiveInitialVelocity: Boolean) { 86 - ball.xPos = Renderer.width / 2.0 87 - ball.yPos = Renderer.height / 2.0 88 - ball.xVelocity = if (positiveInitialVelocity) options.speed else -options.speed 89 - ball.yVelocity = -options.speed 78 + ball.pos = Vec2(Renderer.width / 2.0, Renderer.height / 2.0) 79 + ball.velocity = Vec2(if (positiveInitialVelocity) options.speed else -options.speed, options.speed) 90 80 score.needsRedraw = true 91 81 } 92 82 ··· 123 113 needsRedraw = false 124 114 } 125 115 126 - fun instersect(x: Double, y: Double, xVel: Double, yVel: Double): Double { 127 - val nextX = x + xVel 128 - val nextY = y + yVel 129 - val direct = if (right) x in (this.x-1.0..Renderer.width.toDouble()) else x in (0.0..this.x+1.0) 130 - val indirect = if (right) nextX in (this.x-1.0..Renderer.width.toDouble()) else nextX in (0.0..this.x+1.0) 116 + fun instersect(pos: Vec2, velocity: Vec2): Double { 117 + val next = pos + velocity 118 + val direct = if (right) pos.x in (this.x-1.0..Renderer.width.toDouble()) else pos.x in (0.0..this.x+1.0) 119 + val indirect = if (right) next.x in (this.x-1.0..Renderer.width.toDouble()) else next.x in (0.0..this.x+1.0) 131 120 132 - return if ((y in pos.toDouble()..(pos + 4.0) || (nextY in pos.toDouble()..(pos + 4.0))) 133 - && (direct || indirect)) (this.pos + 2.0) - y else NO_INTERSECTION 121 + val d = this.pos.toDouble() 122 + return if ((pos.y in d..(d + 4.0) || (next.y in d..(d + 4.0))) && (direct || indirect)) (this.pos + 2.0) - pos.y 123 + else NO_INTERSECTION 134 124 } 135 125 136 126 companion object { ··· 138 128 } 139 129 } 140 130 141 - private inner class Ball(var xPos: Double, var yPos: Double) : Renderable { 131 + private inner class Ball(var pos: Vec2) : Renderable { 142 132 143 - var xVelocity: Double = -options.speed 144 - var yVelocity: Double = -options.speed 133 + var velocity = Vec2(-options.speed, -options.speed) 145 134 146 135 override var needsRedraw: Boolean = true 147 136 148 137 override fun render(context: RenderContext) { 149 - val x = xPos.roundToInt(); val y = yPos.roundToInt() 138 + val x = pos.x.roundToInt(); val y = pos.y.roundToInt() 150 139 if (x in 0..<context.width && y in 0..<context.height) 151 140 context.drawChar(x, y, '⬤') 152 141 ··· 199 188 context.drawString(0, 0, """ 200 189 ${FrameCounter.fps} FPS 201 190 options: $options 202 - ball: x=${ball.xPos} y=${ball.yPos} xVel=${ball.xVelocity} yVel=${ball.yVelocity} 191 + ball: position=${ball.pos} velocity=${ball.velocity} 203 192 paddleL: pos=${leftPaddle.pos} paddleR: pos=${rightPaddle?.pos} 204 193 """.trimIndent(), Formatting.Bold, Formatting.Invert) 205 194 }
+47 -8
src/nativeMain/kotlin/uwu/serenity/tuipong/utils/Dimensions.kt
··· 1 1 package uwu.serenity.tuipong.utils 2 2 3 + import kotlin.math.roundToInt 3 4 import kotlin.math.sqrt 4 5 5 - data class Dimensions(val x: Int, val y: Int, val width: Int, val height: Int) 6 - 7 - value class Vec2i internal constructor(@PublishedApi internal val packed: Long) { 8 - 9 - constructor(x: Int, y: Int) : this ((x.toLong() shl 32) or (y.toLong() and -1L)) 10 - 11 - inline val x: Int get() = (packed and UInt.MAX_VALUE.toLong()).toInt() 6 + value class Dimensions(val x: Int, val y: Int, val width: Int, val height: Int) 12 7 13 - inline val y: Int get() = ((packed ushr 32) and UInt.MAX_VALUE.toLong()).toInt() 8 + value class Vec2i(val x: Int, val y: Int) { 14 9 15 10 operator fun minus(other: Vec2i) = Vec2i(this.x - other.x, this.y - other.y) 16 11 17 12 operator fun plus(other: Vec2i) = Vec2i(this.x + other.x, this.y - other.y) 18 13 14 + operator fun plus(value: Int) = Vec2i(this.x + value, this.y + value) 15 + 19 16 operator fun times(other: Vec2i) = Vec2i(this.x * other.x, this.y * other.y) 20 17 18 + operator fun times(value: Int) = Vec2i(this.x * value, this.y * value) 19 + 21 20 operator fun div(other: Vec2i) = Vec2i(this.x / other.x, this.y / other.y) 22 21 23 22 operator fun rem(other: Vec2i) = Vec2i(this.x % other.x, this.y % other.y) ··· 32 31 33 32 inline operator fun component2() = y 34 33 } 34 + 35 + value class Vec2(val x: Double, val y: Double) { 36 + 37 + constructor(value: Double) : this(value, value) 38 + 39 + operator fun minus(other: Vec2) = Vec2(this.x - other.x, this.y - other.y) 40 + 41 + operator fun minus(value: Double) = Vec2(this.x - value, this.y - value) 42 + 43 + operator fun plus(other: Vec2) = Vec2(this.x + other.x, this.y + other.y) 44 + 45 + operator fun plus(value: Double) = Vec2(this.x + value, this.y + value) 46 + 47 + operator fun times(other: Vec2) = Vec2(this.x * other.x, this.y * other.y) 48 + 49 + operator fun times(value: Double) = Vec2(this.x * value, this.y * value) 50 + 51 + operator fun div(other: Vec2) = Vec2(this.x / other.x, this.y / other.y) 52 + 53 + operator fun rem(other: Vec2) = Vec2(this.x % other.x, this.y % other.y) 54 + 55 + operator fun inc(): Vec2 = Vec2(this.x + 1, this.y + 1) 56 + 57 + operator fun dec(): Vec2 = Vec2(this.x - 1, this.y - 1) 58 + 59 + fun length(): Double = sqrt(x * x + y * y) 60 + 61 + fun normalize(): Vec2 { 62 + val l = 1.0 / length() 63 + return Vec2(x * l, y * l) 64 + } 65 + 66 + fun with(x: Double = this.x, y: Double = this.y) = Vec2(x, y) 67 + 68 + fun roundToInt(): Vec2i = Vec2i(x.roundToInt(), y.roundToInt()) 69 + 70 + inline operator fun component1() = x 71 + 72 + inline operator fun component2() = y 73 + }