Compare commits

..

2 Commits

Author SHA1 Message Date
TeamCity
14b4473f72 ci: bump version to v4.3.0 2025-11-26 17:44:23 +00:00
1ef5e8a72f feat(api): Implemented session closed and kick event via websocket (#87)
Reviewed-on: #87
Reviewed-by: lq64 <lq@blackhole.local>
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-11-26 18:41:25 +01:00
26 changed files with 269 additions and 256 deletions

View File

@@ -154,3 +154,8 @@
### Features ### Features
* **api:** Implemented card played event via websocket ([#85](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/85)) ([3c0828f](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/3c0828fdbeb507706b86f1662476c46e760533e4)) * **api:** Implemented card played event via websocket ([#85](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/85)) ([3c0828f](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/3c0828fdbeb507706b86f1662476c46e760533e4))
## (2025-11-26)
### Features
* **api:** Implemented session closed and kick event via websocket ([#87](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/87)) ([1ef5e8a](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/1ef5e8a72fdf8a3d1ae624c8c3d7c6595017bc6f))

View File

@@ -40,7 +40,7 @@ lazy val knockoutwhistweb = project.in(file("knockoutwhistweb"))
libraryDependencies += "de.mkammerer" % "argon2-jvm" % "2.12", libraryDependencies += "de.mkammerer" % "argon2-jvm" % "2.12",
libraryDependencies += "com.auth0" % "java-jwt" % "4.5.0", libraryDependencies += "com.auth0" % "java-jwt" % "4.5.0",
libraryDependencies += "com.github.ben-manes.caffeine" % "caffeine" % "3.2.3", libraryDependencies += "com.github.ben-manes.caffeine" % "caffeine" % "3.2.3",
libraryDependencies += "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.16.1", libraryDependencies += "tools.jackson.module" %% "jackson-module-scala" % "3.0.2",
JsEngineKeys.engineType := JsEngineKeys.EngineType.Node JsEngineKeys.engineType := JsEngineKeys.EngineType.Node
) )

View File

@@ -89,7 +89,7 @@ class IngameController @Inject()(
val game = PodManager.getGame(gameId) val game = PodManager.getGame(gameId)
val playerToKickUUID = UUID.fromString(playerToKick) val playerToKickUUID = UUID.fromString(playerToKick)
val result = Try { val result = Try {
game.get.leaveGame(playerToKickUUID) game.get.leaveGame(playerToKickUUID, true)
} }
if (result.isSuccess) { if (result.isSuccess) {
Ok(Json.obj( Ok(Json.obj(
@@ -107,7 +107,7 @@ class IngameController @Inject()(
def leaveGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => def leaveGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
val game = PodManager.getGame(gameId) val game = PodManager.getGame(gameId)
val result = Try { val result = Try {
game.get.leaveGame(request.user.id) game.get.leaveGame(request.user.id, false)
} }
if (result.isSuccess) { if (result.isSuccess) {
Ok(Json.obj( Ok(Json.obj(

View File

@@ -0,0 +1,9 @@
package events
import model.users.User
case class KickEvent(user: User) extends UserEvent(user) {
override def id: String = "KickEvent"
}

View File

@@ -0,0 +1,9 @@
package events
import model.users.User
case class LeftEvent(user: User) extends UserEvent(user) {
override def id: String = "LeftEvent"
}

View File

@@ -0,0 +1,9 @@
package events
import de.knockoutwhist.utils.events.SimpleEvent
case class LobbyUpdateEvent() extends SimpleEvent {
override def id: String = "LobbyUpdateEvent"
}

View File

@@ -0,0 +1,12 @@
package events
import de.knockoutwhist.utils.events.SimpleEvent
import model.users.User
import java.util.UUID
abstract class UserEvent(user: User) extends SimpleEvent {
def userId: UUID = user.id
}

View File

@@ -2,7 +2,7 @@ package logic.game
import de.knockoutwhist.cards.{Hand, Suit} import de.knockoutwhist.cards.{Hand, Suit}
import de.knockoutwhist.control.GameLogic import de.knockoutwhist.control.GameLogic
import de.knockoutwhist.control.GameState.{InGame, Lobby, MainMenu} import de.knockoutwhist.control.GameState.{Lobby, MainMenu}
import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.{MatchUtil, PlayerUtil} import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.{MatchUtil, PlayerUtil}
import de.knockoutwhist.events.global.{GameStateChangeEvent, SessionClosed} import de.knockoutwhist.events.global.{GameStateChangeEvent, SessionClosed}
import de.knockoutwhist.events.player.PlayerEvent import de.knockoutwhist.events.player.PlayerEvent
@@ -10,13 +10,14 @@ import de.knockoutwhist.player.Playertype.HUMAN
import de.knockoutwhist.player.{AbstractPlayer, PlayerFactory} import de.knockoutwhist.player.{AbstractPlayer, PlayerFactory}
import de.knockoutwhist.rounds.{Match, Round, Trick} import de.knockoutwhist.rounds.{Match, Round, Trick}
import de.knockoutwhist.utils.events.{EventListener, SimpleEvent} import de.knockoutwhist.utils.events.{EventListener, SimpleEvent}
import events.{KickEvent, LeftEvent, LobbyUpdateEvent, UserEvent}
import exceptions.* import exceptions.*
import logic.PodManager import logic.PodManager
import model.sessions.{InteractionType, UserSession} import model.sessions.{InteractionType, UserSession}
import model.users.User import model.users.User
import play.api.libs.json.{JsObject, Json} import play.api.libs.json.{JsObject, Json}
import java.util.UUID import java.util.{Timer, TimerTask, UUID}
import scala.collection.mutable import scala.collection.mutable
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
@@ -44,7 +45,7 @@ class GameLobby private(
) )
users += (user.id -> userSession) users += (user.id -> userSession)
PodManager.registerUserToGame(user, id) PodManager.registerUserToGame(user, id)
//TODO : transmit Lobby Update transmitToAll() logic.invoke(LobbyUpdateEvent())
userSession userSession
} }
@@ -52,13 +53,12 @@ class GameLobby private(
event match { event match {
case event: PlayerEvent => case event: PlayerEvent =>
users.get(event.playerId).foreach(session => session.updatePlayer(event)) users.get(event.playerId).foreach(session => session.updatePlayer(event))
case event: UserEvent =>
users.get(event.userId).foreach(session => session.updatePlayer(event))
case event: GameStateChangeEvent => case event: GameStateChangeEvent =>
if (event.oldState == MainMenu && event.newState == Lobby) { if (event.oldState == MainMenu && event.newState == Lobby) {
return return
} }
if (event.oldState == Lobby && event.newState == InGame) {
println("RECEIVED GAMESTATEEVENT")
}
users.values.foreach(session => session.updatePlayer(event)) users.values.foreach(session => session.updatePlayer(event))
case event: SimpleEvent => case event: SimpleEvent =>
users.values.foreach(session => session.updatePlayer(event)) users.values.foreach(session => session.updatePlayer(event))
@@ -71,7 +71,6 @@ class GameLobby private(
* @param user the user who wants to start the game. * @param user the user who wants to start the game.
*/ */
def startGame(user: User): Unit = { def startGame(user: User): Unit = {
println("STARTED GAME IN LOGIC")
val sessionOpt = users.get(user.id) val sessionOpt = users.get(user.id)
if (sessionOpt.isEmpty) { if (sessionOpt.isEmpty) {
throw new NotInThisGameException("You are not in this game!") throw new NotInThisGameException("You are not in this game!")
@@ -97,8 +96,9 @@ class GameLobby private(
* Remove the user from the game lobby. * Remove the user from the game lobby.
* *
* @param user the user who wants to leave the game. * @param user the user who wants to leave the game.
* @param kicked whether the user was kicked or left voluntarily.
*/ */
def leaveGame(userId: UUID): Unit = { def leaveGame(userId: UUID, kicked: Boolean): Unit = {
val sessionOpt = users.get(userId) val sessionOpt = users.get(userId)
if (sessionOpt.isEmpty) { if (sessionOpt.isEmpty) {
throw new NotInThisGameException("You are not in this game!") throw new NotInThisGameException("You are not in this game!")
@@ -109,16 +109,14 @@ class GameLobby private(
PodManager.removeGame(id) PodManager.removeGame(id)
return return
} }
sessionOpt.get.websocketActor.foreach(act => act.transmitJsonToClient(Json.obj( if (kicked) {
"id" -> "-1", logic.invoke(KickEvent(sessionOpt.get.user))
"event" -> "SessionClosed", } else {
"data" -> Json.obj( logic.invoke(LeftEvent(sessionOpt.get.user))
"reason" -> "You left the game (or got kicked)." }
)
)))
users.remove(userId) users.remove(userId)
PodManager.unregisterUserFromGame(sessionOpt.get.user) PodManager.unregisterUserFromGame(sessionOpt.get.user)
//TODO: transmit Lobby Update transmitToAll() logic.invoke(LobbyUpdateEvent())
} }
/** /**

View File

@@ -2,11 +2,9 @@ package model.sessions
import de.knockoutwhist.events.player.{RequestCardEvent, RequestTieChoiceEvent, RequestTrumpSuitEvent} import de.knockoutwhist.events.player.{RequestCardEvent, RequestTieChoiceEvent, RequestTrumpSuitEvent}
import de.knockoutwhist.utils.events.SimpleEvent import de.knockoutwhist.utils.events.SimpleEvent
import logic.PodManager
import logic.game.GameLobby import logic.game.GameLobby
import model.users.User import model.users.User
import play.api.libs.json.Format.GenericFormat import play.api.libs.json.JsObject
import play.api.libs.json.{JsError, JsObject, JsResult, JsSuccess, JsValue}
import java.util.UUID import java.util.UUID
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
@@ -28,7 +26,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))
} }
override def id: UUID = user.id override def id: UUID = user.id
@@ -39,16 +37,13 @@ class UserSession(val user: User, val host: Boolean, val gameLobby: GameLobby) e
canInteract = None canInteract = None
} }
def handleWebResponse(eventType: String, data: JsObject, user: User, gameLobby: GameLobby): Unit = { def handleWebResponse(eventType: String, data: JsObject): Unit = {
lock.lock() lock.lock()
Try { Try {
eventType match { eventType match {
case "Ping" => case "Ping" =>
// No action needed for Ping // No action needed for Ping
() ()
case "Start Game" =>
println("INSIDE HANDLE WEB RESPONSE" + data)
gameLobby.startGame(user)
} }
} }
lock.unlock() lock.unlock()

View File

@@ -74,7 +74,7 @@ class UserWebsocketActor(
val event = eventOpt.get val event = eventOpt.get
val data = (json \ "data").asOpt[JsObject].getOrElse(Json.obj()) val data = (json \ "data").asOpt[JsObject].getOrElse(Json.obj())
val result = Try { val result = Try {
session.handleWebResponse(event, data, session.user, session.gameLobby) session.handleWebResponse(event, data)
} }
if (result.isSuccess) { if (result.isSuccess) {
transmitJsonToClient(Json.obj( transmitJsonToClient(Json.obj(

View File

@@ -1,7 +1,5 @@
package model.users package model.users
import play.api.libs.json.{Format, Json}
import java.util.UUID import java.util.UUID
case class User( case class User(
@@ -18,4 +16,5 @@ case class User(
private def withPasswordHash(newPasswordHash: String): User = { private def withPasswordHash(newPasswordHash: String): User = {
this.copy(passwordHash = newPasswordHash) this.copy(passwordHash = newPasswordHash)
} }
} }

View File

@@ -6,7 +6,7 @@ import model.sessions.UserSession
import play.api.libs.json.{JsValue, Json} 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, ReceivedHandEventMapper, SimpleEventMapper} import util.mapper.{CardPlayedEventMapper, GameStateEventMapper, KickEventMapper, LeftEventMapper, LobbyUpdateEventMapper, ReceivedHandEventMapper, SessionClosedMapper, SimpleEventMapper}
object WebsocketEventMapper { object WebsocketEventMapper {
@@ -27,10 +27,11 @@ object WebsocketEventMapper {
registerCustomMapper(ReceivedHandEventMapper) registerCustomMapper(ReceivedHandEventMapper)
registerCustomMapper(GameStateEventMapper) registerCustomMapper(GameStateEventMapper)
registerCustomMapper(CardPlayedEventMapper) registerCustomMapper(CardPlayedEventMapper)
registerCustomMapper(NewRoundEventMapper) registerCustomMapper(LobbyUpdateEventMapper)
registerCustomMapper(NewTrickEventMapper) registerCustomMapper(LeftEventMapper)
registerCustomMapper(TrickEndEventMapper) registerCustomMapper(KickEventMapper)
registerCustomMapper(RequestCardEventMapper) registerCustomMapper(SessionClosedMapper)
def toJson(obj: SimpleEvent, session: UserSession): JsValue = { def toJson(obj: SimpleEvent, session: UserSession): JsValue = {
val data: Option[JsValue] = if (customMappers.contains(obj.id)) { val data: Option[JsValue] = if (customMappers.contains(obj.id)) {
Some(customMappers(obj.id).toJson(obj, session)) Some(customMappers(obj.id).toJson(obj, session))

View File

@@ -1,18 +0,0 @@
package util.mapper
import de.knockoutwhist.events.global.GameStateChangeEvent
import logic.game.GameLobby
import play.api.libs.json.{JsObject, Json}
object GameStateChangeEventMapper extends SimpleEventMapper[GameStateChangeEvent]{
override def id: String = "GameStateChangeEvent"
override def toJson(event: GameStateChangeEvent, gameLobby: GameLobby): JsObject = {
println("CALLED toJSON FOR GAMESTATECHANGE")
Json.obj(
"oldState" -> event.oldState.toString,
"newState" -> event.newState.toString,
"gameLobby" -> gameLobby.id
)
}
}

View File

@@ -0,0 +1,19 @@
package util.mapper
import controllers.routes
import events.KickEvent
import model.sessions.UserSession
import play.api.libs.json.{JsObject, Json}
object KickEventMapper extends SimpleEventMapper[KickEvent] {
override def id: String = "KickEvent"
override def toJson(event: KickEvent, session: UserSession): JsObject = {
Json.obj(
"url" -> routes.MainMenuController.mainMenu().url,
"content" -> views.html.mainmenu.creategame(Some(session.user)).toString,
)
}
}

View File

@@ -0,0 +1,19 @@
package util.mapper
import controllers.routes
import events.{KickEvent, LeftEvent}
import model.sessions.UserSession
import play.api.libs.json.{JsObject, Json}
object LeftEventMapper extends SimpleEventMapper[LeftEvent] {
override def id: String = "LeftEvent"
override def toJson(event: LeftEvent, session: UserSession): JsObject = {
Json.obj(
"url" -> routes.MainMenuController.mainMenu().url,
"content" -> views.html.mainmenu.creategame(Some(session.user)).toString
)
}
}

View File

@@ -0,0 +1,25 @@
package util.mapper
import events.LobbyUpdateEvent
import model.sessions.UserSession
import play.api.libs.json.{JsArray, JsObject, Json}
object LobbyUpdateEventMapper extends SimpleEventMapper[LobbyUpdateEvent] {
override def id: String = "LobbyUpdateEvent"
override def toJson(event: LobbyUpdateEvent, session: UserSession): JsObject = {
Json.obj(
"host" -> session.host,
"maxPlayers" -> session.gameLobby.maxPlayers,
"players" -> JsArray(session.gameLobby.getPlayers.values.map(player => {
Json.obj(
"id" -> player.id,
"name" -> player.name,
"self" -> (player.id == session.user.id)
)
}).toList)
)
}
}

View File

@@ -1,16 +0,0 @@
package util.mapper
import de.knockoutwhist.events.global.NewRoundEvent
import logic.game.GameLobby
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 = {
Json.obj(
"trumpsuit" -> gameLobby.getLogic.getCurrentRound.get.trumpSuit.toString,
"players" -> gameLobby.getLogic.getCurrentMatch.get.playersIn.map(player => player.toString)
)
}
}

View File

@@ -1,13 +0,0 @@
package util.mapper
import de.knockoutwhist.events.global.NewTrickEvent
import logic.game.GameLobby
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 = {
Json.obj()
}
}

View File

@@ -1,15 +0,0 @@
package util.mapper
import de.knockoutwhist.events.player.RequestCardEvent
import logic.game.GameLobby
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 = {
Json.obj(
"player" -> event.player.name
)
}
}

View File

@@ -0,0 +1,19 @@
package util.mapper
import controllers.routes
import de.knockoutwhist.events.global.SessionClosed
import model.sessions.UserSession
import play.api.libs.json.{JsObject, Json}
object SessionClosedMapper extends SimpleEventMapper[SessionClosed] {
override def id: String = "SessionClosed"
override def toJson(event: SessionClosed, session: UserSession): JsObject = {
Json.obj(
"url" -> routes.MainMenuController.mainMenu().url,
"content" -> views.html.mainmenu.creategame(Some(session.user)).toString,
)
}
}

View File

@@ -1,18 +0,0 @@
package util.mapper
import de.knockoutwhist.events.global.TrickEndEvent
import de.knockoutwhist.rounds.Trick
import logic.game.GameLobby
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 = {
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

@@ -1,6 +1,35 @@
@(user: Option[model.users.User], gamelobby: logic.game.GameLobby) @(user: Option[model.users.User], gamelobby: logic.game.GameLobby)
<main class="lobby-background vh-100" id="lobbybackground"> <main class="lobby-background vh-100" id="lobbybackground">
<!-- Kick Modal -->
<div class="modal fade" data-backdrop="static" data-keyboard="false" data-focus="true" id="kickedModal" tabindex="-1" role="dialog" aria-labelledby="kickedModalTitle">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="kickedModalTitle">Kicked</h5>
</div>
<div class="modal-body">
<p>You've been kicked from the lobby.</p>
</div>
</div>
</div>
</div>
<!-- Session Closed Modal -->
<div class="modal fade" data-backdrop="static" data-keyboard="false" data-focus="true" id="sessionClosed" tabindex="-1" role="dialog" aria-labelledby="sessionClosedModalTitle">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="sessionClosedModalTitle">Session Closed</h5>
</div>
<div class="modal-body">
<p>The session was closed.</p>
</div>
</div>
</div>
</div>
<!-- Lobby -->
<div class="container d-flex flex-column" style="height: calc(100vh - 1rem);"> <div class="container d-flex flex-column" style="height: calc(100vh - 1rem);">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
@@ -15,7 +44,7 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<div class="p-3 text-center fs-4" id="playerAmount"> <div class="p-3 text-center fs-4" id="playerAmount">
Playeramount: @gamelobby.getPlayers.size / @gamelobby.maxPlayers</div> Players: @gamelobby.getPlayers.size / @gamelobby.maxPlayers</div>
</div> </div>
</div> </div>
<div class="row justify-content-center align-items-center flex-grow-1"> <div class="row justify-content-center align-items-center flex-grow-1">
@@ -40,7 +69,7 @@
} }
</div> </div>
<div class="col-12 text-center mb-5"> <div class="col-12 text-center mb-5">
<div class="btn btn-success" onclick="startGame('@gamelobby.id', '@user.get.id', '@user.get.name','@user.get.passwordHash', @user.get.internalId)">Start Game</div> <div class="btn btn-success" onclick="startGame('@gamelobby.id')">Start Game</div>
</div> </div>
} else { } else {
<div id="players" class="justify-content-center align-items-center d-flex"> <div id="players" class="justify-content-center align-items-center d-flex">
@@ -69,7 +98,6 @@
</div> </div>
</main> </main>
<script> <script>
/*
function waitForFunction(name, checkInterval = 100) { function waitForFunction(name, checkInterval = 100) {
return new Promise(resolve => { return new Promise(resolve => {
const timer = setInterval(() => { const timer = setInterval(() => {
@@ -81,6 +109,5 @@
}); });
} }
waitForFunction("pollForUpdates").then(fn => fn('@gamelobby.id')); waitForFunction("pollForUpdates").then(fn => fn('@gamelobby.id'));
*/
connectWebSocket() connectWebSocket()
</script> </script>

View File

@@ -1,30 +1,3 @@
function alertMessage(message) {
let newHtml = '';
const alertId = `alert-${Date.now()}`;
const fadeTime = 500;
const duration = 5000;
newHtml += `
<div class="fixed-top d-flex justify-content-center mt-3" style="z-index: 1050;">
<div
id="${alertId}" class="alert alert-primary d-flex align-items-center p-2 mb-0 w-auto" role="alert" style="display: none;">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="bi flex-shrink-0 me-2" viewBox="0 0 16 16" role="img" aria-label="Warning:">
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
</svg>
<div class="small">
<small>${message}</small>
</div>
</div>
</div>
`;
$('#main-body').prepend(newHtml);
const $notice = $(`#${alertId}`);
$notice.fadeIn(fadeTime);
setTimeout(function() {
$notice.fadeOut(fadeTime, function() {
$(this).parent().remove();
});
}, duration);
}
function receiveHandEvent(eventData) { function receiveHandEvent(eventData) {
//Data //Data
const dog = eventData.dog; const dog = eventData.dog;
@@ -65,107 +38,13 @@ function receiveHandEvent(eventData) {
} }
handElement.html(newHtml); handElement.html(newHtml);
} }
function newRoundEvent(eventData) {
const trumpsuit = eventData.trumpsuit;
const players = eventData.players;
const tableElement = $('#score-table-body');
let tablehtml = `
<h4 class="fw-bold mb-3 text-black">Tricks Won</h4>
<div class="d-flex justify-content-between score-header pb-1">
<div style="width: 50%">PLAYER</div>
<div style="width: 50%">TRICKS</div>
</div>
`;
players.forEach(
tablehtml += `
<div class="d-flex justify-content-between score-row pt-1">
<div style="width: 50%" class="text-truncate">'${players}'</div>
<div style="width: 50%">
0
</div>
</div>
`
);
tableElement.html(tablehtml);
const trumpsuitClass = $('#trumpsuit');
trumpsuitClass.html(trumpsuit);
}
function trickEndEvent(eventData) {
const winner = eventData.playerwon;
const players = eventData.playersin;
const tricklist = eventData.tricklist;
let newHtml = '';
let tricktable = $('#score-table-body');
newHtml += `
<h4 class="fw-bold mb-3 text-black">Tricks Won</h4>
<div class="d-flex justify-content-between score-header pb-1">
<div style="width: 50%">PLAYER</div>
<div style="width: 50%">TRICKS</div>
</div>
`;
let playercounts = new Map();
players.forEach( player => {
playercounts.set(player, 0)
});
tricklist.forEach( player => {
if ( player !== "Trick in Progress" && playercounts.has(player)) {
playercounts.set(player, playercounts.get(player) + 1)
}
}
)
const playerorder = players.sort((playerA, playerB) => {
const countA = playercounts.get(playerA.name) || 0;
const countB = playercounts.get(playerB.name) || 0;
return countB - countA;
});
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%">
'${playercounts.get(player)}'
</div>
</div>
`
});
tricktable.html(newHtml);
}
function newTrickEvent() {
const firstCardContainer = $('first-card-container');
let newHtml = '';
newHtml += `
<img src="images/cards/1B.png" alt="Blank Card" width="80px"/>
`;
firstCardContainer.html(newHtml);
}
function requestCardEvent(eventData) {
const player = eventData.player;
const handElement = $('#card-slide')
handElement.removeClass('inactive');
}
//alertMessage("It worked!")
function receiveGameStateChange(eventData) { function receiveGameStateChange(eventData) {
const content = eventData.content; const content = eventData.content;
const title = eventData.title || 'Knockout Whist'; const title = eventData.title || 'Knockout Whist';
const url = eventData.url || null;
exchangeBody(content, title); exchangeBody(content, title, url);
} }
function receiveCardPlayedEvent(eventData) { function receiveCardPlayedEvent(eventData) {
const firstCard = eventData.firstCard; const firstCard = eventData.firstCard;
@@ -206,10 +85,79 @@ function receiveCardPlayedEvent(eventData) {
`; `;
firstCardContainer.html(newFirstCardHTML); firstCardContainer.html(newFirstCardHTML);
} }
function receiveLobbyUpdateEvent(eventData) {
const host = eventData.host;
const maxPlayers = eventData.maxPlayers;
const players = eventData.players;
const lobbyPlayersContainer = $('#players');
const playerAmountBox = $('#playerAmount');
let newHtml = ''
if (host) {
players.forEach(user => {
const inner = user.self ? `<h5 class="card-title">${user.name} (You)</h5>
<a href="#" class="btn btn-danger disabled" aria-disabled="true" tabindex="-1">Remove</a>`
: ` <h5 class="card-title">${user.name}</h5>
<div class="btn btn-danger" onclick="removePlayer('${user.id}')">Remove</div>`
newHtml += `<div class="col-auto my-auto m-3">
<div class="card" style="width: 18rem;">
<img src="/assets/images/profile.png" alt="Profile" class="card-img-top w-50 mx-auto mt-3" />
<div class="card-body">
${inner}
</div>
</div>
</div>`
})
} else {
players.forEach(user => {
const inner = user.self ? `<h5 class="card-title">${user.name} (You)</h5>` : ` <h5 class="card-title">${user.name}</h5>`
newHtml += `<div class="col-auto my-auto m-3">
<div class="card" style="width: 18rem;">
<img src="/assets/images/profile.png" alt="Profile" class="card-img-top w-50 mx-auto mt-3" />
<div class="card-body">
${inner}
</div>
</div>
</div>`
})
}
lobbyPlayersContainer.html(newHtml);
playerAmountBox.text(`Players: ${players.length} / ${maxPlayers}`);
}
function receiveKickEvent(eventData) {
$('#kickedModal').modal({
backdrop: 'static',
keyboard: false
}).modal('show');
setTimeout(() => {
receiveGameStateChange(eventData)
}, 5000);
}
function receiveSessionClosedEvent(eventData) {
$('#sessionClosed').modal({
backdrop: 'static',
keyboard: false
}).modal('show');
setTimeout(() => {
receiveGameStateChange(eventData)
}, 5000);
}
onEvent("ReceivedHandEvent", receiveHandEvent) onEvent("ReceivedHandEvent", receiveHandEvent)
onEvent("GameStateChangeEvent", receiveGameStateChange) onEvent("GameStateChangeEvent", receiveGameStateChange)
onEvent("NewRoundEvent", newRoundEvent)
onEvent("TrickEndEvent", trickEndEvent)
onEvent("NewTrickEvent", newTrickEvent)
onEvent("RequestCardEvent", requestCardEvent)
onEvent("CardPlayedEvent", receiveCardPlayedEvent) onEvent("CardPlayedEvent", receiveCardPlayedEvent)
onEvent("LobbyUpdateEvent", receiveLobbyUpdateEvent)
onEvent("LeftEvent", receiveGameStateChange)
onEvent("KickEvent", receiveKickEvent)
onEvent("SessionClosed", receiveSessionClosedEvent)

View File

@@ -5,7 +5,6 @@ function handlePlayCard(card, dog) {
function handleSkipDogLife(button) { function handleSkipDogLife(button) {
// TODO needs implementation // TODO needs implementation
} }
function startGame() { function handleKickPlayer(playerId) {
console.log("CLICKED START GAME, SENDING EVENT...") // TODO needs implementation
sendEvent("Start Game")
} }

View File

@@ -1,3 +1,3 @@
MAJOR=4 MAJOR=4
MINOR=2 MINOR=3
PATCH=0 PATCH=0