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)
case _ =>
}
websocketActor.foreach(_.transmitEventToClient(event, gameLobby))
websocketActor.foreach(_.transmitEventToClient(event, gameLobby, user))
}
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 = {
lock.lock()
Try {
val result = Try {
eventType match {
case "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" =>
println("INSIDE HANDLE WEB RESPONSE" + data)
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()
if (result.isFailure) {
val throwable = result.failed.get
throw throwable
}
}
}

View File

@@ -1,6 +1,9 @@
package model.sessions
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 play.api.libs.json.{JsObject, JsValue, Json}
import util.WebsocketEventMapper
@@ -96,7 +99,8 @@ class UserWebsocketActor(
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))
}

View File

@@ -7,6 +7,7 @@ import play.api.libs.json.{JsValue, Json}
import tools.jackson.databind.json.JsonMapper
import tools.jackson.module.scala.ScalaModule
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 {

View File

@@ -2,12 +2,14 @@ package util.mapper
import de.knockoutwhist.events.global.NewRoundEvent
import logic.game.GameLobby
import model.sessions.UserSession
import play.api.libs.json.{JsObject, Json}
object NewRoundEventMapper extends SimpleEventMapper[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(
"trumpsuit" -> gameLobby.getLogic.getCurrentRound.get.trumpSuit.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 logic.game.GameLobby
import model.sessions.UserSession
import play.api.libs.json.{JsObject, Json}
object NewTrickEventMapper extends SimpleEventMapper[NewTrickEvent]{
override def id: String = "NewTrickEvent"
override def toJson(event: NewTrickEvent, gameLobby: GameLobby): JsObject = {
override def toJson(event: NewTrickEvent, session: UserSession): JsObject = {
Json.obj()
}
}

View File

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

View File

@@ -3,12 +3,14 @@ package util.mapper
import de.knockoutwhist.events.global.TrickEndEvent
import de.knockoutwhist.rounds.Trick
import logic.game.GameLobby
import model.sessions.UserSession
import play.api.libs.json.{JsObject, Json}
object TrickEndEventMapper extends SimpleEventMapper[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(
"playerwon" -> event.winner.name,
"playersin" -> gameLobby.getLogic.getCurrentMatch.get.playersIn.map(player => player.name),

View File

@@ -107,7 +107,7 @@
} else {
@for(i <- player.currentHand().get.cards.indices) {
<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"/>
</div>
@if(player.isInDogLife) {

View File

@@ -18,25 +18,25 @@
<div class="row justify-content-center col-auto mb-5">
<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))
width="120px" style="border-radius: 6px"/>
</div>
</div>
<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))
width="120px" style="border-radius: 6px"/>
</div>
</div>
<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))
width="120px" style="border-radius: 6px"/>
</div>
</div>
<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))
width="120px" style="border-radius: 6px"/>
</div>

View File

@@ -133,9 +133,9 @@ function trickEndEvent(eventData) {
playerorder.forEach( player => {
newHtml += `
<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%">
'${playercounts.get(player)}'
${playercounts.get(player)}
</div>
</div>
`
@@ -143,14 +143,12 @@ function trickEndEvent(eventData) {
tricktable.html(newHtml);
}
function newTrickEvent() {
const firstCardContainer = $('first-card-container');
const firstCardContainer = $('#first-card-container');
let 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);
}
function requestCardEvent(eventData) {
@@ -158,7 +156,6 @@ function requestCardEvent(eventData) {
const handElement = $('#card-slide')
handElement.removeClass('inactive');
}
//alertMessage("It worked!")
function receiveGameStateChange(eventData) {
@@ -207,37 +204,6 @@ function receiveCardPlayedEvent(eventData) {
`;
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) {
const host = eventData.host;
const maxPlayers = eventData.maxPlayers;
@@ -349,4 +315,6 @@ onEvent("LobbyUpdateEvent", receiveLobbyUpdateEvent)
onEvent("LeftEvent", receiveGameStateChange)
onEvent("KickEvent", receiveKickEvent)
onEvent("SessionClosed", receiveSessionClosedEvent)
onEvent("TurnEvent", receiveTurnEvent)
onEvent("TurnEvent", receiveTurnEvent)
globalThis.alertMessage = alertMessage

View File

@@ -1,5 +1,40 @@
function handlePlayCard(card, dog) {
// TODO needs implementation
function handlePlayCard(cardIndex, dog) {
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) {
@@ -8,6 +43,17 @@ function handleSkipDogLife(button) {
function startGame() {
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) {
// TODO needs implementation
}

View File

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