+1
-1
build.gradle.kts
+1
-1
build.gradle.kts
+15
flake.nix
+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
+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
-1
src/nativeMain/kotlin/uwu/serenity/tuipong/games/maze/Maze.kt
+2
-1
src/nativeMain/kotlin/uwu/serenity/tuipong/games/maze/MazeLoadingScreen.kt
+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
+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
+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
+
}