feat(ui): Implement countless feature using the SJWP (#89)

Co-authored-by: LQ63 <lkhermann@web.de>
Reviewed-on: #89
This commit is contained in:
2025-11-27 08:53:37 +01:00
parent 2aee79bb68
commit 1f96290371
14 changed files with 289 additions and 48 deletions

View File

@@ -4,7 +4,7 @@ import de.knockoutwhist.events.player.{RequestCardEvent, RequestTieChoiceEvent,
import de.knockoutwhist.utils.events.SimpleEvent
import logic.game.GameLobby
import model.users.User
import play.api.libs.json.JsObject
import play.api.libs.json.{JsObject, JsValue}
import java.util.UUID
import java.util.concurrent.locks.ReentrantLock
@@ -39,14 +39,38 @@ class UserSession(val user: User, val host: Boolean, val gameLobby: GameLobby) e
def handleWebResponse(eventType: String, data: JsObject): Unit = {
lock.lock()
Try {
val result = Try {
eventType match {
case "Ping" =>
// No action needed for Ping
()
case "Start Game" =>
gameLobby.startGame(user)
case "play Card" =>
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,5 +1,6 @@
package model.users
import java.util.UUID
case class User(
@@ -16,5 +17,4 @@ case class User(
private def withPasswordHash(newPasswordHash: String): User = {
this.copy(passwordHash = newPasswordHash)
}
}
}

View File

@@ -1,8 +1,8 @@
package util
import de.knockoutwhist.cards.{Card, Hand}
import de.knockoutwhist.cards.CardValue.*
import de.knockoutwhist.cards.Suit.{Clubs, Diamonds, Hearts, Spades}
import de.knockoutwhist.cards.{Card, Hand}
import play.api.libs.json.{JsArray, Json}
import play.twirl.api.Html
import scalafx.scene.image.Image

View File

@@ -1,12 +1,11 @@
package util
import de.knockoutwhist.utils.events.SimpleEvent
import logic.game.GameLobby
import model.sessions.UserSession
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.*
object WebsocketEventMapper {
@@ -27,6 +26,10 @@ object WebsocketEventMapper {
registerCustomMapper(ReceivedHandEventMapper)
registerCustomMapper(GameStateEventMapper)
registerCustomMapper(CardPlayedEventMapper)
registerCustomMapper(NewRoundEventMapper)
registerCustomMapper(NewTrickEventMapper)
registerCustomMapper(TrickEndEventMapper)
registerCustomMapper(RequestCardEventMapper)
registerCustomMapper(LobbyUpdateEventMapper)
registerCustomMapper(LeftEventMapper)
registerCustomMapper(KickEventMapper)

View File

@@ -0,0 +1,18 @@
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, 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

@@ -0,0 +1,14 @@
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, session: UserSession): JsObject = {
Json.obj()
}
}

View File

@@ -0,0 +1,16 @@
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, session: UserSession): JsObject = {
Json.obj(
"player" -> event.player.name
)
}
}

View File

@@ -0,0 +1,20 @@
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, session: UserSession): JsObject = {
val gameLobby = session.gameLobby
Json.obj(
"playerwon" -> event.winner.name,
"playersin" -> gameLobby.getLogic.getCurrentMatch.get.playersIn.map(player => player.name),
"tricklist" -> gameLobby.getLogic.getCurrentRound.get.tricklist.map(trick => trick.winner.map(player => player.name).getOrElse("Trick in Progress"))
)
}
}

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

@@ -69,7 +69,7 @@
}
</div>
<div class="col-12 text-center mb-5">
<div class="btn btn-success" onclick="startGame('@gamelobby.id')">Start Game</div>
<div class="btn btn-success" onclick="startGame()">Start Game</div>
</div>
} else {
<div id="players" class="justify-content-center align-items-center d-flex">
@@ -98,6 +98,7 @@
</div>
</main>
<script>
/*
function waitForFunction(name, checkInterval = 100) {
return new Promise(resolve => {
const timer = setInterval(() => {
@@ -109,5 +110,6 @@
});
}
waitForFunction("pollForUpdates").then(fn => fn('@gamelobby.id'));
*/
connectWebSocket()
</script>

View File

@@ -30,4 +30,4 @@
</main>
<script>
disconnectWebSocket();
</script>
</script>