diff --git a/gradle.properties b/gradle.properties index 147051f..54ce473 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,5 +3,5 @@ org.gradle.downloadSources=false org.gradle.parallel=true org.gradle.degree_of_parallelism=16 project_group=top.r3944realms.ltdmanager -project_version=1.16-SNAPSHOT -dg_lab_version=4.4.14.18 +project_version=1.17-SNAPSHOT +dg_lab_version=4.4.14.19 diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/chevereto/CheveretoClient.kt b/src/main/kotlin/top/r3944realms/ltdmanager/chevereto/CheveretoClient.kt index 50ff3dc..4269187 100644 --- a/src/main/kotlin/top/r3944realms/ltdmanager/chevereto/CheveretoClient.kt +++ b/src/main/kotlin/top/r3944realms/ltdmanager/chevereto/CheveretoClient.kt @@ -5,6 +5,7 @@ import io.ktor.client.call.* import io.ktor.client.engine.cio.* import io.ktor.client.plugins.* import io.ktor.client.request.* +import io.ktor.client.request.forms.* import io.ktor.http.* import kotlinx.coroutines.* import kotlinx.coroutines.sync.Mutex @@ -104,15 +105,60 @@ class CheveretoClient private constructor() : method = request.method() - // 设置请求头 - headers { - request.headers().invoke(this) - header("X-API-Key", apiKey) - } + when (request) { + is CheveretoUploadRequest -> { + // 使用 multipart/form-data 格式上传文件 + setBody(MultiPartFormDataContent( + formData { + // API Key + append("key", apiKey) - // 对于非GET请求,设置请求体 - if (request.method() != HttpMethod.Get) { - setBody(request.toJSON()) + // 处理 source + when (val source = request.source) { + is CheveretoSource.ByteArraySource -> { + LoggerUtil.logger.debug("上传文件: ${source.fileName}, 大小: ${source.bytes.size} bytes") + + append("source", source.bytes, Headers.build { + append(HttpHeaders.ContentType, "image/png") + append(HttpHeaders.ContentDisposition, "form-data; name=\"source\"; filename=\"${source.fileName}\"") + }) + } + is CheveretoSource.UrlSource -> { + append("source", source.url) + } + } + + // 添加所有参数 + request.title?.let { append("title", it) } + request.description?.let { append("description", it) } + request.tags?.let { append("tags", it) } + request.albumId?.let { append("album_id", it) } + request.categoryId?.let { append("category_id", it) } + request.width?.let { append("width", it.toString()) } + request.expiration?.let { append("expiration", it) } + request.nsfw?.let { append("nsfw", it.toString()) } + append("format", request.format) + request.useFileDate?.let { append("use_file_date", it.toString()) } + } + )) + + // 设置请求头 + headers { + append(HttpHeaders.Accept, "application/json") + append("X-API-Key", apiKey) + } + } + else -> { + // 其他请求使用 JSON 格式 + headers { + request.headers().invoke(this) + header("X-API-Key", apiKey) + } + + if (request.method() != HttpMethod.Get) { + setBody(request.toJSON()) + } + } } } val responseText: String = response.body() @@ -173,7 +219,7 @@ class CheveretoClient private constructor() : maxRetries: Int = 3 ): CheveretoResponse { - upload(CheveretoUploadRequest( + return upload(CheveretoUploadRequest( source = CheveretoSource.ByteArraySource(file.readBytes(), file.name), format = format, title = title, @@ -186,7 +232,6 @@ class CheveretoClient private constructor() : nsfw = nsfw, useFileDate = useFileDate ), priority, maxRetries).getRetResponse() - throw Exception("Never Reach") } @@ -209,8 +254,24 @@ class CheveretoClient private constructor() : priority: Int = 5, maxRetries: Int = 3 ): CheveretoResponse { - upload(CheveretoUploadRequest( - source = CheveretoSource.ByteArraySource(inputStream.readBytes(), fileName), + val bytes = if (inputStream.markSupported()) { + inputStream.mark(Integer.MAX_VALUE) + val b = inputStream.readBytes() + inputStream.reset() + b + } else { + // 如果不支持 mark,直接读取 + inputStream.readBytes() + } + + if (bytes.isEmpty()) { + throw IllegalStateException("InputStream is empty for file: $fileName") + } + + LoggerUtil.logger.debug("Uploading ${bytes.size} bytes for $fileName") + + return upload(CheveretoUploadRequest( + source = CheveretoSource.ByteArraySource(bytes, fileName), format = format, title = title, description = description, @@ -222,7 +283,6 @@ class CheveretoClient private constructor() : nsfw = nsfw, useFileDate = useFileDate ), priority, maxRetries).getRetResponse() - throw Exception("Never Reach") } /** @@ -243,7 +303,7 @@ class CheveretoClient private constructor() : priority: Int = 5, maxRetries: Int = 3 ): CheveretoResponse { - upload(CheveretoUploadRequest( + return upload(CheveretoUploadRequest( source = CheveretoSource.UrlSource(url), format = format, title = title, @@ -256,7 +316,6 @@ class CheveretoClient private constructor() : nsfw = nsfw, useFileDate = useFileDate ), priority, maxRetries).getRetResponse() - throw Exception("Never Reach") } suspend fun upload( diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/chevereto/data/CheveretoImage.kt b/src/main/kotlin/top/r3944realms/ltdmanager/chevereto/data/CheveretoImage.kt index 482eaa1..cb95f3a 100644 --- a/src/main/kotlin/top/r3944realms/ltdmanager/chevereto/data/CheveretoImage.kt +++ b/src/main/kotlin/top/r3944realms/ltdmanager/chevereto/data/CheveretoImage.kt @@ -19,7 +19,10 @@ data class CheveretoImage( val nsfw: Int, @SerialName("storage_mode") val storageMode: String, - val md5: String, + val checksum: String? = null, + @SerialName("source_checksum") + val sourceChecksum: String? = null, + val md5: String? = null, @SerialName("source_md5") val sourceMd5: String? = null, @SerialName("original_filename") diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/chevereto/request/v1/CheveretoUploadRequest.kt b/src/main/kotlin/top/r3944realms/ltdmanager/chevereto/request/v1/CheveretoUploadRequest.kt index a3a0e26..cdbedd6 100644 --- a/src/main/kotlin/top/r3944realms/ltdmanager/chevereto/request/v1/CheveretoUploadRequest.kt +++ b/src/main/kotlin/top/r3944realms/ltdmanager/chevereto/request/v1/CheveretoUploadRequest.kt @@ -13,20 +13,17 @@ import top.r3944realms.ltdmanager.core.client.response.ResponseResult @Serializable data class CheveretoUploadRequest( - private val source: CheveretoSource, - private val title: String? = null, - private val description: String? = null, - private val tags: String? = null, - @SerialName("album_id") - private val albumId: String? = null, - @SerialName("category_id") - private val categoryId: String? = null, - private val width: Int? = null, - private val expiration: String? = null, - private val nsfw: Int? = null, - private val format: String = "json", - @SerialName("use_file_date") - private val useFileDate: Int? = null + val source: CheveretoSource, + val title: String? = null, + val description: String? = null, + val tags: String? = null, + @SerialName("album_id") val albumId: String? = null, + @SerialName("category_id") val categoryId: String? = null, + val width: Int? = null, + val expiration: String? = null, + val nsfw: Int? = null, + val format: String = "json", + @SerialName("use_file_date") val useFileDate: Int? = null ) : CheveretoRequest() { override fun path(): String = "api/1/upload" diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/core/config/DgLabConfig.kt b/src/main/kotlin/top/r3944realms/ltdmanager/core/config/DgLabConfig.kt index 7bebb90..bfc2705 100644 --- a/src/main/kotlin/top/r3944realms/ltdmanager/core/config/DgLabConfig.kt +++ b/src/main/kotlin/top/r3944realms/ltdmanager/core/config/DgLabConfig.kt @@ -6,6 +6,7 @@ import top.r3944realms.ltdmanager.utils.YamlUpdater data class DgLabConfig( var wsServer: WsServerConfig = WsServerConfig(), + var imgAlbumsId: String = "", var dgLabClient: DgLabClientConfig = DgLabClientConfig(), var pulseData: PulseDataConfig = PulseDataConfig(), var commandText: CommandTextConfig = CommandTextConfig(), diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/core/config/ModuleConfig.kt b/src/main/kotlin/top/r3944realms/ltdmanager/core/config/ModuleConfig.kt index f59ff37..740463a 100644 --- a/src/main/kotlin/top/r3944realms/ltdmanager/core/config/ModuleConfig.kt +++ b/src/main/kotlin/top/r3944realms/ltdmanager/core/config/ModuleConfig.kt @@ -160,14 +160,22 @@ data class ModuleConfig( fun float(paramName: String): Float = get(paramName) // 可选值方法 - inline fun getOrNull(paramName: String): T? = - config[paramName] as? T ?: run { - val value = config[paramName] - if (value == null) null - else if (value is T) value - else null - } + inline fun getOrNull(paramName: String): T? { + val value = config[paramName] ?: return null + return when { + value is T -> value + T::class == Long::class && value is Int -> value.toLong() as T + T::class == Long::class && value is String -> value.toLongOrNull() as? T + T::class == Int::class && value is Long -> value.toInt() as T + T::class == Int::class && value is String -> value.toIntOrNull() as? T + T::class == String::class -> value.toString() as T + T::class == Boolean::class && value is String -> value.toBoolean() as T + T::class == Double::class && value is Number -> value.toDouble() as T + T::class == Float::class && value is Number -> value.toFloat() as T + else -> null + } + } inline fun getOrDefault(paramName: String, defaultValue: T): T = getOrNull(paramName) ?: defaultValue diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/DgLab.kt b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/DgLab.kt index d436c76..c415a07 100644 --- a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/DgLab.kt +++ b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/DgLab.kt @@ -121,31 +121,29 @@ class DgLab { fun getClient(key: String): DGPBClientManager? { return clientManager?.getClient(key) } - /** - * 获取 & 创建 客户端管理类 - */ - fun getClientOrCreate(key: String, operation: ClientOperation): DGPBClientManager { - val client = getClient(key) - if (client == null) { - val loadDgLabConfig = YamlConfigLoader.loadDgLabConfig() - val boxWSClient = PowerBoxWSClient.Builder.getBuilder() - .address(loadDgLabConfig.wsServer.localServerUrl) - .port(loadDgLabConfig.wsServer.localServerPort) - .role(WebSocketClientRole("QQ-$key")) - .operation(operation) - .sharedData(ClientPowerBoxSharedData()) - .useRoleMsgMode(true) - .build() - if (loadDgLabConfig.wsServer.localServerSecure) { - boxWSClient.enableSSL() - } - val clientManager = DGPBClientManager( - boxWSClient - ) - this.clientManager?.addClient(key, clientManager) - return clientManager + /** + * 创建 客户端管理类 + */ + fun createClient(key: String, operation: ClientOperation): DGPBClientManager { + getClient(key)?.stop() + val loadDgLabConfig = YamlConfigLoader.loadDgLabConfig() + val boxWSClient = PowerBoxWSClient.Builder.getBuilder() + .address(loadDgLabConfig.wsServer.localServerUrl) + .port(loadDgLabConfig.wsServer.localServerPort) + .role(WebSocketClientRole("QQ-$key")) + .operation(operation) + .sharedData(ClientPowerBoxSharedData()) + .useRoleMsgMode(true) + .build() + + if (loadDgLabConfig.wsServer.localServerSecure) { + boxWSClient.enableSSL() } - return client + val clientManager = DGPBClientManager( + boxWSClient + ) + addClient(key, clientManager) + return clientManager } } \ No newline at end of file diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/manager/ClientManager.kt b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/manager/ClientManager.kt index faff82f..7d075c5 100644 --- a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/manager/ClientManager.kt +++ b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/manager/ClientManager.kt @@ -6,6 +6,51 @@ class ClientManager( private val clients: MutableMap = mutableMapOf(), ) : IManager> { + /** + * 强度信息数据类 + */ + data class ClientStrengthInfo( + val aValue: Int, + val aMax: Int, + val bValue: Int, + val bMax: Int, + val timestamp: Long = System.currentTimeMillis() + ) { + fun getAStrength(): Int = aValue + fun getBStrength(): Int = bValue + fun getAStrengthPercent(): Double = if (aMax > 0) (aValue.toDouble() / aMax) * 100 else 0.0 + fun getBStrengthPercent(): Double = if (bMax > 0) (bValue.toDouble() / bMax) * 100 else 0.0 + + override fun toString(): String { + return "A: $aValue/$aMax (${String.format("%.1f", getAStrengthPercent())}%), B: $bValue/$bMax (${String.format("%.1f", getBStrengthPercent())}%)" + } + } + + // 存储每个客户端的强度信息 + private val clientStrengths: MutableMap = mutableMapOf() + + // 存储每个客户端最后更新时间 + private val lastUpdateTime: MutableMap = mutableMapOf() + + /** + * 获取客户端的强度信息 + * @param key 客户端标识 + * @return 强度信息,如果不存在则返回 null + */ + fun getClientStrength(key: String): ClientStrengthInfo? { + return clientStrengths[key] + } + + /** + * 更新客户端的强度信息 + * @param key 客户端标识 + * @param strengthInfo 强度信息 + */ + fun updateClientStrength(key: String, strengthInfo: ClientStrengthInfo) { + clientStrengths[key] = strengthInfo + lastUpdateTime[key] = System.currentTimeMillis() + } + /** * 添加单例客户端管理示例 * @param key 唯一标识客户端管理的 key,比如 ID 或 name @@ -19,6 +64,8 @@ class ClientManager( */ fun removeClient(key: String) { clients.remove(key)?.stop() + clientStrengths.remove(key) + lastUpdateTime.remove(key) } /** @@ -40,6 +87,7 @@ class ClientManager( */ override fun stopAll() { clients.values.forEach { it.stop() } + clients.clear() } /** diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/game/GameClientOperation.kt b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/game/GameClientOperation.kt index 917d62b..ed181de 100644 --- a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/game/GameClientOperation.kt +++ b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/game/GameClientOperation.kt @@ -1,16 +1,26 @@ package top.r3944realms.ltdmanager.dglab.model.game +import com.r3944realms.dg_lab.api.dataType.PowerBoxMsgType +import com.r3944realms.dg_lab.api.exception.NoMatchDataTypeException +import com.r3944realms.dg_lab.api.message.IPowerBoxMsg +import com.r3944realms.dg_lab.api.message.argType.ChangePolicy import com.r3944realms.dg_lab.api.operation.ClientOperation +import com.r3944realms.dg_lab.api.websocket.message.MessageDirection +import com.r3944realms.dg_lab.api.websocket.message.PowerBoxMessage import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxData import com.r3944realms.dg_lab.api.websocket.message.data.type.PowerBoxDataType import com.r3944realms.dg_lab.manager.DGPBClientManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.apache.logging.log4j.core.util.UuidUtil import top.r3944realms.ltdmanager.GlobalManager import top.r3944realms.ltdmanager.chevereto.response.FailedCheveretoResponse import top.r3944realms.ltdmanager.chevereto.response.v1.CheveretoUploadResponse +import top.r3944realms.ltdmanager.core.config.DgLabConfig import top.r3944realms.ltdmanager.core.config.YamlConfigLoader +import top.r3944realms.ltdmanager.dglab.DgLab +import top.r3944realms.ltdmanager.dglab.manager.ClientManager import top.r3944realms.ltdmanager.napcat.NapCatClient import top.r3944realms.ltdmanager.napcat.data.ID import top.r3944realms.ltdmanager.napcat.data.MessageElement @@ -19,6 +29,7 @@ import top.r3944realms.ltdmanager.napcat.request.other.SendPrivateMsgRequest import top.r3944realms.ltdmanager.utils.LoggerUtil import top.r3944realms.ltdmanager.utils.QRCodeUtil import java.io.ByteArrayInputStream +import java.util.UUID class GameClientOperation( @@ -28,10 +39,11 @@ class GameClientOperation( private val playerId: Long ) : ClientOperation { private val scope = CoroutineScope(Dispatchers.IO) - private var qrcode:ByteArrayInputStream? = null; + private var qrcode:ByteArrayInputStream? = null var clientSelf: DGPBClientManager? = null private var hasBinding = false private var bindingTimeoutJob: kotlinx.coroutines.Job? = null // 保存倒计时任务 + override fun ClientStartingHandler() { LoggerUtil.logger.debug("Player $playerId is starting the client...") scope.launch { @@ -124,34 +136,48 @@ class GameClientOperation( scope.launch { // 上传二维码图片 - val response = GlobalManager.cheveretoClient.uploadStream( - qrcode!!, - "$playerId-Qrcode-${System.currentTimeMillis()}.png", - "Qrcode-$playerId-${System.currentTimeMillis()}", - "5min后将会自动删除", - albumId = "BFx", - expiration = "PT5M" - ) - if (response is CheveretoUploadResponse){ + try { + val response = GlobalManager.cheveretoClient.uploadStream( + qrcode!!, + "$playerId-Qrcode-${System.currentTimeMillis()}.png", + "Qrcode-$playerId-${System.currentTimeMillis()}", + "5min后将会自动删除", + albumId = YamlConfigLoader.loadDgLabConfig().imgAlbumsId, + expiration = "PT5M" + ) + if (response is CheveretoUploadResponse){ + napCatClient.sendUnit( + SendPrivateMsgRequest( + listOf( + MessageElement.text("请在60s内绑定APP,否则将自动断开连接"), + MessageElement.image(response.image.url, "二维码") + ), + ID.long(playerId) + ) + ) + } else if (response is FailedCheveretoResponse.Default){ + napCatClient.sendUnit( + SendPrivateMsgRequest( + listOf( + MessageElement.text("无法上传图片,请联系管理员:${response.httpStatusCode} , ${response.failedMessage}"), + ), + ID.long(playerId) + ) + ) + clientSelf?.stop() + } + } catch (e: Exception) { napCatClient.sendUnit( SendPrivateMsgRequest( listOf( - MessageElement.text("请在60s内绑定APP,否则将自动断开连接"), - MessageElement.image(response.image.url, "二维码") - ), - ID.long(playerId) - ) - ) - } else if (response is FailedCheveretoResponse.Default){ - napCatClient.sendUnit( - SendPrivateMsgRequest( - listOf( - MessageElement.text("无法上传图片,请联系管理员:${response.httpStatusCode} , ${response.failedMessage}"), + MessageElement.text("无法上传图片,请联系管理员:${e.message}"), ), ID.long(playerId) ) ) + clientSelf?.stop() } + // 启动 60 秒倒计时任务 bindingTimeoutJob = launch { kotlinx.coroutines.delay(60_000) @@ -218,11 +244,35 @@ class GameClientOperation( // napCatClient.sendUnit(SendPrivateMsgRequest(listOf(MessageElement.text("连接断开, $p0")), ID.long(playerId))) // } when (p0?.commandType) { - PowerBoxDataType.STRENGTH -> TODO() - PowerBoxDataType.PULSE -> TODO() - PowerBoxDataType.CLEAR -> TODO() - PowerBoxDataType.FEEDBACK -> TODO() + PowerBoxDataType.STRENGTH -> scope.launch { +// val strengthInfo : IPowerBoxMsg.StrengthInfo +// +// strengthInfo = IPowerBoxMsg.StrengthInfo.read( +// PowerBoxMessage.createPowerBoxMessage( +// p0, +// MessageDirection.of( +// MessageDirection.DirectType.PLACEHOLDER_TO_PLACEHOLDER, +// ROM_UUID, +// ROM_UUID +// ) +// ) +// ) + +// napCatClient.sendUnit(SendPrivateMsgRequest(listOf(MessageElement.text("强度信息:\n A:${strengthInfo.aValue}/${strengthInfo.aMax} \n B:${strengthInfo.bValue}/${strengthInfo.bMax}")), ID.long(playerId))) + } + PowerBoxDataType.PULSE -> scope.launch { + + } + PowerBoxDataType.CLEAR -> scope.launch { + + } + PowerBoxDataType.FEEDBACK -> scope.launch { + + } else -> return } } + companion object { + val ROM_UUID = "00001101-0000-1000-8000-00805f9b34fb" + } } \ No newline at end of file diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/DefaultPulseData.kt b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/DefaultPulseData.kt index 3e9909c..884f72e 100644 --- a/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/DefaultPulseData.kt +++ b/src/main/kotlin/top/r3944realms/ltdmanager/dglab/model/pulseware/DefaultPulseData.kt @@ -4,6 +4,31 @@ import com.r3944realms.dg_lab.api.message.data.PulseWave import com.r3944realms.dg_lab.api.message.data.PulseWaveList object DefaultPulseData { + /** + * 循环次数配置,可以根据需要调整 + */ + private const val DEFAULT_CYCLES = 3 + + /** + * 创建带循环的 PulseWaveList + */ + private fun createCyclicWaveList( + baseSegments: List>, + cycles: Int = DEFAULT_CYCLES, + name: String = "" + ): PulseWaveList { + val list = PulseWaveList() + list.name = name + + repeat(cycles) { + baseSegments.forEach { seg -> + list.add(createWaveSegment(seg[0], seg[1])) + } + } + + return list + } + /** * 将频率转换为 Dg-Lab 格式 * @@ -77,12 +102,7 @@ object DefaultPulseData { arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0)) ) - // 转成 PulseWave 并加入列表 - for (seg in segments) { - list.add(createWaveSegment(seg[0], seg[1])) - } - - list + createCyclicWaveList(segments) } val Tide: PulseWaveList by lazy { val list = PulseWaveList() @@ -100,8 +120,7 @@ object DefaultPulseData { arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(84, 82, 80, 76)), arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(68, 68, 68, 68)) ) - segments.forEach { list.add(createWaveSegment(it[0], it[1])) } - list + createCyclicWaveList(segments) } val Combo: PulseWaveList by lazy { @@ -117,8 +136,7 @@ object DefaultPulseData { arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 1)), arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(2, 2, 2, 2)) ) - segments.forEach { list.add(createWaveSegment(it[0], it[1])) } - list + createCyclicWaveList(segments) } val FastPinch: PulseWaveList by lazy { val list = PulseWaveList() @@ -128,8 +146,7 @@ object DefaultPulseData { arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)), arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0)) ) - segments.forEach { list.add(createWaveSegment(it[0], it[1])) } - list + createCyclicWaveList(segments) } val PinchGradual: PulseWaveList by lazy { val list = PulseWaveList() @@ -147,8 +164,7 @@ object DefaultPulseData { arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)), arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)) ) - segments.forEach { list.add(createWaveSegment(it[0], it[1])) } - list + createCyclicWaveList(segments) } val Heartbeat: PulseWaveList by lazy { @@ -172,8 +188,7 @@ object DefaultPulseData { arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)), arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)) ) - segments.forEach { list.add(createWaveSegment(it[0], it[1])) } - list + createCyclicWaveList(segments) } val Compress: PulseWaveList by lazy { val list = PulseWaveList() @@ -201,8 +216,7 @@ object DefaultPulseData { arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)), arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)) ) - segments.forEach { list.add(createWaveSegment(it[0], it[1])) } - list + createCyclicWaveList(segments) } val RhythmStep: PulseWaveList by lazy { val list = PulseWaveList() @@ -235,8 +249,7 @@ object DefaultPulseData { arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)), arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)) ) - segments.forEach { list.add(createWaveSegment(it[0], it[1])) } - list + createCyclicWaveList(segments) } val GranularFriction: PulseWaveList by lazy { @@ -248,8 +261,7 @@ object DefaultPulseData { arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)), arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)) ) - segments.forEach { list.add(createWaveSegment(it[0], it[1])) } - list + createCyclicWaveList(segments) } val GradualBounce: PulseWaveList by lazy { @@ -263,8 +275,7 @@ object DefaultPulseData { arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0)), arrayOf(intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0)) ) - segments.forEach { list.add(createWaveSegment(it[0], it[1])) } - list + createCyclicWaveList(segments) } val WaveRipple: PulseWaveList by lazy { val list = PulseWaveList() @@ -278,8 +289,7 @@ object DefaultPulseData { arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(50, 50, 50, 50)), arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)) ) - segments.forEach { list.add(createWaveSegment(it[0], it[1])) } - list + createCyclicWaveList(segments) } val RainWash: PulseWaveList by lazy { @@ -291,8 +301,7 @@ object DefaultPulseData { arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(80, 90, 100, 100)), arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)) ) - segments.forEach { list.add(createWaveSegment(it[0], it[1])) } - list + createCyclicWaveList(segments) } val SpeedHit: PulseWaveList by lazy { @@ -305,8 +314,7 @@ object DefaultPulseData { arrayOf(intArrayOf(20, 20, 20, 20), intArrayOf(50, 50, 50, 50)), arrayOf(intArrayOf(15, 15, 15, 15), intArrayOf(0, 0, 0, 0)) ) - segments.forEach { list.add(createWaveSegment(it[0], it[1])) } - list + createCyclicWaveList(segments) } val SignalLight: PulseWaveList by lazy { val list = PulseWaveList() @@ -317,8 +325,7 @@ object DefaultPulseData { arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 0, 0, 0)), arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 100, 100, 100)) ) - segments.forEach { list.add(createWaveSegment(it[0], it[1])) } - list + createCyclicWaveList(segments) } val Tease1: PulseWaveList by lazy { @@ -328,8 +335,7 @@ object DefaultPulseData { arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 30, 60, 100)), arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 70, 40, 0)) ) - segments.forEach { list.add(createWaveSegment(it[0], it[1])) } - list + createCyclicWaveList(segments) } val Tease2: PulseWaveList by lazy { @@ -339,7 +345,6 @@ object DefaultPulseData { arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(0, 50, 100, 100)), arrayOf(intArrayOf(10, 10, 10, 10), intArrayOf(100, 50, 0, 0)) ) - segments.forEach { list.add(createWaveSegment(it[0], it[1])) } - list + createCyclicWaveList(segments) } } \ No newline at end of file diff --git a/src/main/kotlin/top/r3944realms/ltdmanager/module/DGLabModule.kt b/src/main/kotlin/top/r3944realms/ltdmanager/module/DGLabModule.kt index 80de5e2..c8c66ed 100644 --- a/src/main/kotlin/top/r3944realms/ltdmanager/module/DGLabModule.kt +++ b/src/main/kotlin/top/r3944realms/ltdmanager/module/DGLabModule.kt @@ -54,74 +54,296 @@ class DGLabModule( var dgLabManager: DgLab? = null private var scope: CoroutineScope? = null private var dglabCommandDispatcher: CommandDispatcher = CommandDispatcher().apply { + /* + dglab (命令头) + │ + ├── server (管理员) + │ ├── start - 启动 DG-LAB 服务器 + │ ├── stop - 停止 DG-LAB 服务器 + │ └── clients + │ └── stopAll - 停止所有客户端 + │ + ├── client + │ ├── start - 启动自己的客户端 + │ └── stop - 停止自己的客户端 + │ + ├── strength + │ ├── self - 对自己的操作 + │ │ ├── a + │ │ │ ├── add - A通道增加强度 (-200~200) + │ │ │ └── set - A通道设置强度 (0~200) + │ │ ├── b + │ │ │ ├── add - B通道增加强度 (-200~200) + │ │ │ └── set - B通道设置强度 (0~200) + │ │ └── ab + │ │ ├── add - 双通道增加强度 (-200~200) + │ │ └── set - 双通道设置强度 (0~200) + │ │ + │ └── target - 对他人的操作 + │ └── (QQ号) + │ ├── a + │ │ ├── add - A通道增加强度 (-200~200) + │ │ └── set - A通道设置强度 (0~200) + │ ├── b + │ │ ├── add - B通道增加强度 (-200~200) + │ │ └── set - B通道设置强度 (0~200) + │ └── ab + │ ├── add - 双通道增加强度 (-200~200) + │ └── set - 双通道设置强度 (0~200) + │ + ├── pulse + │ ├── self - 对自己的操作 + │ │ ├── a + │ │ │ ├── clear - 清除A通道脉冲 + │ │ │ └── set - 设置A通道脉冲 + │ │ ├── b + │ │ │ ├── clear - 清除B通道脉冲 + │ │ │ └── set - 设置B通道脉冲 + │ │ └── ab + │ │ ├── clear - 清除双通道脉冲 + │ │ └── set - 设置双通道脉冲 + │ │ + │ └── target - 对他人的操作 + │ └── (QQ号) + │ ├── a + │ │ ├── clear - 清除A通道脉冲 + │ │ └── set - 设置A通道脉冲 + │ ├── b + │ │ ├── clear - 清除B通道脉冲 + │ │ └── set - 设置B通道脉冲 + │ └── ab + │ ├── clear - 清除双通道脉冲 + │ └── set - 设置双通道脉冲 + │ + ├── add - 快捷增加强度 + │ ├── a - A通道增加强度 + │ ├── b - B通道增加强度 + │ └── ab - 双通道增加强度 + │ + ├── set - 快捷设置强度 + │ ├── a - A通道设置强度 + │ ├── b - B通道设置强度 + │ └── ab - 双通道设置强度 + │ + └── clear - 快捷清除脉冲 + ├── a - 清除A通道脉冲 + ├── b - 清除B通道脉冲 + └── ab - 清除双通道脉冲 + */ for (command in commandHead) register( literal(command) + // ========== 服务器管理(管理员) ========== .then(literal("server").requires { adminIds.contains(it.id) } .then(literal("start").executes { startDgLab() }) .then(literal("stop").executes { stopDgLab() }) - .then(literal("stopAllClient").executes { stopAllDgLabClient() }) + .then(literal("clients") + .then(literal("stopAll").executes { stopAllDgLabClient() }) + ) ) + + // ========== 客户端管理 ========== .then(literal("client") .then(literal("start").executes { startClient(it.source.id) }) .then(literal("stop").executes { stopClient(it.source.id) }) ) + + // ========== 强度控制(统一结构) ========== .then(literal("strength") - .then(argument("channel", StringArgumentType.string()) - .then(literal("add") - .then(argument("value", IntegerArgumentType.integer(-200, 200)) - .executes { strengthAdd(it.source.id, StringArgumentType.getString(it, "channel"), IntegerArgumentType.getInteger(it, "value")) } - ) - ) - .then(literal("set") - .then(argument("value", IntegerArgumentType.integer(0, 200)) - .executes { strengthSet(it.source.id, StringArgumentType.getString(it, "channel"), IntegerArgumentType.getInteger(it, "value")) } - ) - ) - ) - .then(argument("player", LongArgumentType.longArg()) - .then(argument("channel", StringArgumentType.string()) + // 对自己的操作 + .then(literal("self") + .then(literal("a") .then(literal("add") .then(argument("value", IntegerArgumentType.integer(-200, 200)) - .executes { strengthAdd(LongArgumentType.getLong(it, "player"), StringArgumentType.getString(it, "channel"), IntegerArgumentType.getInteger(it, "value")) } + .executes { strengthAdd(it.source.id, "a", IntegerArgumentType.getInteger(it, "value")) } ) ) .then(literal("set") .then(argument("value", IntegerArgumentType.integer(0, 200)) - .executes { strengthSet(LongArgumentType.getLong(it, "player"), StringArgumentType.getString(it, "channel"), IntegerArgumentType.getInteger(it, "value")) } + .executes { strengthSet(it.source.id, "a", IntegerArgumentType.getInteger(it, "value")) } ) ) ) - ) - ) - .then(literal("pulse") - .then(argument("channel", StringArgumentType.string()) - .then(literal("clear").executes { pulseClear(it.source.id, StringArgumentType.getString(it, "channel")) }) - .then(literal("set") - .then(argument("pulseName", StringArgumentType.string()) - .then(argument("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE)) - .executes { pulseSet(it.source.id, StringArgumentType.getString(it, "channel"), StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) } + .then(literal("b") + .then(literal("add") + .then(argument("value", IntegerArgumentType.integer(-200, 200)) + .executes { strengthAdd(it.source.id, "b", IntegerArgumentType.getInteger(it, "value")) } ) ) - ) - ) - .then(argument("player", LongArgumentType.longArg()) - .then(argument("channel", StringArgumentType.string()) - .then(literal("clear").executes { pulseClear(LongArgumentType.getLong(it, "player"), StringArgumentType.getString(it, "channel")) }) .then(literal("set") - .then(argument("pulseName", StringArgumentType.string()) - .then(argument("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE)) - .executes { pulseSet(LongArgumentType.getLong(it, "player"), StringArgumentType.getString(it, "channel"), StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) } + .then(argument("value", IntegerArgumentType.integer(0, 200)) + .executes { strengthSet(it.source.id, "b", IntegerArgumentType.getInteger(it, "value")) } + ) + ) + ) + .then(literal("ab") + .then(literal("add") + .then(argument("value", IntegerArgumentType.integer(-200, 200)) + .executes { strengthAdd(it.source.id, "ab", IntegerArgumentType.getInteger(it, "value")) } + ) + ) + .then(literal("set") + .then(argument("value", IntegerArgumentType.integer(0, 200)) + .executes { strengthSet(it.source.id, "ab", IntegerArgumentType.getInteger(it, "value")) } + ) + ) + ) + ) + // 对他人的操作 + .then(literal("target") + .then(argument("player", LongArgumentType.longArg()) + .then(literal("a") + .then(literal("add") + .then(argument("value", IntegerArgumentType.integer(-200, 200)) + .executes { strengthAdd(LongArgumentType.getLong(it, "player"), "a", IntegerArgumentType.getInteger(it, "value")) } + ) + ) + .then(literal("set") + .then(argument("value", IntegerArgumentType.integer(0, 200)) + .executes { strengthSet(LongArgumentType.getLong(it, "player"), "a", IntegerArgumentType.getInteger(it, "value")) } + ) + ) + ) + .then(literal("b") + .then(literal("add") + .then(argument("value", IntegerArgumentType.integer(-200, 200)) + .executes { strengthAdd(LongArgumentType.getLong(it, "player"), "b", IntegerArgumentType.getInteger(it, "value")) } + ) + ) + .then(literal("set") + .then(argument("value", IntegerArgumentType.integer(0, 200)) + .executes { strengthSet(LongArgumentType.getLong(it, "player"), "b", IntegerArgumentType.getInteger(it, "value")) } + ) + ) + ) + .then(literal("ab") + .then(literal("add") + .then(argument("value", IntegerArgumentType.integer(-200, 200)) + .executes { strengthAdd(LongArgumentType.getLong(it, "player"), "ab", IntegerArgumentType.getInteger(it, "value")) } + ) + ) + .then(literal("set") + .then(argument("value", IntegerArgumentType.integer(0, 200)) + .executes { strengthSet(LongArgumentType.getLong(it, "player"), "ab", IntegerArgumentType.getInteger(it, "value")) } ) ) ) ) ) ) - ) -// .then(literal("info").executes {} -// .then(argument("player", StringArgumentType.string()).executes {}) -// ) + // ========== 脉冲控制(统一结构) ========== + .then(literal("pulse") + // 对自己的操作 + .then(literal("self") + .then(literal("a") + .then(literal("clear").executes { pulseClear(it.source.id, "a") }) + .then(literal("set") + .then(argument("pulseName", StringArgumentType.string()) + .then(argument("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE)) + .executes { pulseSet(it.source.id, "a", StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) } + ) + ) + ) + ) + .then(literal("b") + .then(literal("clear").executes { pulseClear(it.source.id, "b") }) + .then(literal("set") + .then(argument("pulseName", StringArgumentType.string()) + .then(argument("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE)) + .executes { pulseSet(it.source.id, "b", StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) } + ) + ) + ) + ) + .then(literal("ab") + .then(literal("clear").executes { pulseClear(it.source.id, "ab") }) + .then(literal("set") + .then(argument("pulseName", StringArgumentType.string()) + .then(argument("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE)) + .executes { pulseSet(it.source.id, "ab", StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) } + ) + ) + ) + ) + ) + // 对他人的操作 + .then(literal("target") + .then(argument("player", LongArgumentType.longArg()) + .then(literal("a") + .then(literal("clear").executes { pulseClear(LongArgumentType.getLong(it, "player"), "a") }) + .then(literal("set") + .then(argument("pulseName", StringArgumentType.string()) + .then(argument("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE)) + .executes { pulseSet(LongArgumentType.getLong(it, "player"), "a", StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) } + ) + ) + ) + ) + .then(literal("b") + .then(literal("clear").executes { pulseClear(LongArgumentType.getLong(it, "player"), "b") }) + .then(literal("set") + .then(argument("pulseName", StringArgumentType.string()) + .then(argument("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE)) + .executes { pulseSet(LongArgumentType.getLong(it, "player"), "b", StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) } + ) + ) + ) + ) + .then(literal("ab") + .then(literal("clear").executes { pulseClear(LongArgumentType.getLong(it, "player"), "ab") }) + .then(literal("set") + .then(argument("pulseName", StringArgumentType.string()) + .then(argument("timer", IntegerArgumentType.integer(0, Int.MAX_VALUE)) + .executes { pulseSet(LongArgumentType.getLong(it, "player"), "ab", StringArgumentType.getString(it, "pulseName"), IntegerArgumentType.getInteger(it, "timer")) } + ) + ) + ) + ) + ) + ) + ) + + // ========== 快捷操作(简化版) ========== + .then(literal("add") + .then(literal("a") + .then(argument("value", IntegerArgumentType.integer(-200, 200)) + .executes { strengthAdd(it.source.id, "a", IntegerArgumentType.getInteger(it, "value")) } + ) + ) + .then(literal("b") + .then(argument("value", IntegerArgumentType.integer(-200, 200)) + .executes { strengthAdd(it.source.id, "b", IntegerArgumentType.getInteger(it, "value")) } + ) + ) + .then(literal("ab") + .then(argument("value", IntegerArgumentType.integer(-200, 200)) + .executes { strengthAdd(it.source.id, "ab", IntegerArgumentType.getInteger(it, "value")) } + ) + ) + ) + .then(literal("set") + .then(literal("a") + .then(argument("value", IntegerArgumentType.integer(0, 200)) + .executes { strengthSet(it.source.id, "a", IntegerArgumentType.getInteger(it, "value")) } + ) + ) + .then(literal("b") + .then(argument("value", IntegerArgumentType.integer(0, 200)) + .executes { strengthSet(it.source.id, "b", IntegerArgumentType.getInteger(it, "value")) } + ) + ) + .then(literal("ab") + .then(argument("value", IntegerArgumentType.integer(0, 200)) + .executes { strengthSet(it.source.id, "ab", IntegerArgumentType.getInteger(it, "value")) } + ) + ) + ) + .then(literal("clear") + .then(literal("a").executes { pulseClear(it.source.id, "a") }) + .then(literal("b").executes { pulseClear(it.source.id, "b") }) + .then(literal("ab").executes { pulseClear(it.source.id, "ab") }) + ) + ) } private val stateFile: File = getStateFileInternal("dg_lab_state.json", name) private val stateBackupFile: File = getStateFileInternal("dg_lab_state.json.bak", name) @@ -184,11 +406,22 @@ class DGLabModule( triggerMsgs.forEach { (msg, userId, processedText) -> refMsg = msg LoggerUtil.logger.info("[$name] 原始消息用户: $userId") - LoggerUtil.logger.info("[$name] 处理后的命令: $processedText") + var result = processedText.replace(Regex("\\s+"), " ") + result = result.replace("“", "\"") + .replace("”", "\"") + .replace("‘", "'") + .replace("’", "'") + .replace("『", "\"") + .replace("』", "\"") + .replace("【", "[") + .replace("】", "]") + .replace("(", "(") + .replace(")", ")") + LoggerUtil.logger.info("[$name] 处理后的命令: $result") refPlayer = dgLabManager?.getPlayerManager()?.getPlayer(userId) dgLabState = dgLabState.updateOrCreate(userId, msg.realId, msg.time) - val execute = dglabCommandDispatcher.execute(processedText, refPlayer) + val execute = dglabCommandDispatcher.execute(result, refPlayer) scope?.launch { GlobalManager.napCatClient.sendUnit( SetMsgEmojiLikeRequest( @@ -300,7 +533,7 @@ class DGLabModule( dgLabManager!!.getPlayerManager(), qq ) - val dgpbClientManager = dgLabManager?.getClientOrCreate( + val dgpbClientManager = dgLabManager?.createClient( qq.toString(), operation ) @@ -310,7 +543,7 @@ class DGLabModule( return 1 } private fun stopClient(qq: Long): Int { - dgLabManager?.getClient(qq.toString())?.stop() + dgLabManager?.removeClient(qq.toString()) return 1 } private fun strengthAdd(qq: Long, channel: String, value: Int): Int { diff --git a/src/test/kotlin/top/r394realms/ltdmanagertest/testRandom.kt b/src/test/kotlin/top/r394realms/ltdmanagertest/testRandom.kt index 0b3d9f8..bd2d5b3 100644 --- a/src/test/kotlin/top/r394realms/ltdmanagertest/testRandom.kt +++ b/src/test/kotlin/top/r394realms/ltdmanagertest/testRandom.kt @@ -1,9 +1,165 @@ package top.r394realms.ltdmanagertest -import kotlin.random.Random - fun main() { - for(item in 1..100){ - println(Random.nextInt(100)) + val startTime = System.currentTimeMillis() + + // 生成1~9的所有排列并检查是否是幻方 + val numbers = arrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9) + val magicSquares = mutableListOf>>() + val used = BooleanArray(9) + val current = Array(3) { Array(3) { 0 } } + + fun backtrack(index: Int) { + if (index == 9) { + if (isMagicSquare(current)) { + // 深拷贝当前矩阵 + val copy = Array(3) { i -> Array(3) { j -> current[i][j] } } + magicSquares.add(copy) + } + return + } + + for (i in numbers.indices) { + if (!used[i]) { + used[i] = true + current[index / 3][index % 3] = numbers[i] + backtrack(index + 1) + used[i] = false + } + } } + + backtrack(0) + + // 去重(排除旋转和镜像对称的重复解) + val uniqueSquares = removeDuplicates(magicSquares) + + println("找到 ${uniqueSquares.size} 个独特的3阶幻方:") + println("=".repeat(30)) + + uniqueSquares.forEachIndexed { idx, square -> + println("幻方 ${idx + 1}:") + printMagicSquare(square) + println("-".repeat(20)) + } + + val endTime = System.currentTimeMillis() + println("总排列数: ${factorial(9)}") + println("有效幻方数: ${magicSquares.size}") + println("独特幻方数: ${uniqueSquares.size}") + println("运行时间: ${endTime - startTime}ms") +} + +fun isMagicSquare(square: Array>): Boolean { + // 检查所有行、列、对角线之和是否为15 + return (square[0][0] + square[0][1] + square[0][2] == 15) && + (square[1][0] + square[1][1] + square[1][2] == 15) && + (square[2][0] + square[2][1] + square[2][2] == 15) && + + (square[0][0] + square[1][0] + square[2][0] == 15) && + (square[0][1] + square[1][1] + square[2][1] == 15) && + (square[0][2] + square[1][2] + square[2][2] == 15) && + + (square[0][0] + square[1][1] + square[2][2] == 15) && + (square[0][2] + square[1][1] + square[2][0] == 15) +} + +fun removeDuplicates(squares: List>>): List>> { + val unique = mutableListOf>>() + val seen = mutableSetOf() + + for (square in squares) { + // 获取所有旋转和镜像变换 + val transformations = getAllTransformations(square) + var isNew = true + + for (transform in transformations) { + val key = transformToString(transform) + if (key in seen) { + isNew = false + break + } + } + + if (isNew) { + val key = transformToString(square) + seen.add(key) + unique.add(square) + } + } + + return unique +} + +fun getAllTransformations(square: Array>): List>> { + val transformations = mutableListOf>>() + var current = square + + // 4个旋转方向 + repeat(4) { + transformations.add(current) + // 添加镜像 + transformations.add(mirrorHorizontal(current)) + transformations.add(mirrorVertical(current)) + transformations.add(mirrorDiagonal(current)) + + current = rotate90(current) + } + + return transformations.distinctBy { transformToString(it) } +} + +fun rotate90(square: Array>): Array> { + val result = Array(3) { Array(3) { 0 } } + for (i in 0..2) { + for (j in 0..2) { + result[j][2 - i] = square[i][j] + } + } + return result +} + +fun mirrorHorizontal(square: Array>): Array> { + val result = Array(3) { Array(3) { 0 } } + for (i in 0..2) { + for (j in 0..2) { + result[i][2 - j] = square[i][j] + } + } + return result +} + +fun mirrorVertical(square: Array>): Array> { + val result = Array(3) { Array(3) { 0 } } + for (i in 0..2) { + for (j in 0..2) { + result[2 - i][j] = square[i][j] + } + } + return result +} + +fun mirrorDiagonal(square: Array>): Array> { + val result = Array(3) { Array(3) { 0 } } + for (i in 0..2) { + for (j in 0..2) { + result[j][i] = square[i][j] + } + } + return result +} + +fun transformToString(square: Array>): String { + return square.joinToString("") { row -> row.joinToString("") } +} + +fun printMagicSquare(square: Array>) { + for (row in square) { + println(row.joinToString(" ") { "%d".format(it) }) + } +} + +fun factorial(n: Long): Long { + return if (n <= 1) 1 + else n * factorial(n - 1) } \ No newline at end of file