Skip to content

Commit

Permalink
server: extract DataStorage and Game
Browse files Browse the repository at this point in the history
  • Loading branch information
ForNeVeR committed Jan 6, 2024
1 parent 9f3f5e4 commit 541ee41
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 36 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package ru.org.codingteam.hyperspace.server

import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

class DataStorage {
private val mutex = Mutex()
private val storage = mutableMapOf<Int, Game>()
private var lastId = 0

suspend fun getAllGames(): List<Game> = mutex.withLock { storage.values.toList() }
suspend fun getGame(id: Int): Game? = mutex.withLock { storage[id] }

suspend fun createGame(fieldSize: FieldSize): Int = mutex.withLock {
val game = Game(fieldSize)
val nextId = ++lastId
storage[nextId] = game
nextId
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ru.org.codingteam.hyperspace.server

enum class GameStatus {
Waiting, Started, Finished
}

data class FieldSize(val width: Int, val height: Int)
data class Game(val size: FieldSize, val playerCount: Int = 0, val status: GameStatus = GameStatus.Waiting)
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.ktor.http.*
import io.ktor.jackson.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import ru.org.codingteam.hyperspace.server.DataStorage
import ru.org.codingteam.hyperspace.web.features.configureGameApi

fun Application.configureLogging() {
Expand All @@ -25,9 +26,10 @@ fun Application.configureSerialization() {
}

fun main() {
val storage = DataStorage()
embeddedServer(Netty, port = 8080, host = "localhost") {
configureLogging()
configureSerialization()
configureGameApi()
configureGameApi(storage)
}.start(wait = true)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,34 @@ import io.ktor.http.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import ru.org.codingteam.hyperspace.server.DataStorage
import ru.org.codingteam.hyperspace.server.FieldSize

data class GameDefinition(val width: Int, val height: Int)

fun Application.configureGameApi() {
val mutex = Mutex()
val storage = mutableMapOf<Int, GameDefinition>()
var lastId = 0
fun Application.configureGameApi(storage: DataStorage) {
fun ApplicationCall.getInt(name: String): Int? = parameters[name]?.toIntOrNull()
suspend fun ApplicationCall.invalidArg(name: String) = respondText(
"Invalid parameter: $name",
status = HttpStatusCode.BadRequest
)
suspend fun ApplicationCall.gameNotFound() = respondText(
"Game not found",
status = HttpStatusCode.NotFound
)

routing {
route("/api/game") {
get("/") {
val state = mutex.withLock { storage.values.toList() }
call.respond(state)
val games = storage.getAllGames()
call.respond(games)
}
get("{id}") {
val id = call.parameters["id"]?.toIntOrNull() ?: return@get call.respondText(
"Invalid id",
status = HttpStatusCode.BadRequest
)
val game = mutex.withLock { storage[id] } ?: return@get call.respondText(
"Game not found",
status = HttpStatusCode.NotFound
)

val id = call.getInt("id") ?: return@get call.invalidArg("id")
val game = storage.getGame(id) ?: return@get call.gameNotFound()
call.respond(game)
}
post("/") {
val newGame = call.receive<GameDefinition>()
val id = mutex.withLock {
val nextId = ++lastId
storage[nextId] = newGame
nextId
}
val newGameParameters = call.receive<FieldSize>()
val id = storage.createGame(newGameParameters)
call.respond(id)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@ package ru.org.codingteam.hyperspace.web.features
import io.ktor.http.*
import io.ktor.server.testing.*
import kotlinx.coroutines.runBlocking
import ru.org.codingteam.hyperspace.server.DataStorage
import ru.org.codingteam.hyperspace.server.FieldSize
import ru.org.codingteam.hyperspace.server.Game
import ru.org.codingteam.hyperspace.web.configureLogging
import ru.org.codingteam.hyperspace.web.configureSerialization
import ru.org.codingteam.hyperspace.web.jsonSerializer
import kotlin.test.Test
import kotlin.test.assertEquals

class GamesTests {
private val storage = DataStorage()
private fun withTestApplication(test: TestApplicationEngine.() -> Unit) {
withTestApplication({
configureLogging()
configureSerialization()
configureGameApi()
configureGameApi(storage)
}, test)
}

Expand Down Expand Up @@ -47,10 +51,10 @@ class GamesTests {
fun testGameList() {
withTestApplication {
sendRequest("/api/game/") {
receive<List<GameDefinition>>()
receive<List<Game>>()
assertEquals(HttpStatusCode.OK, response.status())
response.content
val list = runBlocking { receive<List<GameDefinition>>() }
val list = runBlocking { receive<List<Game>>() }
assertEquals(list, emptyList())
}
}
Expand All @@ -68,12 +72,12 @@ class GamesTests {
@Test
fun newGameShouldBeAdded() {
withTestApplication {
val game = GameDefinition(480, 640)
sendRequest("/api/game/", HttpMethod.Post, game) {
val size = FieldSize(480, 640)
sendRequest("/api/game/", HttpMethod.Post, size) {
val id = receive<Int>()
sendRequest("/api/game/$id") {
val createdGame = receive<GameDefinition>()
assertEquals(game, createdGame)
val createdGame = receive<Game>()
assertEquals(size, createdGame.size)
}
}
}
Expand All @@ -82,11 +86,11 @@ class GamesTests {
@Test
fun newGameIdShouldBeGenerated() {
withTestApplication {
val game = GameDefinition(480, 640)
sendRequest("/api/game/", HttpMethod.Post, game) {
val size = FieldSize(480, 640)
sendRequest("/api/game/", HttpMethod.Post, size) {
assertEquals(1, receive())
}
sendRequest("/api/game/", HttpMethod.Post, game) {
sendRequest("/api/game/", HttpMethod.Post, size) {
assertEquals(2, receive())
}
}
Expand Down

0 comments on commit 541ee41

Please sign in to comment.