Arindy 4da0d7b1f0
All checks were successful
CI / deploy (push) Successful in 5m39s
CI / deploy (pull_request) Successful in 5m38s
caps the number of allowed dice
2025-02-22 13:53:49 +01:00

185 lines
6.6 KiB
Kotlin

package de.arindy.dicetower
import io.quarkus.runtime.annotations.RegisterForReflection
import jakarta.enterprise.context.ApplicationScoped
import jakarta.ws.rs.Consumes
import jakarta.ws.rs.GET
import jakarta.ws.rs.POST
import jakarta.ws.rs.Path
import jakarta.ws.rs.PathParam
import jakarta.ws.rs.Produces
import jakarta.ws.rs.core.Context
import jakarta.ws.rs.core.MediaType
import jakarta.ws.rs.sse.OutboundSseEvent
import jakarta.ws.rs.sse.Sse
import jakarta.ws.rs.sse.SseBroadcaster
import jakarta.ws.rs.sse.SseEventSink
import org.eclipse.microprofile.config.inject.ConfigProperty
import java.util.Optional
import java.util.UUID
private const val LIMIT = 30
@Path("/dice/{id}")
@ApplicationScoped
final class DiceResource(@Context val sse: Sse) {
@ConfigProperty(name = "dice.limit")
private lateinit var diceLimit: Optional<Int>
private var eventBuilder: OutboundSseEvent.Builder = sse.newEventBuilder()
private var sseBroadcasters: MutableMap<String, SseBroadcaster> = HashMap()
@POST
@Consumes(MediaType.APPLICATION_JSON)
fun parseCommand(@PathParam("id") id: String, data: RollPayload) {
data.room = id.split(":")[0]
data.user = id.split(":")[1]
val results = ArrayList<Results>()
var numberOfDice = 0
data.command.split(" ", "&", "and").filter { it.isNotEmpty() }.map { it.trim() }.toTypedArray<String>().forEach { command ->
val dice = command.split("d")
var amount = dice[0].toInt()
val limit = diceLimit.orElse(LIMIT)
if (limit < numberOfDice + amount) {
amount = limit - numberOfDice
}
numberOfDice += amount
if (amount > 0) {
val result = IntArray(amount)
val sides = dice[1].split("+", "-")
val modifier = if (dice[1].contains("+")) sides[1].toInt() else if (dice[1].contains("+")) -1 * sides[1].toInt() else 0
repeat(amount) { index ->
result[index] = (Math.random() * sides[0].toInt() + 1).toInt()
}
results.add(Results(sides[0].toInt(), modifier, result.sum() + modifier, result.map { Roll(it) }.toTypedArray()))
}
}
val map = results.map { r ->
"${r.rolls.size}d${r.sides}@${
if (r.sides == 100) r.rolls.map { roll -> Roll(roll.value / 10 * 10) }
.joinToString(",") else r.rolls.joinToString(",")
}"
}.toTypedArray()
data.roll = map + results.filter { it.sides == 100 }.map { r -> "${r.rolls.size}d10@${r.rolls.map { roll -> Roll(roll.value % 10) }.joinToString(",")}"}.toTypedArray()
sseBroadcasters[id]?.broadcast(
eventBuilder.id((UUID.randomUUID()).toString())
.mediaType(MediaType.APPLICATION_JSON_TYPE).data(data).build()
)
if (data.roll.all { it.trim().isNotEmpty() }) {
results(data.room!!, Result(data.name, data.user!!, data.faceColor, null))
}
Thread.sleep(1000)
results(data.room!!, Result(data.name, data.user!!, data.faceColor, results.toTypedArray()))
}
@POST
@Path("/register")
@Consumes(MediaType.APPLICATION_JSON)
fun register(@PathParam("id") id: String, data: Any) {
sseBroadcasters["register:$id"]?.broadcast(
eventBuilder.id((UUID.randomUUID()).toString())
.mediaType(MediaType.APPLICATION_JSON_TYPE).data(data).build())
}
@POST
@Path("/results")
@Consumes(MediaType.APPLICATION_JSON)
fun results(@PathParam("id") id: String, data: Any) {
sseBroadcasters[id]?.broadcast(
eventBuilder.id((UUID.randomUUID()).toString())
.mediaType(MediaType.APPLICATION_JSON_TYPE).data(data).build()
)
}
@GET
@Path("/stream")
@Produces(MediaType.SERVER_SENT_EVENTS)
fun stream(@PathParam("id") id: String, @Context sseEventSink: SseEventSink) {
if (!sseBroadcasters.containsKey(id)) {
sseBroadcasters[id] = sse.newBroadcaster()
}
sseBroadcasters[id]?.register(sseEventSink)
}
@GET
@Path("/results")
@Produces(MediaType.SERVER_SENT_EVENTS)
fun results(@PathParam("id") id: String, @Context sseEventSink: SseEventSink) {
if (!sseBroadcasters.containsKey(id)) {
sseBroadcasters[id] = sse.newBroadcaster()
}
sseBroadcasters[id]?.register(sseEventSink)
}
@GET
@Path("/users")
@Produces(MediaType.SERVER_SENT_EVENTS)
fun users(@PathParam("id") id: String, @Context sseEventSink: SseEventSink) {
if (!sseBroadcasters.containsKey("register:$id")) {
sseBroadcasters["register:$id"] = sse.newBroadcaster()
}
sseBroadcasters["register:$id"]?.register(sseEventSink)
}
@RegisterForReflection
data class Result(val name: String, val user: String, val faceColor: String, val results: Array<Results>?) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Result
if (name != other.name) return false
if (user != other.user) return false
if (faceColor != other.faceColor) return false
if (results != null) {
if (other.results == null) return false
if (!results.contentEquals(other.results)) return false
} else if (other.results != null) return false
return true
}
override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + user.hashCode()
result = 31 * result + faceColor.hashCode()
result = 31 * result + (results?.contentHashCode() ?: 0)
return result
}
}
@RegisterForReflection
data class Results(val sides: Int, val modifier: Int, val value: Int, val rolls: Array<Roll>) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Results
if (sides != other.sides) return false
if (modifier != other.modifier) return false
if (value != other.value) return false
if (!rolls.contentEquals(other.rolls)) return false
return true
}
override fun hashCode(): Int {
var result = sides
result = 31 * result + modifier
result = 31 * result + value
result = 31 * result + rolls.contentHashCode()
return result
}
}
@RegisterForReflection
data class Roll(val value: Int)
}