feat(ui): Websocket

Started implementing functionality to the Websocket
This commit is contained in:
LQ63
2025-11-26 18:56:26 +01:00
committed by Janis
parent 6402df43b1
commit 6e76223c4a
12 changed files with 102 additions and 54 deletions

View File

@@ -28,7 +28,7 @@ class UserSession(val user: User, val host: Boolean, val gameLobby: GameLobby) e
else canInteract = Some(InteractionType.Card) else canInteract = Some(InteractionType.Card)
case _ => case _ =>
} }
websocketActor.foreach(_.transmitEventToClient(event, gameLobby)) websocketActor.foreach(_.transmitEventToClient(event, gameLobby, user))
} }
override def id: UUID = user.id override def id: UUID = user.id
@@ -41,7 +41,7 @@ class UserSession(val user: User, val host: Boolean, val gameLobby: GameLobby) e
def handleWebResponse(eventType: String, data: JsObject, user: User, gameLobby: GameLobby): Unit = { def handleWebResponse(eventType: String, data: JsObject, user: User, gameLobby: GameLobby): Unit = {
lock.lock() lock.lock()
Try { val result = Try {
eventType match { eventType match {
case "Ping" => case "Ping" =>
// No action needed for Ping // No action needed for Ping
@@ -49,9 +49,32 @@ class UserSession(val user: User, val host: Boolean, val gameLobby: GameLobby) e
case "Start Game" => case "Start Game" =>
println("INSIDE HANDLE WEB RESPONSE" + data) println("INSIDE HANDLE WEB RESPONSE" + data)
gameLobby.startGame(user) gameLobby.startGame(user)
case "play Card" =>
println("PLAYING CARD..." + data)
val maybeCardIndex: Option[Int] = (data \ "cardindex").asOpt[Int]
maybeCardIndex match {
case Some(index) =>
val session = gameLobby.getUserSession(user.id)
gameLobby.playCard(session, index)
case None =>
println("Card Index not found or is not a number.")
}
case "Picked Trumpsuit" =>
val maybeSuitIndex: Option[Int] = (data \ "suitIndex").asOpt[Int]
maybeSuitIndex match {
case Some(index) =>
val session = gameLobby.getUserSession(user.id)
gameLobby.selectTrump(session, index)
case None =>
println("Card Index not found or is not a number.")
}
} }
} }
lock.unlock() lock.unlock()
if (result.isFailure) {
val throwable = result.failed.get
throw throwable
}
} }
} }

View File

@@ -1,6 +1,9 @@
package model.sessions package model.sessions
import de.knockoutwhist.utils.events.SimpleEvent import de.knockoutwhist.utils.events.SimpleEvent
import logic.PodManager
import logic.game.GameLobby
import model.users.User
import org.apache.pekko.actor.{Actor, ActorRef} import org.apache.pekko.actor.{Actor, ActorRef}
import play.api.libs.json.{JsObject, JsValue, Json} import play.api.libs.json.{JsObject, JsValue, Json}
import util.WebsocketEventMapper import util.WebsocketEventMapper
@@ -96,7 +99,8 @@ class UserWebsocketActor(
transmitTextToClient(jsonObj.toString()) transmitTextToClient(jsonObj.toString())
} }
def transmitEventToClient(event: SimpleEvent): Unit = { def transmitEventToClient(event: SimpleEvent, gameLobby: GameLobby, user: User): Unit = {
val session = gameLobby.getUserSession(user.id)
transmitJsonToClient(WebsocketEventMapper.toJson(event, session)) transmitJsonToClient(WebsocketEventMapper.toJson(event, session))
} }

View File

