From 52aedd561a80e438e4d1bd99b0c72f2edab247eb Mon Sep 17 00:00:00 2001 From: Sokolvyak Sergey Date: Sun, 25 Apr 2021 12:32:40 +0300 Subject: [PATCH 1/9] split into two modules --- .gitignore | 223 ------------------ build.gradle.kts | 35 +-- game-client/build.gradle.kts | 19 ++ .../src}/main/kotlin/ru/ifmo/sd/Main.kt | 0 .../kotlin/ru/ifmo/sd/stuff/MapTextPane.kt | 0 .../main/kotlin/ru/ifmo/sd/stuff/SymbolMap.kt | 0 game-server/build.gradle.kts | 39 +++ settings.gradle.kts | 4 +- 8 files changed, 67 insertions(+), 253 deletions(-) create mode 100644 game-client/build.gradle.kts rename {src => game-client/src}/main/kotlin/ru/ifmo/sd/Main.kt (100%) rename {src => game-client/src}/main/kotlin/ru/ifmo/sd/stuff/MapTextPane.kt (100%) rename {src => game-client/src}/main/kotlin/ru/ifmo/sd/stuff/SymbolMap.kt (100%) create mode 100644 game-server/build.gradle.kts diff --git a/.gitignore b/.gitignore index 33bfc3b..3a51581 100644 --- a/.gitignore +++ b/.gitignore @@ -19,52 +19,18 @@ gradle-app.setting .idea -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - # IntelliJ out/ # mpeltonen/sbt-idea plugin .idea_modules/ -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - ### Kotlin template # Compiled class file *.class @@ -89,192 +55,3 @@ fabric.properties # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* - -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -### Kotlin template -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - -### Gradle template -.gradle -**/build/ -!client/src/**/build/ - -# Ignore Gradle GUI config -gradle-app.setting - -# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) -!gradle-wrapper.jar - -# Cache of project -.gradletasknamecache - -# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 -# gradle/wrapper/gradle-wrapper.properties - -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - diff --git a/build.gradle.kts b/build.gradle.kts index d571af3..3e1c1df 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,32 +1,9 @@ -plugins { - application - kotlin("jvm") version "1.4.32" - kotlin("plugin.serialization") version "1.4.32" - id("io.gitlab.arturbosch.detekt") version "1.16.0-RC2" +allprojects { + repositories { + jcenter() + } } -group = "ru.ifmo.jb" -version = "1.0-SNAPSHOT" -val ktor_version = "1.5.3" - -application { - mainClass.set("ru.ifmo.sd.MainKt") -} - -repositories { - mavenCentral() -} - -dependencies { - implementation("io.ktor:ktor-websockets:$ktor_version") - implementation("io.ktor:ktor-client-cio:$ktor_version") - implementation("io.ktor:ktor-client-gson:$ktor_version") - implementation("io.ktor:ktor-gson:$ktor_version") - implementation("io.ktor:ktor-server-core:$ktor_version") - implementation("io.ktor:ktor-server-netty:$ktor_version") - testImplementation("io.ktor:ktor-server-tests:$ktor_version") -} - -tasks.test { - useJUnit() +subprojects { + version = "1.0" } diff --git a/game-client/build.gradle.kts b/game-client/build.gradle.kts new file mode 100644 index 0000000..4984fce --- /dev/null +++ b/game-client/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + java + kotlin("jvm") version "1.4.21" +} + +group = "ru.ifmo.jb" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + implementation(kotlin("stdlib")) +} + +tasks.test { + useJUnitPlatform() +} diff --git a/src/main/kotlin/ru/ifmo/sd/Main.kt b/game-client/src/main/kotlin/ru/ifmo/sd/Main.kt similarity index 100% rename from src/main/kotlin/ru/ifmo/sd/Main.kt rename to game-client/src/main/kotlin/ru/ifmo/sd/Main.kt diff --git a/src/main/kotlin/ru/ifmo/sd/stuff/MapTextPane.kt b/game-client/src/main/kotlin/ru/ifmo/sd/stuff/MapTextPane.kt similarity index 100% rename from src/main/kotlin/ru/ifmo/sd/stuff/MapTextPane.kt rename to game-client/src/main/kotlin/ru/ifmo/sd/stuff/MapTextPane.kt diff --git a/src/main/kotlin/ru/ifmo/sd/stuff/SymbolMap.kt b/game-client/src/main/kotlin/ru/ifmo/sd/stuff/SymbolMap.kt similarity index 100% rename from src/main/kotlin/ru/ifmo/sd/stuff/SymbolMap.kt rename to game-client/src/main/kotlin/ru/ifmo/sd/stuff/SymbolMap.kt diff --git a/game-server/build.gradle.kts b/game-server/build.gradle.kts new file mode 100644 index 0000000..db86089 --- /dev/null +++ b/game-server/build.gradle.kts @@ -0,0 +1,39 @@ +plugins { + java + application + kotlin("jvm") version "1.4.21" + kotlin("plugin.serialization") version "1.4.21" +} + +group = "ru.ifmo.jb" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + implementation(kotlin("stdlib")) + + implementation("io.ktor:ktor-server-core:1.5.2") + implementation("io.ktor:ktor-server-netty:1.5.2") + implementation("io.ktor:ktor-serialization:1.5.2") + testImplementation("io.ktor:ktor-server-tests:1.5.2") +} + +application { + +} + +tasks { + compileKotlin { + kotlinOptions.jvmTarget = "1.8" + } + compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" + } +} + +tasks.test { + useJUnitPlatform() +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 25ad449..7f3ab9b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,3 @@ -rootProject.name = "roguelike" \ No newline at end of file +rootProject.name = "roguelike" +include("game-client") +include("game-server") From 873430ee67592893ec16b3d8f0a5cdccd75f8e3a Mon Sep 17 00:00:00 2001 From: Sokolvyak Sergey Date: Sun, 25 Apr 2021 12:34:01 +0300 Subject: [PATCH 2/9] add core functionality --- .../kotlin/ru/ifmo/sd/httpapi/Application.kt | 15 +++++++++ .../sd/httpapi/models/LevelConfiguration.kt | 6 ++++ .../ifmo/sd/httpapi/models/MoveEventData.kt | 9 ++++++ .../ru/ifmo/sd/httpapi/routes/GameRoutes.kt | 32 +++++++++++++++++++ .../world/configuration/GameConfiguration.kt | 9 ++++++ .../world/configuration/GameConfigurator.kt | 32 +++++++++++++++++++ .../ru/ifmo/sd/world/events/EventsHandler.kt | 19 +++++++++++ .../sd/world/generation/LevelGenerator.kt | 11 +++++++ .../ifmo/sd/world/representation/GameLevel.kt | 20 ++++++++++++ .../ifmo/sd/world/representation/Position.kt | 3 ++ .../representation/UnitsHealthStorage.kt | 23 +++++++++++++ .../representation/UnitsPositionStorage.kt | 15 +++++++++ .../sd/world/representation/units/GameUnit.kt | 5 +++ .../sd/world/representation/units/Player.kt | 6 ++++ 14 files changed, 205 insertions(+) create mode 100644 game-server/src/main/kotlin/ru/ifmo/sd/httpapi/Application.kt create mode 100644 game-server/src/main/kotlin/ru/ifmo/sd/httpapi/models/LevelConfiguration.kt create mode 100644 game-server/src/main/kotlin/ru/ifmo/sd/httpapi/models/MoveEventData.kt create mode 100644 game-server/src/main/kotlin/ru/ifmo/sd/httpapi/routes/GameRoutes.kt create mode 100644 game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfiguration.kt create mode 100644 game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfigurator.kt create mode 100644 game-server/src/main/kotlin/ru/ifmo/sd/world/events/EventsHandler.kt create mode 100644 game-server/src/main/kotlin/ru/ifmo/sd/world/generation/LevelGenerator.kt create mode 100644 game-server/src/main/kotlin/ru/ifmo/sd/world/representation/GameLevel.kt create mode 100644 game-server/src/main/kotlin/ru/ifmo/sd/world/representation/Position.kt create mode 100644 game-server/src/main/kotlin/ru/ifmo/sd/world/representation/UnitsHealthStorage.kt create mode 100644 game-server/src/main/kotlin/ru/ifmo/sd/world/representation/UnitsPositionStorage.kt create mode 100644 game-server/src/main/kotlin/ru/ifmo/sd/world/representation/units/GameUnit.kt create mode 100644 game-server/src/main/kotlin/ru/ifmo/sd/world/representation/units/Player.kt diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/Application.kt b/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/Application.kt new file mode 100644 index 0000000..bd00812 --- /dev/null +++ b/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/Application.kt @@ -0,0 +1,15 @@ +package ru.ifmo.sd.httpapi + +import io.ktor.application.* +import io.ktor.features.* +import io.ktor.serialization.* +import ru.ifmo.sd.httpapi.routes.registerGameRoutes + +fun main(args: Array): Unit = io.ktor.server.netty.EngineMain.main(args) + +fun Application.module(testing: Boolean = false) { + install(ContentNegotiation) { + json() + } + registerGameRoutes() +} diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/models/LevelConfiguration.kt b/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/models/LevelConfiguration.kt new file mode 100644 index 0000000..4e8ddcf --- /dev/null +++ b/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/models/LevelConfiguration.kt @@ -0,0 +1,6 @@ +package ru.ifmo.sd.httpapi.models + +import kotlinx.serialization.Serializable + +@Serializable +data class LevelConfiguration(val levelLength: Int, val levelWidth: Int) diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/models/MoveEventData.kt b/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/models/MoveEventData.kt new file mode 100644 index 0000000..2d45006 --- /dev/null +++ b/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/models/MoveEventData.kt @@ -0,0 +1,9 @@ +package ru.ifmo.sd.httpapi.models + +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable +import ru.ifmo.sd.world.representation.Position +import ru.ifmo.sd.world.representation.units.GameUnit + +@Serializable +data class MoveEventData(val targetUnit: @Contextual GameUnit, val newPos: @Contextual Position) diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/routes/GameRoutes.kt b/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/routes/GameRoutes.kt new file mode 100644 index 0000000..b968d10 --- /dev/null +++ b/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/routes/GameRoutes.kt @@ -0,0 +1,32 @@ +package ru.ifmo.sd.httpapi.routes + +import io.ktor.application.* +import io.ktor.http.* +import io.ktor.request.* +import io.ktor.response.* +import io.ktor.routing.* +import ru.ifmo.sd.httpapi.models.LevelConfiguration +import ru.ifmo.sd.httpapi.models.MoveEventData +import ru.ifmo.sd.world.events.EventsHandler + +fun Route.gameRouting() { + route("/") { + post { + val levelConfiguration = call.receive() + val startedGame = EventsHandler.startGame(levelConfiguration) + call.respond(message = startedGame,status = HttpStatusCode.Created) + } + + post { + val moveEventData = call.receive() + EventsHandler.move(moveEventData.targetUnit, moveEventData.newPos) + call.respondText("Position successfully updated", status = HttpStatusCode.Accepted) + } + } +} + +fun Application.registerGameRoutes() { + routing { + gameRouting() + } +} diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfiguration.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfiguration.kt new file mode 100644 index 0000000..e35406c --- /dev/null +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfiguration.kt @@ -0,0 +1,9 @@ +package ru.ifmo.sd.world.configuration + +import ru.ifmo.sd.world.representation.* + +data class GameConfiguration( + val level: GameLevel, + val unitsPositions: UnitsPositionStorage, + val unitsHealthStorage: UnitsHealthStorage +) diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfigurator.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfigurator.kt new file mode 100644 index 0000000..b22df10 --- /dev/null +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfigurator.kt @@ -0,0 +1,32 @@ +package ru.ifmo.sd.world.configuration + +import ru.ifmo.sd.world.generation.LevelGenerator +import ru.ifmo.sd.world.representation.* +import ru.ifmo.sd.world.representation.units.Player + +object GameConfigurator { + private lateinit var gameLevel: GameLevel + private lateinit var unitsPositions: UnitsPositionStorage + private lateinit var unitsHealths: UnitsHealthStorage + + fun getGameLevel(): GameLevel { + return gameLevel + } + + fun getUnitsPositions(): UnitsPositionStorage { + return unitsPositions + } + + fun getUnitsHealths(): UnitsHealthStorage { + return unitsHealths + } + + fun configure(levelLength: Int, levelWidth: Int): GameConfiguration { + gameLevel = GameLevel(LevelGenerator.generateLevel( + levelLength, levelWidth)) + val player = Player(1) + unitsPositions.move(player, Position(0, 0)) + unitsHealths.addUnit(player) + return GameConfiguration(gameLevel, unitsPositions, unitsHealths) + } +} diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/events/EventsHandler.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/events/EventsHandler.kt new file mode 100644 index 0000000..ef7865f --- /dev/null +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/events/EventsHandler.kt @@ -0,0 +1,19 @@ +package ru.ifmo.sd.world.events + +import ru.ifmo.sd.httpapi.models.LevelConfiguration +import ru.ifmo.sd.world.configuration.GameConfiguration +import ru.ifmo.sd.world.configuration.GameConfigurator +import ru.ifmo.sd.world.representation.Position +import ru.ifmo.sd.world.representation.units.GameUnit + +class EventsHandler { + companion object { + fun startGame(levelConf: LevelConfiguration): GameConfiguration { + return GameConfigurator.configure(levelConf.levelLength, levelConf.levelWidth) + } + + fun move(targetUnit: GameUnit, newPos: Position) { + GameConfigurator.getUnitsPositions().move(targetUnit, newPos) + } + } +} diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/generation/LevelGenerator.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/generation/LevelGenerator.kt new file mode 100644 index 0000000..1d95933 --- /dev/null +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/generation/LevelGenerator.kt @@ -0,0 +1,11 @@ +package ru.ifmo.sd.world.generation + +class LevelGenerator { + companion object { + private lateinit var level: Array + + fun generateLevel(length: Int, width: Int): Array { + return Array(length) { IntArray(width){1} } + } + } +} diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/GameLevel.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/GameLevel.kt new file mode 100644 index 0000000..15b12d2 --- /dev/null +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/GameLevel.kt @@ -0,0 +1,20 @@ +package ru.ifmo.sd.world.representation + +import kotlin.math.abs + +class GameLevel(private val level: Array) { + val gameLevel: Array + get() = this.level + + fun isAvailable(oldPos: Position, newPos: Position): Boolean { + return isAdjacent(oldPos, newPos) && insideMaze(newPos) && level[newPos.row][newPos.column] == 0 + } + + private fun insideMaze(newPos: Position): Boolean { + return newPos.row < level.size && newPos.column < level[0].size + } + + private fun isAdjacent(oldPos: Position, newPos: Position): Boolean { + return abs(oldPos.row - newPos.row) <= 1 && abs(oldPos.column - newPos.column) <= 1 + } +} diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/Position.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/Position.kt new file mode 100644 index 0000000..c758189 --- /dev/null +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/Position.kt @@ -0,0 +1,3 @@ +package ru.ifmo.sd.world.representation + +data class Position(val row: Int, val column: Int) diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/UnitsHealthStorage.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/UnitsHealthStorage.kt new file mode 100644 index 0000000..2c63012 --- /dev/null +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/UnitsHealthStorage.kt @@ -0,0 +1,23 @@ +package ru.ifmo.sd.world.representation + +import ru.ifmo.sd.world.representation.units.GameUnit + +class UnitsHealthStorage { + private val healths: MutableMap = HashMap() + + fun addUnit(unit: GameUnit, value: Int = 100) { + healths[unit] = value + } + + fun increase(gameUnit: GameUnit, value: Int) { + healths.merge(gameUnit, value, Int::plus) + } + + fun decrease(gameUnit: GameUnit, value: Int) { + healths.merge(gameUnit, value, Int::minus) + } + + fun isAlive(gameUnit: GameUnit): Boolean { + return healths.getOrDefault(gameUnit, 0) > 0 + } +} diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/UnitsPositionStorage.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/UnitsPositionStorage.kt new file mode 100644 index 0000000..7c85d66 --- /dev/null +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/UnitsPositionStorage.kt @@ -0,0 +1,15 @@ +package ru.ifmo.sd.world.representation + +import ru.ifmo.sd.world.representation.units.GameUnit + +class UnitsPositionStorage { + private val positionStorage: MutableMap = HashMap() + + fun move(targetGameUnit: GameUnit, newPos: Position) { + positionStorage[targetGameUnit] = newPos + } + + fun eliminateUnit(targetGameUnit: GameUnit) { + positionStorage.remove(targetGameUnit) + } +} diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/units/GameUnit.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/units/GameUnit.kt new file mode 100644 index 0000000..51280af --- /dev/null +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/units/GameUnit.kt @@ -0,0 +1,5 @@ +package ru.ifmo.sd.world.representation.units + +abstract class GameUnit { + abstract val id: Int +} diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/units/Player.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/units/Player.kt new file mode 100644 index 0000000..0ee8024 --- /dev/null +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/units/Player.kt @@ -0,0 +1,6 @@ +package ru.ifmo.sd.world.representation.units + +class Player(private val identifier: Int) : GameUnit() { + override val id: Int + get() = this.identifier +} From cc280a77520d4c0b2a94304f1caeaff6321ebf50 Mon Sep 17 00:00:00 2001 From: Sokolvyak Sergey Date: Sun, 25 Apr 2021 13:37:50 +0300 Subject: [PATCH 3/9] add application configuration file --- game-server/build.gradle.kts | 4 +++- game-server/src/main/resources/application.conf | 9 +++++++++ game-server/src/main/resources/logback.xml | 12 ++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 game-server/src/main/resources/application.conf create mode 100644 game-server/src/main/resources/logback.xml diff --git a/game-server/build.gradle.kts b/game-server/build.gradle.kts index db86089..e7e646a 100644 --- a/game-server/build.gradle.kts +++ b/game-server/build.gradle.kts @@ -15,6 +15,8 @@ repositories { dependencies { implementation(kotlin("stdlib")) + implementation("ch.qos.logback:logback-classic:1.2.3") + implementation("io.ktor:ktor-server-core:1.5.2") implementation("io.ktor:ktor-server-netty:1.5.2") implementation("io.ktor:ktor-serialization:1.5.2") @@ -22,7 +24,7 @@ dependencies { } application { - + mainClassName = "ru.ifmo.sd.httpapi.ApplicationKt" } tasks { diff --git a/game-server/src/main/resources/application.conf b/game-server/src/main/resources/application.conf new file mode 100644 index 0000000..b661627 --- /dev/null +++ b/game-server/src/main/resources/application.conf @@ -0,0 +1,9 @@ +ktor { + deployment { + port = 8080 + port = ${?PORT} + } + application { + modules = [ ru.ifmo.sd.httpapi.ApplicationKt.module ] + } +} diff --git a/game-server/src/main/resources/logback.xml b/game-server/src/main/resources/logback.xml new file mode 100644 index 0000000..bdbb64e --- /dev/null +++ b/game-server/src/main/resources/logback.xml @@ -0,0 +1,12 @@ + + + + %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + From 0b87f1f554e77aa93a50a3131f1dba86a9ee464f Mon Sep 17 00:00:00 2001 From: Sokolvyak Sergey Date: Sun, 25 Apr 2021 13:38:06 +0300 Subject: [PATCH 4/9] fix problems with serialization --- .../kotlin/ru/ifmo/sd/httpapi/models/MoveEventData.kt | 3 +-- .../kotlin/ru/ifmo/sd/httpapi/routes/GameRoutes.kt | 6 +++--- .../ifmo/sd/world/configuration/GameConfiguration.kt | 10 +++++++--- .../ru/ifmo/sd/world/configuration/GameConfigurator.kt | 4 +++- .../kotlin/ru/ifmo/sd/world/representation/Position.kt | 3 +++ .../ifmo/sd/world/representation/UnitsHealthStorage.kt | 2 ++ .../sd/world/representation/UnitsPositionStorage.kt | 8 +++++--- .../ru/ifmo/sd/world/representation/units/GameUnit.kt | 7 ++++--- .../ru/ifmo/sd/world/representation/units/Player.kt | 5 +---- 9 files changed, 29 insertions(+), 19 deletions(-) diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/models/MoveEventData.kt b/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/models/MoveEventData.kt index 2d45006..d7866c4 100644 --- a/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/models/MoveEventData.kt +++ b/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/models/MoveEventData.kt @@ -1,9 +1,8 @@ package ru.ifmo.sd.httpapi.models -import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import ru.ifmo.sd.world.representation.Position import ru.ifmo.sd.world.representation.units.GameUnit @Serializable -data class MoveEventData(val targetUnit: @Contextual GameUnit, val newPos: @Contextual Position) +data class MoveEventData(val targetUnit: GameUnit, val newPos: Position) diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/routes/GameRoutes.kt b/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/routes/GameRoutes.kt index b968d10..6d27b51 100644 --- a/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/routes/GameRoutes.kt +++ b/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/routes/GameRoutes.kt @@ -11,13 +11,13 @@ import ru.ifmo.sd.world.events.EventsHandler fun Route.gameRouting() { route("/") { - post { + post("/start") { val levelConfiguration = call.receive() val startedGame = EventsHandler.startGame(levelConfiguration) - call.respond(message = startedGame,status = HttpStatusCode.Created) + call.respond(message = startedGame, status = HttpStatusCode.Created) } - post { + post("/move") { val moveEventData = call.receive() EventsHandler.move(moveEventData.targetUnit, moveEventData.newPos) call.respondText("Position successfully updated", status = HttpStatusCode.Accepted) diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfiguration.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfiguration.kt index e35406c..fadd18c 100644 --- a/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfiguration.kt +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfiguration.kt @@ -1,9 +1,13 @@ package ru.ifmo.sd.world.configuration +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable import ru.ifmo.sd.world.representation.* +import ru.ifmo.sd.world.representation.units.GameUnit +@Serializable data class GameConfiguration( - val level: GameLevel, - val unitsPositions: UnitsPositionStorage, - val unitsHealthStorage: UnitsHealthStorage + val level: Array, + val unitsPositions: Map<@Contextual GameUnit, @Contextual Position>, + val unitsHealthStorage: Map<@Contextual GameUnit, Int> ) diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfigurator.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfigurator.kt index b22df10..db82f0e 100644 --- a/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfigurator.kt +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfigurator.kt @@ -25,8 +25,10 @@ object GameConfigurator { gameLevel = GameLevel(LevelGenerator.generateLevel( levelLength, levelWidth)) val player = Player(1) + unitsPositions = UnitsPositionStorage() + unitsHealths = UnitsHealthStorage() unitsPositions.move(player, Position(0, 0)) unitsHealths.addUnit(player) - return GameConfiguration(gameLevel, unitsPositions, unitsHealths) + return GameConfiguration(gameLevel.gameLevel, unitsPositions.getPositions(), unitsHealths.getHealths()) } } diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/Position.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/Position.kt index c758189..ff9779d 100644 --- a/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/Position.kt +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/Position.kt @@ -1,3 +1,6 @@ package ru.ifmo.sd.world.representation +import kotlinx.serialization.Serializable + +@Serializable data class Position(val row: Int, val column: Int) diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/UnitsHealthStorage.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/UnitsHealthStorage.kt index 2c63012..39defc9 100644 --- a/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/UnitsHealthStorage.kt +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/UnitsHealthStorage.kt @@ -5,6 +5,8 @@ import ru.ifmo.sd.world.representation.units.GameUnit class UnitsHealthStorage { private val healths: MutableMap = HashMap() + fun getHealths() = this.healths + fun addUnit(unit: GameUnit, value: Int = 100) { healths[unit] = value } diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/UnitsPositionStorage.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/UnitsPositionStorage.kt index 7c85d66..c316da0 100644 --- a/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/UnitsPositionStorage.kt +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/UnitsPositionStorage.kt @@ -3,13 +3,15 @@ package ru.ifmo.sd.world.representation import ru.ifmo.sd.world.representation.units.GameUnit class UnitsPositionStorage { - private val positionStorage: MutableMap = HashMap() + private val positions: MutableMap = HashMap() + + fun getPositions() = this.positions fun move(targetGameUnit: GameUnit, newPos: Position) { - positionStorage[targetGameUnit] = newPos + positions[targetGameUnit] = newPos } fun eliminateUnit(targetGameUnit: GameUnit) { - positionStorage.remove(targetGameUnit) + positions.remove(targetGameUnit) } } diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/units/GameUnit.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/units/GameUnit.kt index 51280af..3a05b26 100644 --- a/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/units/GameUnit.kt +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/units/GameUnit.kt @@ -1,5 +1,6 @@ package ru.ifmo.sd.world.representation.units -abstract class GameUnit { - abstract val id: Int -} +import kotlinx.serialization.Serializable + +@Serializable +open class GameUnit(val id: Int) diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/units/Player.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/units/Player.kt index 0ee8024..2896dc5 100644 --- a/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/units/Player.kt +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/representation/units/Player.kt @@ -1,6 +1,3 @@ package ru.ifmo.sd.world.representation.units -class Player(private val identifier: Int) : GameUnit() { - override val id: Int - get() = this.identifier -} +class Player(id: Int): GameUnit(id) From 7ce9fc02e025bf7c09a0f21e6900fbc4eedd4f7b Mon Sep 17 00:00:00 2001 From: Sokolvyak Sergey Date: Sun, 25 Apr 2021 17:27:04 +0300 Subject: [PATCH 5/9] add level generation --- .../world/configuration/GameConfigurator.kt | 5 +- .../sd/world/generation/LevelGenerator.kt | 55 ++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfigurator.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfigurator.kt index db82f0e..d9aab6d 100644 --- a/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfigurator.kt +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfigurator.kt @@ -24,10 +24,11 @@ object GameConfigurator { fun configure(levelLength: Int, levelWidth: Int): GameConfiguration { gameLevel = GameLevel(LevelGenerator.generateLevel( levelLength, levelWidth)) - val player = Player(1) + val playerId = 1 + val player = Player(playerId) unitsPositions = UnitsPositionStorage() unitsHealths = UnitsHealthStorage() - unitsPositions.move(player, Position(0, 0)) + unitsPositions.move(player, Position(1, 1)) unitsHealths.addUnit(player) return GameConfiguration(gameLevel.gameLevel, unitsPositions.getPositions(), unitsHealths.getHealths()) } diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/generation/LevelGenerator.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/generation/LevelGenerator.kt index 1d95933..a547142 100644 --- a/game-server/src/main/kotlin/ru/ifmo/sd/world/generation/LevelGenerator.kt +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/generation/LevelGenerator.kt @@ -1,11 +1,64 @@ package ru.ifmo.sd.world.generation +import kotlin.random.Random + class LevelGenerator { companion object { private lateinit var level: Array fun generateLevel(length: Int, width: Int): Array { - return Array(length) { IntArray(width){1} } + val actualLength = if (length < 4) 9 else 2 * length + 1 + val actualWidth = if (width < 4) 9 else 2 * width + 1 + level = Array(actualLength) { IntArray(actualWidth) { 1 } } + + val enterPosRow = 0 + val enterPosColumn = 1 + level[enterPosRow][enterPosColumn] = 0 + + val exitPosRow = actualLength - 1 + val exitPosColumn = actualWidth - 2 + level[exitPosRow][exitPosColumn] = 0 + + val playerPosRow = 1 + val playerPosColumn = 1 + level[playerPosRow][playerPosColumn] = 0 + + generateRec(playerPosRow, playerPosColumn) + + return level + } + + private fun generateRec(curRow: Int, curColumn: Int) { + var i: Int + val dirs: Array = Array(3) { IntArray(2) { 0 } } + while (true) { + i = 0 + if (curRow > 1 && level[curRow - 2][curColumn] != 0) { + dirs[i][0] = curRow - 2 + dirs[i][1] = curColumn + i++ + } + if (curRow < level.size - 2 && level[curRow + 2][curColumn] != 0) { + dirs[i][0] = curRow + 2 + dirs[i][1] = curColumn + i++ + } + if (curColumn > 1 && level[curRow][curColumn - 2] != 0) { + dirs[i][0] = curRow + dirs[i][1] = curColumn - 2 + i++ + } + if (curColumn < level[0].size - 2 && level[curRow][curColumn + 2] != 0) { + dirs[i][0] = curRow + dirs[i][1] = curColumn + 2 + i++ + } + if (i == 0) break + i = (i * Random.nextDouble()).toInt() + level[(dirs[i][0] + curRow) / 2][(dirs[i][1] + curColumn) / 2] = 0 + level[dirs[i][0]][dirs[i][1]] = 0 + generateRec(dirs[i][0], dirs[i][1]) + } } } } From 2aba3f45b242260894132352c20b536ddda42b65 Mon Sep 17 00:00:00 2001 From: Sokolvyak Sergey Date: Sun, 25 Apr 2021 17:30:24 +0300 Subject: [PATCH 6/9] extend configuration data with player position info --- .../sd/world/configuration/GameConfiguration.kt | 6 +++--- .../sd/world/configuration/GameConfigurator.kt | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfiguration.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfiguration.kt index fadd18c..3f88854 100644 --- a/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfiguration.kt +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfiguration.kt @@ -1,13 +1,13 @@ package ru.ifmo.sd.world.configuration -import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import ru.ifmo.sd.world.representation.* import ru.ifmo.sd.world.representation.units.GameUnit @Serializable data class GameConfiguration( + val playerPos: Position, val level: Array, - val unitsPositions: Map<@Contextual GameUnit, @Contextual Position>, - val unitsHealthStorage: Map<@Contextual GameUnit, Int> + val unitsPositions: Map, + val unitsHealthStorage: Map ) diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfigurator.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfigurator.kt index d9aab6d..1c9db5f 100644 --- a/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfigurator.kt +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfigurator.kt @@ -22,14 +22,23 @@ object GameConfigurator { } fun configure(levelLength: Int, levelWidth: Int): GameConfiguration { - gameLevel = GameLevel(LevelGenerator.generateLevel( - levelLength, levelWidth)) + gameLevel = GameLevel( + LevelGenerator.generateLevel( + levelLength, levelWidth + ) + ) val playerId = 1 val player = Player(playerId) + val playerPos = Position(1, 1) unitsPositions = UnitsPositionStorage() unitsHealths = UnitsHealthStorage() - unitsPositions.move(player, Position(1, 1)) + unitsPositions.move(player, playerPos) unitsHealths.addUnit(player) - return GameConfiguration(gameLevel.gameLevel, unitsPositions.getPositions(), unitsHealths.getHealths()) + return GameConfiguration( + playerPos, + gameLevel.gameLevel, + unitsPositions.getPositions(), + unitsHealths.getHealths() + ) } } From 37bf9f85b13e777e8901fe097cb0da59101cd1f3 Mon Sep 17 00:00:00 2001 From: Kirill Esakov Date: Mon, 26 Apr 2021 09:57:12 +0300 Subject: [PATCH 7/9] client movements --- game-client/build.gradle.kts | 6 + .../src/main/kotlin/ru/ifmo/sd/Main.kt | 178 ++-------- .../src/main/kotlin/ru/ifmo/sd/stuff/GUI.kt | 331 ++++++++++++++++++ .../kotlin/ru/ifmo/sd/stuff/MapTextPane.kt | 6 +- .../main/kotlin/ru/ifmo/sd/stuff/SymbolMap.kt | 26 +- .../ru/ifmo/sd/httpapi/routes/GameRoutes.kt | 2 +- .../world/configuration/GameConfiguration.kt | 29 +- 7 files changed, 418 insertions(+), 160 deletions(-) create mode 100644 game-client/src/main/kotlin/ru/ifmo/sd/stuff/GUI.kt diff --git a/game-client/build.gradle.kts b/game-client/build.gradle.kts index 4984fce..f693aa4 100644 --- a/game-client/build.gradle.kts +++ b/game-client/build.gradle.kts @@ -5,6 +5,7 @@ plugins { group = "ru.ifmo.jb" version = "1.0-SNAPSHOT" +val ktor_version = "1.5.2" repositories { mavenCentral() @@ -12,6 +13,11 @@ repositories { dependencies { implementation(kotlin("stdlib")) + implementation("io.ktor:ktor-client-cio:$ktor_version") + implementation("io.ktor:ktor-client-gson:$ktor_version") + implementation("io.ktor:ktor-client-jackson:$ktor_version") + implementation("io.ktor:ktor-client-serialization:1.5.2") + implementation(project(":game-server")) } tasks.test { diff --git a/game-client/src/main/kotlin/ru/ifmo/sd/Main.kt b/game-client/src/main/kotlin/ru/ifmo/sd/Main.kt index 18da3f5..3caf8eb 100644 --- a/game-client/src/main/kotlin/ru/ifmo/sd/Main.kt +++ b/game-client/src/main/kotlin/ru/ifmo/sd/Main.kt @@ -1,164 +1,40 @@ package ru.ifmo.sd -import ru.ifmo.sd.stuff.* -import java.awt.BorderLayout +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.engine.cio.* +import io.ktor.client.features.json.* +import io.ktor.client.features.json.serializer.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import ru.ifmo.sd.httpapi.models.LevelConfiguration +import ru.ifmo.sd.stuff.GUI +import ru.ifmo.sd.world.configuration.GameConfiguration +import ru.ifmo.sd.world.configuration.GameConfigurationSerializable +import ru.ifmo.sd.world.representation.units.GameUnit import java.awt.EventQueue -import java.awt.event.KeyEvent -import javax.swing.* -import javax.swing.text.* -class GUI(title: String) : JFrame() { - val mainPanel = JPanel() - val headerPanel = JPanel() - val mapPanel = JPanel() - val infoPanel = JPanel() - var map = SymbolMap(mapPreviewSymbols) - - init { - createUI(title) - } - - private fun createUI(title: String) { - setTitle(title) - -// createMenuBar() - createLayout() - - defaultCloseOperation = EXIT_ON_CLOSE - setSize(800, 600) - isResizable = false - setLocationRelativeTo(null) +suspend fun main() { + val client = HttpClient(CIO) { + install(JsonFeature) { + serializer = KotlinxSerializer() + } } - - private fun createMenuBar() { - val menubar = JMenuBar() - val icon = ImageIcon("src/main/resources/exit.png") - - val file = JMenu("File") - file.mnemonic = KeyEvent.VK_F - - val eMenuItem = JMenuItem("Exit", icon) - eMenuItem.mnemonic = KeyEvent.VK_E - eMenuItem.toolTipText = "Exit application" - eMenuItem.addActionListener { System.exit(0) } - - file.add(eMenuItem) - menubar.add(file) - - jMenuBar = menubar + val response = client.post("http://localhost:8080/start") { + contentType(ContentType.Application.Json) + body = LevelConfiguration(levelLength = 15, levelWidth = 30) } - private fun createLayout() { - add(mainPanel) - mainPanel.layout = BorderLayout() - - headerPanel.setSize(800, 50) - mainPanel.add(headerPanel, BorderLayout.NORTH) - headerPanel.add(JButton("hey")) + println(response.deserializeBack()) - mapPanel.setSize(550, 550) - mainPanel.add(mapPanel, BorderLayout.WEST) - mapPanel.add(MapTextPane) - createMap() + EventQueue.invokeLater { createAndShowGUI(response.deserializeBack()) } - infoPanel.setSize(250, 550) - mainPanel.add(infoPanel, BorderLayout.EAST) - infoPanel.add(JButton("Okaey"), BorderLayout.WEST) - } - - private fun createMap() { - for (i in 0 until SymbolMap.rowSize) { - if (i != 0) { - insertText("\n", MapTextPane.colorMap[MapSymbolColor.BLACK]!!) - } - for (j in 0 until SymbolMap.columnSize) { - val symbol = map.rows[i][j] - val style = MapTextPane.colorMap[symbol.color]!! - insertText(symbol.content.toString(), style) - } - } - } - - private fun insertText(string: String, style: Style) { - try { - val doc = MapTextPane.document - doc.insertString(doc.length, string, style) - } catch (e: Exception) { - e.printStackTrace() // TODO - } - } +// client.close() } -private fun createAndShowGUI() { - val frame = GUI("Roguelike") +private fun createAndShowGUI(config: GameConfiguration) { + val frame = GUI("Roguelike", config) frame.isVisible = true -} - -fun main() { - EventQueue.invokeLater(::createAndShowGUI) -} - - - - - - - - - - - - - - - - - - - - - - - - - -private val mapPreviewText = listOf( - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", - "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -) - -val mapPreviewSymbols: List> = - mapPreviewText.map { s -> - s.map { c -> Symbol(c, MapSymbolColor.BLACK) } - } \ No newline at end of file +} \ No newline at end of file diff --git a/game-client/src/main/kotlin/ru/ifmo/sd/stuff/GUI.kt b/game-client/src/main/kotlin/ru/ifmo/sd/stuff/GUI.kt new file mode 100644 index 0000000..14a949b --- /dev/null +++ b/game-client/src/main/kotlin/ru/ifmo/sd/stuff/GUI.kt @@ -0,0 +1,331 @@ +package ru.ifmo.sd.stuff + +import ru.ifmo.sd.stuff.SymbolMap.Symbol.* +import ru.ifmo.sd.world.configuration.GameConfiguration +import ru.ifmo.sd.world.representation.Position +import java.awt.BorderLayout +import java.awt.Color +import java.awt.event.KeyEvent +import java.awt.event.KeyListener +import javax.swing.* +import javax.swing.text.Style + +enum class MoveEvent { + UP, DOWN, LEFT, RIGHT, NONE +} + +class GUI : JFrame, KeyListener { + constructor(title: String, gameConfiguration: GameConfiguration) : super() { + this.mainPanel = JPanel() + this.headerPanel = JPanel() + this.mapPanel = JPanel() + this.infoPanel = JPanel() + this.map = SymbolMap(gameConfiguration.level) + this.mapTextPane = MapTextPane() + this.currPos = gameConfiguration.playerPos +// this.keyListener = MyKeyListener() + createUI(title) + } + +// private enum class State { +// STARTING, INPUT_WAITING, PENDING_REQUEST +// } + + + private var currPos: Position + private val mainPanel: JPanel + private val headerPanel: JPanel + private val mapPanel: JPanel + private val infoPanel: JPanel + private var map: SymbolMap + private var mapTextPane: MapTextPane +// private var keyListener: MyKeyListener + +// private var lock = Object() +// private var state = State.STARTING + + private fun createUI(title: String) { + setTitle(title) + +// createMenuBar() + createLayout() + makeNotFocusable() + + defaultCloseOperation = EXIT_ON_CLOSE + setSize(800, 600) + isResizable = false + setLocationRelativeTo(null) + + // TODO should work regardless of component being focused + isFocusable = true + focusTraversalKeysEnabled = false + this.addKeyListener(this) + + requestFocus() + requestFocusInWindow() + +// state = State.INPUT_WAITING + start() + } + + private fun start() { + Thread { + while (true) { +// if (state === GAME_OVERED) break +// if (state === GAME_PUSHED) { +// synchronized(lock) { +// try { +// lock.wait() +// } catch (e: InterruptedException) { +// break +// } +// } +// } + + try { + Thread.sleep(30) + } catch (e: InterruptedException) { + break + } +// when (state) { +// GAME_BARRIER_SELECT -> bselector.draw(bufferScreen) +// GAME_MAP_SELECT -> { +// } +// GAME_RUNING -> runningDraw() +// GAME_FAILED, GAME_SUCCEED -> if (gresult != null) gresult.draw(bufferScreen) +// } + + mainPanel.repaint() + } + }.start() + } + + + private fun createMenuBar() { + val menubar = JMenuBar() + val icon = ImageIcon("src/main/resources/exit.png") + + val file = JMenu("File") + file.mnemonic = KeyEvent.VK_F + + val eMenuItem = JMenuItem("Exit", icon) + eMenuItem.mnemonic = KeyEvent.VK_E + eMenuItem.toolTipText = "Exit application" + eMenuItem.addActionListener { System.exit(0) } + + file.add(eMenuItem) + menubar.add(file) + + jMenuBar = menubar + } + + private fun createLayout() { + add(mainPanel) + mainPanel.layout = BorderLayout() + + headerPanel.setSize(800, 50) + mainPanel.add(headerPanel, BorderLayout.NORTH) +// val infoLabel = JLabel("loading...") +// infoLabel.isFocusable = false +// headerPanel.add(infoLabel) + + mapPanel.setSize(550, 550) + mainPanel.add(mapPanel, BorderLayout.WEST) + mapPanel.add(mapTextPane) + createMap() + + infoPanel.setSize(250, 550) + mainPanel.add(infoPanel, BorderLayout.EAST) + +// val hpLabel = JLabel("HP: ") +// hpLabel.isFocusable = false +// infoPanel.add(hpLabel, BorderLayout.WEST) + } + + private fun makeNotFocusable() { + mainPanel.isFocusable = false + headerPanel.isFocusable = false + mapPanel.isFocusable = false + infoPanel.isFocusable = false + mapTextPane.isFocusable = false + } + + private fun createMap() { + for (i in 0 until map.rowSize) { + if (i != 0) { + insertText("\n", mapTextPane.colorMap[MapSymbolColor.BLACK]!!) + } + for (j in 0 until map.columnSize) { + if (i == currPos.row && j == currPos.column) { + val style = mapTextPane.colorMap[MapSymbolColor.RED]!! + insertText(PLAYER.symbol.toString(), style) + } else { + val symbol = map.rows[i][j] + val style = mapTextPane.colorMap[symbol.color]!! + insertText(symbol.content.toString(), style) + } + } + } + } + + private fun insertText(string: String, style: Style) { + try { + val doc = mapTextPane.document + doc.insertString(doc.length, string, style) + } catch (e: Exception) { + e.printStackTrace() // TODO + } + } + + ///////////////////////////////////// + + var moved = MoveEvent.NONE + var didMove = false + + override fun keyPressed(e: KeyEvent?) { + if (e != null) { + println("Some key pressed, current=$moved, keyCode=${e.keyCode}, didMove=$didMove, currPos=$currPos") + } else { + println("e == null") + } + if (!didMove) { + if (e == null) { + moved = MoveEvent.NONE + return + } + + // TODO: rewrite to "when (e.keyCode)" + // note: may be not working when several keys pressed at the same time + if (e.keyCode == KeyEvent.VK_UP || e.keyCode == KeyEvent.VK_W) { + if (currPos.row > 0) { + val currRow = currPos.row - 1 + val currColumn = currPos.column + val currSymb = map.rows[currRow][currColumn] + if (currSymb.content == NONE.symbol) { + didMove = true + moved = MoveEvent.UP + map.rows[currPos.row][currPos.column] = ColoredSymbol(NONE.symbol) + map.rows[currRow][currColumn] = ColoredSymbol(PLAYER.symbol, currSymb.color) + replaceSymbolAt(currPos.row, currPos.column, NONE.symbol) + replaceSymbolAt(currPos.row - 1, currPos.column, PLAYER.symbol) + currPos = Position(currPos.row - 1, currPos.column) + } + } + } + if (e.keyCode == KeyEvent.VK_DOWN || e.keyCode == KeyEvent.VK_S) { + val currRow = currPos.row + 1 + val currColumn = currPos.column + val currSymb = map.rows[currRow][currColumn] + if (currPos.row < map.rowSize - 1 && currSymb.content == NONE.symbol) { + didMove = true + moved = MoveEvent.DOWN + map.rows[currPos.row][currPos.column] = ColoredSymbol(NONE.symbol) + map.rows[currRow][currColumn] = ColoredSymbol(PLAYER.symbol, currSymb.color) + replaceSymbolAt(currPos.row, currPos.column, NONE.symbol) + replaceSymbolAt(currPos.row + 1, currPos.column, PLAYER.symbol) + currPos = Position(currPos.row + 1, currPos.column) + } + } + if (e.keyCode == KeyEvent.VK_LEFT || e.keyCode == KeyEvent.VK_A) { + if (currPos.column > 0) { + val currRow = currPos.row + val currColumn = currPos.column - 1 + val currSymb = map.rows[currRow][currColumn] + if (currSymb.content == NONE.symbol) { + didMove = true + moved = MoveEvent.LEFT + map.rows[currPos.row][currPos.column] = ColoredSymbol(NONE.symbol) + map.rows[currRow][currColumn] = ColoredSymbol(PLAYER.symbol, currSymb.color) + replaceSymbolAt(currPos.row, currPos.column, NONE.symbol) + replaceSymbolAt(currPos.row, currPos.column - 1, PLAYER.symbol) + currPos = Position(currPos.row, currPos.column - 1) + } + } + } + if (e.keyCode == KeyEvent.VK_RIGHT || e.keyCode == KeyEvent.VK_D) { + val currRow = currPos.row + val currColumn = currPos.column + 1 + val currSymb = map.rows[currRow][currColumn] + if (currPos.column < map.columnSize - 1 && currSymb.content == NONE.symbol) { + didMove = true + moved = MoveEvent.RIGHT + map.rows[currPos.row][currPos.column] = ColoredSymbol(NONE.symbol) + map.rows[currRow][currColumn] = ColoredSymbol(PLAYER.symbol, currSymb.color) + replaceSymbolAt(currPos.row, currPos.column, NONE.symbol) + replaceSymbolAt(currPos.row, currPos.column + 1, PLAYER.symbol) + currPos = Position(currPos.row, currPos.column + 1) + } + } + + if (didMove) { + println("Should move=$moved") + + didMove = false + this.validate() + this.mapPanel.validate() + this.mapTextPane.validate() + this.repaint() + this.mapPanel.repaint() + this.mapTextPane.repaint() + } + } + } + + private fun replaceSymbolAt(row: Int, column: Int, symbol: Char) { + mapTextPane.isEditable = true + val index = row * (map.columnSize + 1) + column + mapTextPane.select(index, index + 1) + println("selected text='${mapTextPane.selectedText}'") + mapTextPane.replaceSelection(symbol.toString()) + if (symbol == PLAYER.symbol) { + mapTextPane.selectedTextColor = Color.RED + } else { + mapTextPane.selectedTextColor = Color.BLACK + } + mapTextPane.isEditable = false + } + + override fun keyReleased(e: KeyEvent?) {} + override fun keyTyped(e: KeyEvent?) {} +} + + +//private val mapPreviewText = listOf( +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", +//) +// +//val mapPreviewSymbols: List> = +// mapPreviewText.map { s -> +// s.map { c -> Symbol(c, MapSymbolColor.BLACK) } +// } \ No newline at end of file diff --git a/game-client/src/main/kotlin/ru/ifmo/sd/stuff/MapTextPane.kt b/game-client/src/main/kotlin/ru/ifmo/sd/stuff/MapTextPane.kt index 8ee5813..76d47dc 100644 --- a/game-client/src/main/kotlin/ru/ifmo/sd/stuff/MapTextPane.kt +++ b/game-client/src/main/kotlin/ru/ifmo/sd/stuff/MapTextPane.kt @@ -1,13 +1,15 @@ package ru.ifmo.sd.stuff +import ru.ifmo.sd.world.representation.Position import java.awt.Color import javax.swing.JTextPane import javax.swing.text.Style import javax.swing.text.StyleConstants +import java.awt.Graphics; -object MapTextPane : JTextPane() { +class MapTextPane : JTextPane() { private val FONT_FAMILY = "Monospaced" - private val FONT_SIZE = 12 + private val FONT_SIZE = 14 val colorMap: MutableMap = mutableMapOf() init { diff --git a/game-client/src/main/kotlin/ru/ifmo/sd/stuff/SymbolMap.kt b/game-client/src/main/kotlin/ru/ifmo/sd/stuff/SymbolMap.kt index 12eca35..cbede27 100644 --- a/game-client/src/main/kotlin/ru/ifmo/sd/stuff/SymbolMap.kt +++ b/game-client/src/main/kotlin/ru/ifmo/sd/stuff/SymbolMap.kt @@ -1,14 +1,30 @@ package ru.ifmo.sd.stuff +import ru.ifmo.sd.stuff.SymbolMap.Symbol.* + enum class MapSymbolColor { BLACK, GREEN, YELLOW, RED, BLUE, MAGENTA, ORANGE } -data class Symbol(val content: Char, val color: MapSymbolColor = MapSymbolColor.BLACK) +data class ColoredSymbol(val content: Char, val color: MapSymbolColor = MapSymbolColor.BLACK) + +class SymbolMap(level: Array) { + enum class Symbol(val symbol: Char, val color: MapSymbolColor = MapSymbolColor.BLACK) { + WALL('#'), + PLAYER('@', MapSymbolColor.RED), + NONE(' '), + } -data class SymbolMap(val rows: List> = List(rowSize) { List(columnSize) { Symbol(' ') } } ) { - companion object { - const val rowSize = 32 - const val columnSize = 64 + val rows: List> = level.map { arr -> + arr.map { i -> + ColoredSymbol( + if (i == 0) NONE.symbol else WALL.symbol + ) + }.toMutableList() } + + val rowSize: Int + get() = rows.size + val columnSize: Int + get() = rows[0].size } \ No newline at end of file diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/routes/GameRoutes.kt b/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/routes/GameRoutes.kt index 6d27b51..c91d1fa 100644 --- a/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/routes/GameRoutes.kt +++ b/game-server/src/main/kotlin/ru/ifmo/sd/httpapi/routes/GameRoutes.kt @@ -14,7 +14,7 @@ fun Route.gameRouting() { post("/start") { val levelConfiguration = call.receive() val startedGame = EventsHandler.startGame(levelConfiguration) - call.respond(message = startedGame, status = HttpStatusCode.Created) + call.respond(message = startedGame.makeSerializable(), status = HttpStatusCode.Created) } post("/move") { diff --git a/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfiguration.kt b/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfiguration.kt index 3f88854..911d1a3 100644 --- a/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfiguration.kt +++ b/game-server/src/main/kotlin/ru/ifmo/sd/world/configuration/GameConfiguration.kt @@ -1,6 +1,8 @@ package ru.ifmo.sd.world.configuration +import io.ktor.serialization.* import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json import ru.ifmo.sd.world.representation.* import ru.ifmo.sd.world.representation.units.GameUnit @@ -10,4 +12,29 @@ data class GameConfiguration( val level: Array, val unitsPositions: Map, val unitsHealthStorage: Map -) +) { + fun makeSerializable(): GameConfigurationSerializable { + return GameConfigurationSerializable( + playerPos, level, + unitsPositions.mapKeys { (gameUnit, _) -> Json.encodeToString(GameUnit.serializer(), gameUnit) }, + unitsHealthStorage.mapKeys { (gameUnit, _) -> Json.encodeToString(GameUnit.serializer(), gameUnit) } + ) + } +} + +@Serializable +data class GameConfigurationSerializable( + val playerPos: Position, + val level: Array, + val unitsPositions: Map, + val unitsHealthStorage: Map +) { + fun deserializeBack(): GameConfiguration { + return GameConfiguration( + playerPos, level, + unitsPositions.mapKeys { (jsonString, _) -> Json.decodeFromString(GameUnit.serializer(), jsonString) }, + unitsHealthStorage.mapKeys { (jsonString, _) -> Json.decodeFromString(GameUnit.serializer(), jsonString) } + ) + } + +} From 07fddb9147c3b08a3e82a8acd9ba01ab1c9d265e Mon Sep 17 00:00:00 2001 From: Kirill Esakov Date: Sat, 1 May 2021 17:29:04 +0300 Subject: [PATCH 8/9] client: map reloads when ended the level; bug fix, refactoring, polishing --- game-client/build.gradle.kts | 6 + .../src/main/kotlin/ru/ifmo/sd/Main.kt | 26 +- .../src/main/kotlin/ru/ifmo/sd/stuff/GUI.kt | 295 ++++++------------ .../kotlin/ru/ifmo/sd/stuff/MapTextPane.kt | 16 +- .../main/kotlin/ru/ifmo/sd/stuff/SymbolMap.kt | 30 +- 5 files changed, 146 insertions(+), 227 deletions(-) diff --git a/game-client/build.gradle.kts b/game-client/build.gradle.kts index f693aa4..2dfeba1 100644 --- a/game-client/build.gradle.kts +++ b/game-client/build.gradle.kts @@ -1,5 +1,6 @@ plugins { java + application kotlin("jvm") version "1.4.21" } @@ -13,6 +14,7 @@ repositories { dependencies { implementation(kotlin("stdlib")) + implementation("org.jetbrains.kotlin:kotlin-reflect:1.1.0") implementation("io.ktor:ktor-client-cio:$ktor_version") implementation("io.ktor:ktor-client-gson:$ktor_version") implementation("io.ktor:ktor-client-jackson:$ktor_version") @@ -20,6 +22,10 @@ dependencies { implementation(project(":game-server")) } +application { + mainClass.set("ru.ifmo.sd.MainKt") +} + tasks.test { useJUnitPlatform() } diff --git a/game-client/src/main/kotlin/ru/ifmo/sd/Main.kt b/game-client/src/main/kotlin/ru/ifmo/sd/Main.kt index 3caf8eb..d424b4b 100644 --- a/game-client/src/main/kotlin/ru/ifmo/sd/Main.kt +++ b/game-client/src/main/kotlin/ru/ifmo/sd/Main.kt @@ -9,23 +9,25 @@ import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import ru.ifmo.sd.httpapi.models.LevelConfiguration +import ru.ifmo.sd.httpapi.models.MoveEventData import ru.ifmo.sd.stuff.GUI import ru.ifmo.sd.world.configuration.GameConfiguration import ru.ifmo.sd.world.configuration.GameConfigurationSerializable +import ru.ifmo.sd.world.representation.Position import ru.ifmo.sd.world.representation.units.GameUnit +import ru.ifmo.sd.world.representation.units.Player import java.awt.EventQueue +private var client: HttpClient? = null + suspend fun main() { - val client = HttpClient(CIO) { + client = HttpClient(CIO) { install(JsonFeature) { serializer = KotlinxSerializer() } } - val response = client.post("http://localhost:8080/start") { - contentType(ContentType.Application.Json) - body = LevelConfiguration(levelLength = 15, levelWidth = 30) - } + val response = makeNewGameConfiguration() println(response.deserializeBack()) @@ -34,6 +36,20 @@ suspend fun main() { // client.close() } +internal suspend fun makeNewGameConfiguration(): GameConfigurationSerializable { + return client!!.post("http://localhost:8080/start") { + contentType(ContentType.Application.Json) + body = LevelConfiguration(levelLength = 3, levelWidth = 3) + } +} + +internal suspend fun makeMove(newPos: Position, unit: GameUnit = Player(0)) { + client!!.post("http://localhost:8080/move") { + contentType(ContentType.Application.Json) + body = MoveEventData(unit, newPos) + } +} + private fun createAndShowGUI(config: GameConfiguration) { val frame = GUI("Roguelike", config) frame.isVisible = true diff --git a/game-client/src/main/kotlin/ru/ifmo/sd/stuff/GUI.kt b/game-client/src/main/kotlin/ru/ifmo/sd/stuff/GUI.kt index 14a949b..b429734 100644 --- a/game-client/src/main/kotlin/ru/ifmo/sd/stuff/GUI.kt +++ b/game-client/src/main/kotlin/ru/ifmo/sd/stuff/GUI.kt @@ -1,11 +1,16 @@ package ru.ifmo.sd.stuff -import ru.ifmo.sd.stuff.SymbolMap.Symbol.* +import kotlinx.coroutines.runBlocking +import ru.ifmo.sd.makeMove +import ru.ifmo.sd.makeNewGameConfiguration +import ru.ifmo.sd.stuff.ColoredSymbol.* import ru.ifmo.sd.world.configuration.GameConfiguration import ru.ifmo.sd.world.representation.Position import java.awt.BorderLayout -import java.awt.Color +import java.awt.Color.* +import java.awt.Container import java.awt.event.KeyEvent +import java.awt.event.KeyEvent.* import java.awt.event.KeyListener import javax.swing.* import javax.swing.text.Style @@ -14,40 +19,23 @@ enum class MoveEvent { UP, DOWN, LEFT, RIGHT, NONE } -class GUI : JFrame, KeyListener { - constructor(title: String, gameConfiguration: GameConfiguration) : super() { - this.mainPanel = JPanel() - this.headerPanel = JPanel() - this.mapPanel = JPanel() - this.infoPanel = JPanel() - this.map = SymbolMap(gameConfiguration.level) - this.mapTextPane = MapTextPane() - this.currPos = gameConfiguration.playerPos -// this.keyListener = MyKeyListener() +class GUI(title: String, gameConfiguration: GameConfiguration) : JFrame(), KeyListener { + private var currPos = gameConfiguration.playerPos + private val mainPanel = JPanel() + private val headerPanel = JPanel() + private val mapPanel = JPanel() + private val infoPanel = JPanel() + private var map = SymbolMap(gameConfiguration) + private var mapTextPane = MapTextPane() + + init { createUI(title) + start() } -// private enum class State { -// STARTING, INPUT_WAITING, PENDING_REQUEST -// } - - - private var currPos: Position - private val mainPanel: JPanel - private val headerPanel: JPanel - private val mapPanel: JPanel - private val infoPanel: JPanel - private var map: SymbolMap - private var mapTextPane: MapTextPane -// private var keyListener: MyKeyListener - -// private var lock = Object() -// private var state = State.STARTING - private fun createUI(title: String) { setTitle(title) -// createMenuBar() createLayout() makeNotFocusable() @@ -63,62 +51,23 @@ class GUI : JFrame, KeyListener { requestFocus() requestFocusInWindow() - -// state = State.INPUT_WAITING - start() } private fun start() { Thread { while (true) { -// if (state === GAME_OVERED) break -// if (state === GAME_PUSHED) { -// synchronized(lock) { -// try { -// lock.wait() -// } catch (e: InterruptedException) { -// break -// } -// } -// } try { Thread.sleep(30) } catch (e: InterruptedException) { break } -// when (state) { -// GAME_BARRIER_SELECT -> bselector.draw(bufferScreen) -// GAME_MAP_SELECT -> { -// } -// GAME_RUNING -> runningDraw() -// GAME_FAILED, GAME_SUCCEED -> if (gresult != null) gresult.draw(bufferScreen) -// } mainPanel.repaint() } }.start() } - - private fun createMenuBar() { - val menubar = JMenuBar() - val icon = ImageIcon("src/main/resources/exit.png") - - val file = JMenu("File") - file.mnemonic = KeyEvent.VK_F - - val eMenuItem = JMenuItem("Exit", icon) - eMenuItem.mnemonic = KeyEvent.VK_E - eMenuItem.toolTipText = "Exit application" - eMenuItem.addActionListener { System.exit(0) } - - file.add(eMenuItem) - menubar.add(file) - - jMenuBar = menubar - } - private fun createLayout() { add(mainPanel) mainPanel.layout = BorderLayout() @@ -132,7 +81,7 @@ class GUI : JFrame, KeyListener { mapPanel.setSize(550, 550) mainPanel.add(mapPanel, BorderLayout.WEST) mapPanel.add(mapTextPane) - createMap() + createMapTextPane() infoPanel.setSize(250, 550) mainPanel.add(infoPanel, BorderLayout.EAST) @@ -142,30 +91,34 @@ class GUI : JFrame, KeyListener { // infoPanel.add(hpLabel, BorderLayout.WEST) } - private fun makeNotFocusable() { - mainPanel.isFocusable = false - headerPanel.isFocusable = false - mapPanel.isFocusable = false - infoPanel.isFocusable = false - mapTextPane.isFocusable = false + private fun makeNotFocusable(container: Container = this) { + container.isFocusable = false + for (c in container.components) { + if (c is Container) makeNotFocusable(c) + } } - private fun createMap() { + private fun createMapTextPane() { + mapTextPane.document.remove(0, mapTextPane.document.length) for (i in 0 until map.rowSize) { if (i != 0) { - insertText("\n", mapTextPane.colorMap[MapSymbolColor.BLACK]!!) + insertText("\n", mapTextPane.colorMap[BLACK]!!) } for (j in 0 until map.columnSize) { - if (i == currPos.row && j == currPos.column) { - val style = mapTextPane.colorMap[MapSymbolColor.RED]!! - insertText(PLAYER.symbol.toString(), style) - } else { - val symbol = map.rows[i][j] - val style = mapTextPane.colorMap[symbol.color]!! - insertText(symbol.content.toString(), style) - } + val symbol = map.rows[i][j] + val style = mapTextPane.colorMap[symbol.color]!! + insertText(symbol.char.toString(), style) } } + // TODO: make lineSpacing = 0 +// mapTextPane.margin = Insets(0, 0, 0, 0) +// mapTextPane.selectAll() +// val aSet = SimpleAttributeSet(mapTextPane.paragraphAttributes) +// val doc = mapTextPane.styledDocument +// mapTextPane.setParagraphAttributes(aSet, false) +// doc.setParagraphAttributes(0, doc.length, aSet, false) +// StyleConstants.setLineSpacing(aSet, 0F) +// mapTextPane.select(0, 0) } private fun insertText(string: String, style: Style) { @@ -182,6 +135,8 @@ class GUI : JFrame, KeyListener { var moved = MoveEvent.NONE var didMove = false + override fun keyReleased(e: KeyEvent?) {} + override fun keyTyped(e: KeyEvent?) {} override fun keyPressed(e: KeyEvent?) { if (e != null) { println("Some key pressed, current=$moved, keyCode=${e.keyCode}, didMove=$didMove, currPos=$currPos") @@ -189,71 +144,41 @@ class GUI : JFrame, KeyListener { println("e == null") } if (!didMove) { - if (e == null) { - moved = MoveEvent.NONE - return - } - - // TODO: rewrite to "when (e.keyCode)" - // note: may be not working when several keys pressed at the same time - if (e.keyCode == KeyEvent.VK_UP || e.keyCode == KeyEvent.VK_W) { - if (currPos.row > 0) { - val currRow = currPos.row - 1 - val currColumn = currPos.column - val currSymb = map.rows[currRow][currColumn] - if (currSymb.content == NONE.symbol) { + when (e?.keyCode) { + null -> { + moved = MoveEvent.NONE + } + VK_UP, VK_W -> { + val newPos = Position(currPos.row - 1, currPos.column) + if (replaceSymbol(currPos, newPos)) { didMove = true moved = MoveEvent.UP - map.rows[currPos.row][currPos.column] = ColoredSymbol(NONE.symbol) - map.rows[currRow][currColumn] = ColoredSymbol(PLAYER.symbol, currSymb.color) - replaceSymbolAt(currPos.row, currPos.column, NONE.symbol) - replaceSymbolAt(currPos.row - 1, currPos.column, PLAYER.symbol) - currPos = Position(currPos.row - 1, currPos.column) + currPos = newPos } } - } - if (e.keyCode == KeyEvent.VK_DOWN || e.keyCode == KeyEvent.VK_S) { - val currRow = currPos.row + 1 - val currColumn = currPos.column - val currSymb = map.rows[currRow][currColumn] - if (currPos.row < map.rowSize - 1 && currSymb.content == NONE.symbol) { - didMove = true - moved = MoveEvent.DOWN - map.rows[currPos.row][currPos.column] = ColoredSymbol(NONE.symbol) - map.rows[currRow][currColumn] = ColoredSymbol(PLAYER.symbol, currSymb.color) - replaceSymbolAt(currPos.row, currPos.column, NONE.symbol) - replaceSymbolAt(currPos.row + 1, currPos.column, PLAYER.symbol) - currPos = Position(currPos.row + 1, currPos.column) + VK_DOWN, VK_S -> { + val newPos = Position(currPos.row + 1, currPos.column) + if (replaceSymbol(currPos, newPos)) { + didMove = true + moved = MoveEvent.DOWN + currPos = newPos + } } - } - if (e.keyCode == KeyEvent.VK_LEFT || e.keyCode == KeyEvent.VK_A) { - if (currPos.column > 0) { - val currRow = currPos.row - val currColumn = currPos.column - 1 - val currSymb = map.rows[currRow][currColumn] - if (currSymb.content == NONE.symbol) { + VK_LEFT, VK_A -> { + val newPos = Position(currPos.row, currPos.column - 1) + if (replaceSymbol(currPos, newPos)) { didMove = true moved = MoveEvent.LEFT - map.rows[currPos.row][currPos.column] = ColoredSymbol(NONE.symbol) - map.rows[currRow][currColumn] = ColoredSymbol(PLAYER.symbol, currSymb.color) - replaceSymbolAt(currPos.row, currPos.column, NONE.symbol) - replaceSymbolAt(currPos.row, currPos.column - 1, PLAYER.symbol) - currPos = Position(currPos.row, currPos.column - 1) + currPos = newPos } } - } - if (e.keyCode == KeyEvent.VK_RIGHT || e.keyCode == KeyEvent.VK_D) { - val currRow = currPos.row - val currColumn = currPos.column + 1 - val currSymb = map.rows[currRow][currColumn] - if (currPos.column < map.columnSize - 1 && currSymb.content == NONE.symbol) { - didMove = true - moved = MoveEvent.RIGHT - map.rows[currPos.row][currPos.column] = ColoredSymbol(NONE.symbol) - map.rows[currRow][currColumn] = ColoredSymbol(PLAYER.symbol, currSymb.color) - replaceSymbolAt(currPos.row, currPos.column, NONE.symbol) - replaceSymbolAt(currPos.row, currPos.column + 1, PLAYER.symbol) - currPos = Position(currPos.row, currPos.column + 1) + VK_RIGHT, VK_D -> { + val newPos = Position(currPos.row, currPos.column + 1) + if (replaceSymbol(currPos, newPos)) { + didMove = true + moved = MoveEvent.RIGHT + currPos = newPos + } } } @@ -267,65 +192,43 @@ class GUI : JFrame, KeyListener { this.repaint() this.mapPanel.repaint() this.mapTextPane.repaint() + + runBlocking { + if (currPos.row == map.rowSize - 1 && currPos.column == map.columnSize - 2) { + val newConfig = makeNewGameConfiguration().deserializeBack() + map = SymbolMap(newConfig) + createMapTextPane() + currPos = newConfig.playerPos + } else { + makeMove(currPos) + } + } } } } - private fun replaceSymbolAt(row: Int, column: Int, symbol: Char) { + private fun replaceSymbol(oldPos: Position, newPos: Position, replaceSymb: ColoredSymbol = NONE): Boolean { + if (newPos.row < map.rowSize && newPos.row >= 0 && + newPos.column < map.columnSize && newPos.column >= 0 && + map.rows[newPos.row][newPos.column] == NONE + ) { + val oldSymb = map.rows[oldPos.row][oldPos.column] + map.rows[oldPos.row][oldPos.column] = replaceSymb + map.rows[newPos.row][newPos.column] = oldSymb + replacePaneSymbol(oldPos, replaceSymb) + replacePaneSymbol(newPos, oldSymb) + return true + } + return false + } + + private fun replacePaneSymbol(pos: Position, symbol: ColoredSymbol) { mapTextPane.isEditable = true - val index = row * (map.columnSize + 1) + column + val index = pos.row * (map.columnSize + 1) + pos.column mapTextPane.select(index, index + 1) - println("selected text='${mapTextPane.selectedText}'") - mapTextPane.replaceSelection(symbol.toString()) - if (symbol == PLAYER.symbol) { - mapTextPane.selectedTextColor = Color.RED - } else { - mapTextPane.selectedTextColor = Color.BLACK - } +// println("selected text='${mapTextPane.selectedText}'") + mapTextPane.selectionColor = symbol.color + mapTextPane.replaceSelection(symbol.char.toString()) mapTextPane.isEditable = false } - - override fun keyReleased(e: KeyEvent?) {} - override fun keyTyped(e: KeyEvent?) {} -} - - -//private val mapPreviewText = listOf( -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -// "qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09qwert 09", -//) -// -//val mapPreviewSymbols: List> = -// mapPreviewText.map { s -> -// s.map { c -> Symbol(c, MapSymbolColor.BLACK) } -// } \ No newline at end of file +} \ No newline at end of file diff --git a/game-client/src/main/kotlin/ru/ifmo/sd/stuff/MapTextPane.kt b/game-client/src/main/kotlin/ru/ifmo/sd/stuff/MapTextPane.kt index 76d47dc..288f4ad 100644 --- a/game-client/src/main/kotlin/ru/ifmo/sd/stuff/MapTextPane.kt +++ b/game-client/src/main/kotlin/ru/ifmo/sd/stuff/MapTextPane.kt @@ -2,6 +2,7 @@ package ru.ifmo.sd.stuff import ru.ifmo.sd.world.representation.Position import java.awt.Color +import java.awt.Color.* import javax.swing.JTextPane import javax.swing.text.Style import javax.swing.text.StyleConstants @@ -9,23 +10,18 @@ import java.awt.Graphics; class MapTextPane : JTextPane() { private val FONT_FAMILY = "Monospaced" - private val FONT_SIZE = 14 - val colorMap: MutableMap = mutableMapOf() + private val FONT_SIZE = 24 + val colorMap: MutableMap = mutableMapOf() + private val colors = arrayOf(BLACK, RED, YELLOW, GREEN, MAGENTA, ORANGE, BLUE, GRAY) init { isEditable = false - for (color in MapSymbolColor.values()) { + for (color in colors) { val newStyle = addStyle(color.toString(), null) StyleConstants.setFontFamily(newStyle, FONT_FAMILY) StyleConstants.setFontSize(newStyle, FONT_SIZE) colorMap[color] = newStyle + StyleConstants.setForeground(newStyle, color) } - StyleConstants.setForeground(colorMap[MapSymbolColor.BLACK], Color.BLACK) - StyleConstants.setForeground(colorMap[MapSymbolColor.GREEN], Color.GREEN) - StyleConstants.setForeground(colorMap[MapSymbolColor.RED], Color.RED) - StyleConstants.setForeground(colorMap[MapSymbolColor.YELLOW], Color.YELLOW) - StyleConstants.setForeground(colorMap[MapSymbolColor.MAGENTA], Color.MAGENTA) - StyleConstants.setForeground(colorMap[MapSymbolColor.ORANGE], Color.ORANGE) - StyleConstants.setForeground(colorMap[MapSymbolColor.BLUE], Color.BLUE) } } \ No newline at end of file diff --git a/game-client/src/main/kotlin/ru/ifmo/sd/stuff/SymbolMap.kt b/game-client/src/main/kotlin/ru/ifmo/sd/stuff/SymbolMap.kt index cbede27..72d0a48 100644 --- a/game-client/src/main/kotlin/ru/ifmo/sd/stuff/SymbolMap.kt +++ b/game-client/src/main/kotlin/ru/ifmo/sd/stuff/SymbolMap.kt @@ -1,26 +1,24 @@ package ru.ifmo.sd.stuff -import ru.ifmo.sd.stuff.SymbolMap.Symbol.* +import ru.ifmo.sd.stuff.ColoredSymbol.* +import ru.ifmo.sd.world.configuration.GameConfiguration +import java.awt.Color +import java.awt.Color.* -enum class MapSymbolColor { - BLACK, GREEN, YELLOW, RED, BLUE, MAGENTA, ORANGE -} -data class ColoredSymbol(val content: Char, val color: MapSymbolColor = MapSymbolColor.BLACK) +enum class ColoredSymbol(val char: Char, val color: Color = BLACK) { + WALL(0x2591.toChar(), GRAY), + PLAYER(0x267F.toChar()/*, RED*/), + NONE(' '), +} -class SymbolMap(level: Array) { - enum class Symbol(val symbol: Char, val color: MapSymbolColor = MapSymbolColor.BLACK) { - WALL('#'), - PLAYER('@', MapSymbolColor.RED), - NONE(' '), +class SymbolMap(config: GameConfiguration) { + val rows: List> = config.level.map { arr -> + arr.map { i -> if (i == 0) NONE else WALL }.toMutableList() } - val rows: List> = level.map { arr -> - arr.map { i -> - ColoredSymbol( - if (i == 0) NONE.symbol else WALL.symbol - ) - }.toMutableList() + init { + rows[config.playerPos.row][config.playerPos.column] = PLAYER } val rowSize: Int From fb27b4e132b06d0bbcd288d6ac2b246c0dba994a Mon Sep 17 00:00:00 2001 From: Kirill Esakov Date: Mon, 3 May 2021 00:45:37 +0300 Subject: [PATCH 9/9] client: small fixes --- game-client/build.gradle.kts | 2 +- game-client/src/main/kotlin/ru/ifmo/sd/Main.kt | 2 +- game-client/src/main/kotlin/ru/ifmo/sd/stuff/GUI.kt | 9 +++++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/game-client/build.gradle.kts b/game-client/build.gradle.kts index 2dfeba1..34c6d13 100644 --- a/game-client/build.gradle.kts +++ b/game-client/build.gradle.kts @@ -14,7 +14,7 @@ repositories { dependencies { implementation(kotlin("stdlib")) - implementation("org.jetbrains.kotlin:kotlin-reflect:1.1.0") + implementation("org.jetbrains.kotlin:kotlin-reflect:1.4.32") implementation("io.ktor:ktor-client-cio:$ktor_version") implementation("io.ktor:ktor-client-gson:$ktor_version") implementation("io.ktor:ktor-client-jackson:$ktor_version") diff --git a/game-client/src/main/kotlin/ru/ifmo/sd/Main.kt b/game-client/src/main/kotlin/ru/ifmo/sd/Main.kt index d424b4b..c05b6d9 100644 --- a/game-client/src/main/kotlin/ru/ifmo/sd/Main.kt +++ b/game-client/src/main/kotlin/ru/ifmo/sd/Main.kt @@ -19,7 +19,7 @@ import ru.ifmo.sd.world.representation.units.Player import java.awt.EventQueue -private var client: HttpClient? = null +internal var client: HttpClient? = null suspend fun main() { client = HttpClient(CIO) { diff --git a/game-client/src/main/kotlin/ru/ifmo/sd/stuff/GUI.kt b/game-client/src/main/kotlin/ru/ifmo/sd/stuff/GUI.kt index b429734..27fcaae 100644 --- a/game-client/src/main/kotlin/ru/ifmo/sd/stuff/GUI.kt +++ b/game-client/src/main/kotlin/ru/ifmo/sd/stuff/GUI.kt @@ -1,6 +1,7 @@ package ru.ifmo.sd.stuff import kotlinx.coroutines.runBlocking +import ru.ifmo.sd.client import ru.ifmo.sd.makeMove import ru.ifmo.sd.makeNewGameConfiguration import ru.ifmo.sd.stuff.ColoredSymbol.* @@ -12,6 +13,8 @@ import java.awt.Container import java.awt.event.KeyEvent import java.awt.event.KeyEvent.* import java.awt.event.KeyListener +import java.awt.event.WindowAdapter +import java.awt.event.WindowEvent import javax.swing.* import javax.swing.text.Style @@ -40,6 +43,12 @@ class GUI(title: String, gameConfiguration: GameConfiguration) : JFrame(), KeyLi makeNotFocusable() defaultCloseOperation = EXIT_ON_CLOSE + addWindowListener(object : WindowAdapter() { + override fun windowClosing(e: WindowEvent) { + client!!.close() + e.window.dispose() + } + }) setSize(800, 600) isResizable = false setLocationRelativeTo(null)