From a452e7753bdc07eab181926efca0a85301b8734b Mon Sep 17 00:00:00 2001 From: Janis Date: Sun, 23 Nov 2025 17:39:36 +0100 Subject: [PATCH] feat(api): Implement received hand event handling and UI updates --- .../model/sessions/UserWebsocketActor.scala | 2 +- knockoutwhistweb/app/util/WebUIUtils.scala | 21 +++++++++- .../app/util/WebsocketEventMapper.scala | 22 +++++++++- .../util/mapper/ReceivedHandEventMapper.scala | 16 +++++++ .../app/util/mapper/SimpleEventMapper.scala | 11 +++++ knockoutwhistweb/app/views/main.scala.html | 2 + knockoutwhistweb/public/javascripts/events.js | 42 +++++++++++++++++++ .../public/javascripts/interact.js | 7 ++++ .../public/javascripts/websocket.js | 8 ++-- 9 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 knockoutwhistweb/app/util/mapper/ReceivedHandEventMapper.scala create mode 100644 knockoutwhistweb/app/util/mapper/SimpleEventMapper.scala create mode 100644 knockoutwhistweb/public/javascripts/events.js create mode 100644 knockoutwhistweb/public/javascripts/interact.js diff --git a/knockoutwhistweb/app/model/sessions/UserWebsocketActor.scala b/knockoutwhistweb/app/model/sessions/UserWebsocketActor.scala index fba8100..0a9a820 100644 --- a/knockoutwhistweb/app/model/sessions/UserWebsocketActor.scala +++ b/knockoutwhistweb/app/model/sessions/UserWebsocketActor.scala @@ -87,7 +87,7 @@ class UserWebsocketActor( } def transmitEventToClient(event: SimpleEvent): Unit = { - val jsonString = WebsocketEventMapper.toJsonString(event) + val jsonString = WebsocketEventMapper.toJson(event) out ! jsonString } diff --git a/knockoutwhistweb/app/util/WebUIUtils.scala b/knockoutwhistweb/app/util/WebUIUtils.scala index f797985..625303d 100644 --- a/knockoutwhistweb/app/util/WebUIUtils.scala +++ b/knockoutwhistweb/app/util/WebUIUtils.scala @@ -1,8 +1,9 @@ package util -import de.knockoutwhist.cards.Card +import de.knockoutwhist.cards.{Card, Hand} import de.knockoutwhist.cards.CardValue.* import de.knockoutwhist.cards.Suit.{Clubs, Diamonds, Hearts, Spades} +import play.api.libs.json.{JsArray, Json} import play.twirl.api.Html import scalafx.scene.image.Image @@ -36,4 +37,22 @@ object WebUIUtils { f"$cv$s" } + /** + * Map a Hand to a JsArray of cards + * Per card it has the string and the index in the hand + * @param hand + * @return + */ + def handToJson(hand: Hand): JsArray = { + val cards = hand.cards + JsArray( + cards.zipWithIndex.map { case (card, index) => + Json.obj( + "idx" -> index, + "card" -> cardtoString(card) + ) + } + ) + } + } diff --git a/knockoutwhistweb/app/util/WebsocketEventMapper.scala b/knockoutwhistweb/app/util/WebsocketEventMapper.scala index 7e01a7f..f8f55c1 100644 --- a/knockoutwhistweb/app/util/WebsocketEventMapper.scala +++ b/knockoutwhistweb/app/util/WebsocketEventMapper.scala @@ -1,8 +1,10 @@ package util import de.knockoutwhist.utils.events.SimpleEvent +import play.api.libs.json.{JsValue, Json} import tools.jackson.databind.json.JsonMapper import tools.jackson.module.scala.ScalaModule +import util.mapper.{ReceivedHandEventMapper, SimpleEventMapper} object WebsocketEventMapper { @@ -13,8 +15,24 @@ object WebsocketEventMapper { private val mapper = JsonMapper.builder().addModule(scalaModule).build() - def toJsonString(obj: SimpleEvent): String = { - mapper.writeValueAsString(obj) + private var customMappers: Map[String,SimpleEventMapper[SimpleEvent]] = Map() + + private def registerCustomMapper[T <: SimpleEvent](mapper: SimpleEventMapper[T]): Unit = { + customMappers = customMappers + (mapper.id -> mapper.asInstanceOf[SimpleEventMapper[SimpleEvent]]) + } + + // Register all custom mappers here + registerCustomMapper(ReceivedHandEventMapper) + + def toJson(obj: SimpleEvent): JsValue = { + val data = if (customMappers.contains(obj.id)) { + return customMappers(obj.id).toJson(obj) + }else Json.parse(mapper.writeValueAsString(obj)) + Json.obj( + "id" -> ("request-" + java.util.UUID.randomUUID().toString), + "event" -> obj.id, + "data" -> data + ) } } diff --git a/knockoutwhistweb/app/util/mapper/ReceivedHandEventMapper.scala b/knockoutwhistweb/app/util/mapper/ReceivedHandEventMapper.scala new file mode 100644 index 0000000..13827c0 --- /dev/null +++ b/knockoutwhistweb/app/util/mapper/ReceivedHandEventMapper.scala @@ -0,0 +1,16 @@ +package util.mapper + +import de.knockoutwhist.events.player.ReceivedHandEvent +import play.api.libs.json.{JsArray, JsObject, Json} +import util.WebUIUtils + +object ReceivedHandEventMapper extends SimpleEventMapper[ReceivedHandEvent] { + + override def id: String = "ReceivedHandEvent" + override def toJson(event: ReceivedHandEvent): JsObject = { + Json.obj( + "dog" -> event.player.isInDogLife, + "hand" -> event.player.currentHand().map(hand => WebUIUtils.handToJson(hand)) + ) + } +} diff --git a/knockoutwhistweb/app/util/mapper/SimpleEventMapper.scala b/knockoutwhistweb/app/util/mapper/SimpleEventMapper.scala new file mode 100644 index 0000000..aa91a1a --- /dev/null +++ b/knockoutwhistweb/app/util/mapper/SimpleEventMapper.scala @@ -0,0 +1,11 @@ +package util.mapper + +import de.knockoutwhist.utils.events.SimpleEvent +import play.api.libs.json.JsObject + +trait SimpleEventMapper[T <: SimpleEvent] { + + def id: String + def toJson(event: T): JsObject + +} diff --git a/knockoutwhistweb/app/views/main.scala.html b/knockoutwhistweb/app/views/main.scala.html index 72e8799..aea7e72 100644 --- a/knockoutwhistweb/app/views/main.scala.html +++ b/knockoutwhistweb/app/views/main.scala.html @@ -26,6 +26,8 @@ + + diff --git a/knockoutwhistweb/public/javascripts/events.js b/knockoutwhistweb/public/javascripts/events.js new file mode 100644 index 0000000..24d7860 --- /dev/null +++ b/knockoutwhistweb/public/javascripts/events.js @@ -0,0 +1,42 @@ +function receiveHandEvent(eventData) { + //Data + const dog = eventData.dog; + const hand = eventData.hand; + + const handElement = $('#card-slide'); + handElement.addClass('ingame-cards-slide') + + let newHtml = ''; + + //Build Hand Container + hand.forEach((card) => { + //Data + const idx = card.idx + const cardS = card.card; + + const cardHtml = ` +
+
+ + ${cardS} +
+
+ `; + newHtml += cardHtml; + }); + + //Build dog if needed + if (dog) { + newHtml += ` +
+ +
+ `; + } + handElement.html(newHtml); +} + +onEvent("ReceivedHandEvent", receiveHandEvent) \ No newline at end of file diff --git a/knockoutwhistweb/public/javascripts/interact.js b/knockoutwhistweb/public/javascripts/interact.js new file mode 100644 index 0000000..ad34feb --- /dev/null +++ b/knockoutwhistweb/public/javascripts/interact.js @@ -0,0 +1,7 @@ +function handlePlayCard(card, dog) { + // TODO needs implementation +} + +function handleSkipDogLife(button) { + // TODO needs implementation +} \ No newline at end of file diff --git a/knockoutwhistweb/public/javascripts/websocket.js b/knockoutwhistweb/public/javascripts/websocket.js index 744c37a..cdacc58 100644 --- a/knockoutwhistweb/public/javascripts/websocket.js +++ b/knockoutwhistweb/public/javascripts/websocket.js @@ -1,7 +1,9 @@ +type EventHandler = (data: any) => any | Promise; + // javascript let ws = null; // will be created by connectWebSocket() -const pending = new Map(); // id -> { resolve, reject, timer } -const handlers = new Map(); // eventType -> handler(data) -> (value|Promise) +const pending: Map = new Map(); // id -> { resolve, reject, timer } +const handlers: Map = new Map(); // eventType -> handler(data) -> (value|Promise) let timer = null; @@ -180,7 +182,7 @@ function sendEventAndWait(eventType, eventData, timeoutMs = 10000) { return p; } -function onEvent(eventType, handler) { +function onEvent(eventType: string, handler: EventHandler) { handlers.set(eventType, handler); }