@@ -7,6 +7,7 @@ import play.api.libs.json.{JsValue, Json}
import tools.jackson.databind.json.JsonMapper import tools.jackson.databind.json.JsonMapper
import tools.jackson.module.scala.ScalaModule import tools.jackson.module.scala.ScalaModule
import util.mapper.{CardPlayedEventMapper, GameStateEventMapper, KickEventMapper, LeftEventMapper, LobbyUpdateEventMapper, ReceivedHandEventMapper, SessionClosedMapper, SimpleEventMapper, TurnEventMapper} import util.mapper.{CardPlayedEventMapper, GameStateEventMapper, KickEventMapper, LeftEventMapper, LobbyUpdateEventMapper, ReceivedHandEventMapper, SessionClosedMapper, SimpleEventMapper, TurnEventMapper}
import util.mapper.{GameStateEventMapper, NewRoundEventMapper, NewTrickEventMapper, ReceivedHandEventMapper, RequestCardEventMapper, SimpleEventMapper, TrickEndEventMapper, CardPlayedEventMapper}
object WebsocketEventMapper { object WebsocketEventMapper {

View File

@@ -2,12 +2,14 @@ package util.mapper
import de.knockoutwhist.events.global.NewRoundEvent import de.knockoutwhist.events.global.NewRoundEvent
import logic.game.GameLobby import logic.game.GameLobby
import model.sessions.UserSession
import play.api.libs.json.{JsObject, Json} import play.api.libs.json.{JsObject, Json}
object NewRoundEventMapper extends SimpleEventMapper[NewRoundEvent]{ object NewRoundEventMapper extends SimpleEventMapper[NewRoundEvent]{
override def id: String = "NewRoundEvent" override def id: String = "NewRoundEvent"
override def toJson(event: NewRoundEvent, gameLobby: GameLobby): JsObject = { override def toJson(event: NewRoundEvent, session: UserSession): JsObject = {
val gameLobby = session.gameLobby
Json.obj( Json.obj(
"trumpsuit" -> gameLobby.getLogic.getCurrentRound.get.trumpSuit.toString, "trumpsuit" -> gameLobby.getLogic.getCurrentRound.get.trumpSuit.toString,
"players" -> gameLobby.getLogic.getCurrentMatch.get.playersIn.map(player => player.toString) "players" -> gameLobby.getLogic.getCurrentMatch.get.playersIn.map(player => player.toString)

View File

@@ -2,12 +2,13 @@ package util.mapper
import de.knockoutwhist.events.global.NewTrickEvent import de.knockoutwhist.events.global.NewTrickEvent
import logic.game.GameLobby import logic.game.GameLobby
import model.sessions.UserSession
import play.api.libs.json.{JsObject, Json} import play.api.libs.json.{JsObject, Json}
object NewTrickEventMapper extends SimpleEventMapper[NewTrickEvent]{ object NewTrickEventMapper extends SimpleEventMapper[NewTrickEvent]{
override def id: String = "NewTrickEvent" override def id: String = "NewTrickEvent"
override def toJson(event: NewTrickEvent, gameLobby: GameLobby): JsObject = { override def toJson(event: NewTrickEvent, session: UserSession): JsObject = {
Json.obj() Json.obj()
} }
} }

View File

@@ -2,12 +2,13 @@ package util.mapper
import de.knockoutwhist.events.player.RequestCardEvent import de.knockoutwhist.events.player.RequestCardEvent
import logic.game.GameLobby import logic.game.GameLobby
import model.sessions.UserSession
import play.api.libs.json.{JsObject, Json} import play.api.libs.json.{JsObject, Json}
object RequestCardEventMapper extends SimpleEventMapper[RequestCardEvent]{ object RequestCardEventMapper extends SimpleEventMapper[RequestCardEvent]{
override def id: String = "RequestCardEvent" override def id: String = "RequestCardEvent"
override def toJson(event: RequestCardEvent, gameLobby: GameLobby): JsObject = { override def toJson(event: RequestCardEvent, session: UserSession): JsObject = {
Json.obj( Json.obj(
"player" -> event.player.name "player" -> event.player.name
) )

View File

@@ -3,12 +3,14 @@ package util.mapper
import de.knockoutwhist.events.global.TrickEndEvent import de.knockoutwhist.events.global.TrickEndEvent
import de.knockoutwhist.rounds.Trick import de.knockoutwhist.rounds.Trick
import logic.game.GameLobby import logic.game.GameLobby
import model.sessions.UserSession
import play.api.libs.json.{JsObject, Json} import play.api.libs.json.{JsObject, Json}
object TrickEndEventMapper extends SimpleEventMapper[TrickEndEvent]{ object TrickEndEventMapper extends SimpleEventMapper[TrickEndEvent]{
override def id: String = "TrickEndEvent" override def id: String = "TrickEndEvent"
override def toJson(event: TrickEndEvent, gameLobby: GameLobby): JsObject = { override def toJson(event: TrickEndEvent, session: UserSession): JsObject = {
val gameLobby = session.gameLobby
Json.obj( Json.obj(
"playerwon" -> event.winner.name, "playerwon" -> event.winner.name,
"playersin" -> gameLobby.getLogic.getCurrentMatch.get.playersIn.map(player => player.name), "playersin" -> gameLobby.getLogic.getCurrentMatch.get.playersIn.map(player => player.name),

View File

@@ -107,7 +107,7 @@
} else { } else {
@for(i <- player.currentHand().get.cards.indices) { @for(i <- player.currentHand().get.cards.indices) {
<div class="col-auto handcard" style="border-radius: 6px"> <div class="col-auto handcard" style="border-radius: 6px">
<div class="btn btn-outline-light p-0 border-0 shadow-none" data-card-id="@i" style="border-radius: 6px" onclick="handlePlayCard(this, '@gamelobby.id', '@player.isInDogLife')"> <div class="btn btn-outline-light p-0 border-0 shadow-none" id="@i" data-card-id="@i" style="border-radius: 6px" onclick="handlePlayCard(@i, '@player.isInDogLife')">
@util.WebUIUtils.cardtoImage(player.currentHand().get.cards(i)) width="120px" style="border-radius: 6px"/> @util.WebUIUtils.cardtoImage(player.currentHand().get.cards(i)) width="120px" style="border-radius: 6px"/>
</div> </div>
@if(player.isInDogLife) { @if(player.isInDogLife) {

View File

@@ -18,25 +18,25 @@
<div class="row justify-content-center col-auto mb-5"> <div class="row justify-content-center col-auto mb-5">
<div class="col-auto handcard"> <div class="col-auto handcard">
<div class="btn btn-outline-light p-0 border-0 shadow-none" data-trump="0" style="border-radius: 6px" onclick="handleTrumpSelection(this, '@gamelobby.id')"> <div class="btn btn-outline-light p-0 border-0 shadow-none" data-trump="0" style="border-radius: 6px" onclick="handleTrumpSelection(this)">
@util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Spades)) @util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Spades))
width="120px" style="border-radius: 6px"/> width="120px" style="border-radius: 6px"/>
</div> </div>
</div> </div>
<div class="col-auto handcard"> <div class="col-auto handcard">
<div class="btn btn-outline-light p-0 border-0 shadow-none" data-trump="1" style="border-radius: 6px" onclick="handleTrumpSelection(this, '@gamelobby.id')"> <div class="btn btn-outline-light p-0 border-0 shadow-none" data-trump="1" style="border-radius: 6px" onclick="handleTrumpSelection(this)">
@util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Hearts)) @util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Hearts))
width="120px" style="border-radius: 6px"/> width="120px" style="border-radius: 6px"/>
</div> </div>
</div> </div>
<div class="col-auto handcard"> <div class="col-auto handcard">
<div class="btn btn-outline-light p-0 border-0 shadow-none" data-trump="2" style="border-radius: 6px" onclick="handleTrumpSelection(this, '@gamelobby.id')"> <div class="btn btn-outline-light p-0 border-0 shadow-none" data-trump="2" style="border-radius: 6px" onclick="handleTrumpSelection(this)">
@util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Diamonds)) @util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Diamonds))
width="120px" style="border-radius: 6px"/> width="120px" style="border-radius: 6px"/>
</div> </div>
</div> </div>
<div class="col-auto handcard"> <div class="col-auto handcard">
<div class="btn btn-outline-light p-0 border-0 shadow-none" data-trump="3" style="border-radius: 6px" onclick="handleTrumpSelection(this, '@gamelobby.id')"> <div class="btn btn-outline-light p-0 border-0 shadow-none" data-trump="3" style="border-radius: 6px" onclick="handleTrumpSelection(this)">
@util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Clubs)) @util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Clubs))
width="120px" style="border-radius: 6px"/> width="120px" style="border-radius: 6px"/>
</div> </div>

View File

@@ -133,9 +133,9 @@ function trickEndEvent(eventData) {
playerorder.forEach( player => { playerorder.forEach( player => {
newHtml += ` newHtml += `
<div class="d-flex justify-content-between score-row pt-1"> <div class="d-flex justify-content-between score-row pt-1">
<div style="width: 50%" class="text-truncate">'${player}'</div> <div style="width: 50%" class="text-truncate">${player}</div>
<div style="width: 50%"> <div style="width: 50%">
'${playercounts.get(player)}' ${playercounts.get(player)}
</div> </div>
</div> </div>
` `
@@ -143,14 +143,12 @@ function trickEndEvent(eventData) {
tricktable.html(newHtml); tricktable.html(newHtml);
} }
function newTrickEvent() { function newTrickEvent() {
const firstCardContainer = $('first-card-container'); const firstCardContainer = $('#first-card-container');
let newHtml = ''; let newHtml = '';
newHtml += ` newHtml += `
<img src="images/cards/1B.png" alt="Blank Card" width="80px"/> <img src="/assets/images/cards/1B.png" alt="Blank Card" width="80px" style="border-radius: 6px"/>
`; `;
firstCardContainer.html(newHtml); firstCardContainer.html(newHtml);
} }
function requestCardEvent(eventData) { function requestCardEvent(eventData) {
@@ -158,7 +156,6 @@ function requestCardEvent(eventData) {
const handElement = $('#card-slide') const handElement = $('#card-slide')
handElement.removeClass('inactive'); handElement.removeClass('inactive');
} }
//alertMessage("It worked!")
function receiveGameStateChange(eventData) { function receiveGameStateChange(eventData) {
@@ -207,37 +204,6 @@ function receiveCardPlayedEvent(eventData) {
`; `;
firstCardContainer.html(newFirstCardHTML); firstCardContainer.html(newFirstCardHTML);
} }
function receiveTurnEvent(eventData) {
const currentPlayer = eventData.currentPlayer;
const nextPlayers = eventData.nextPlayers;
const currentPlayerNameContainer = $('#current-player-name');
const nextPlayersContainer = $('#next-players-container');
const nextPlayerText = $('#next-players-section');
let currentPlayerName = currentPlayer.name;
if (currentPlayer.dog) {
currentPlayerName += " 🐶";
}
currentPlayerNameContainer.text(currentPlayerName);
if (nextPlayers.length === 0) {
nextPlayerText.hide();
nextPlayersContainer.html('');
} else {
nextPlayerText.show();
let nextPlayersHtml = '';
nextPlayers.forEach((player) => {
let playerName = player.name;
if (player.dog) {
playerName += " 🐶";
}
nextPlayersHtml += `<p className="fs-5 text-primary">${playerName}</p>`;
});
nextPlayersContainer.html(nextPlayersHtml);
}
}
function receiveLobbyUpdateEvent(eventData) { function receiveLobbyUpdateEvent(eventData) {
const host = eventData.host; const host = eventData.host;
const maxPlayers = eventData.maxPlayers; const maxPlayers = eventData.maxPlayers;
@@ -349,4 +315,6 @@ onEvent("LobbyUpdateEvent", receiveLobbyUpdateEvent)
onEvent("LeftEvent", receiveGameStateChange) onEvent("LeftEvent", receiveGameStateChange)
onEvent("KickEvent", receiveKickEvent) onEvent("KickEvent", receiveKickEvent)
onEvent("SessionClosed", receiveSessionClosedEvent) onEvent("SessionClosed", receiveSessionClosedEvent)
onEvent("TurnEvent", receiveTurnEvent) onEvent("TurnEvent", receiveTurnEvent)
globalThis.alertMessage = alertMessage

View File

@@ -1,5 +1,40 @@
function handlePlayCard(card, dog) { function handlePlayCard(cardIndex, dog) {
// TODO needs implementation const cardslide = $('#card-slide')
cardslide.addClass("inactive")
const payload = {
cardindex: cardIndex,
isDog: dog
}
sendEventAndWait("play Card", payload).then(
() => {
console.debug("play card successful")
const datacardid = $(`#${cardIndex}`)
datacardid.parent('.handcard').remove();
cardslide.find('.handcard').each(function(newIndex) {
const $innerButton = $(this).find('.btn');
$innerButton.attr('id', newIndex);
$innerButton.attr('data-card-id', newIndex);
const isInDogLife = $innerButton.attr('onclick').includes("'true'") ? 'true' : 'false';
$innerButton.attr('onclick', `handlePlayCard(${newIndex}, '${isInDogLife}')`);
console.debug(`Re-indexed card: Old index was ${$innerButton.attr('data-card-id')}, New index is ${newIndex}`);
});
}
).catch(
(err) => {
const cardslide = $('#card-slide')
console.log("EERROOOORRR PLAYING CARD" + (err.toString() === "You can't play this card!") + err.message)
console.warn("play card was not successful")
if (err.message === "You can't play this card!") {
cardslide.removeClass("inactive")
}
alertMessage("You aren't allowed to play this card")
}
)
} }
function handleSkipDogLife(button) { function handleSkipDogLife(button) {
@@ -8,6 +43,17 @@ function handleSkipDogLife(button) {
function startGame() { function startGame() {
sendEvent("Start Game") sendEvent("Start Game")
} }
function handleTrumpSelection(object) {
const $button = $(object);
const trumpIndex = parseInt($button.data('trump'));
const payload = {
suitIndex: trumpIndex
}
console.log("SENDING TRUMP SUIT SELECTION:", payload);
sendEvent("Picked Trumpsuit", payload)
}
function handleKickPlayer(playerId) { function handleKickPlayer(playerId) {
// TODO needs implementation // TODO needs implementation
} }

View File

@@ -31,7 +31,7 @@ function setupSocketHandlers(socket) {
if (status === "success") { if (status === "success") {
entry.resolve(data === undefined ? {} : data); entry.resolve(data === undefined ? {} : data);
} else { } else {
entry.reject(new Error(msg.error || "Server returned error")); entry.reject(new Error(msg.error || "Server returned error"));
} }
return; return;
} }