feat(game): Implement return to lobby functionality and enhance dog life handling
This commit is contained in:
@@ -227,7 +227,10 @@ class IngameController @Inject() (
|
|||||||
val game = podManager.getGame(gameId)
|
val game = podManager.getGame(gameId)
|
||||||
game match {
|
game match {
|
||||||
case Some(g) => {
|
case Some(g) => {
|
||||||
val cardIdOpt = request.body.asFormUrlEncoded.flatMap(_.get("cardId").flatMap(_.headOption))
|
val jsonBody = request.body.asJson
|
||||||
|
val cardIdOpt: Option[String] = jsonBody.flatMap { jsValue =>
|
||||||
|
(jsValue \ "cardID").asOpt[String]
|
||||||
|
}
|
||||||
var optSession: Option[UserSession] = None
|
var optSession: Option[UserSession] = None
|
||||||
val result = Try {
|
val result = Try {
|
||||||
cardIdOpt match {
|
cardIdOpt match {
|
||||||
@@ -344,4 +347,46 @@ class IngameController @Inject() (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def returnToLobby(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||||
|
val game = podManager.getGame(gameId)
|
||||||
|
game match {
|
||||||
|
case Some(g) =>
|
||||||
|
val result = Try {
|
||||||
|
val session = g.getUserSession(request.user.id)
|
||||||
|
g.returnToLobby(session)
|
||||||
|
}
|
||||||
|
if (result.isSuccess) {
|
||||||
|
Ok(Json.obj(
|
||||||
|
"status" -> "success",
|
||||||
|
"redirectUrl" -> routes.IngameController.game(gameId).url
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
val throwable = result.failed.get
|
||||||
|
throwable match {
|
||||||
|
case _: NotInThisGameException =>
|
||||||
|
BadRequest(Json.obj(
|
||||||
|
"status" -> "failure",
|
||||||
|
"errorMessage" -> throwable.getMessage
|
||||||
|
))
|
||||||
|
case _: IllegalStateException =>
|
||||||
|
BadRequest(Json.obj(
|
||||||
|
"status" -> "failure",
|
||||||
|
"errorMessage" -> throwable.getMessage
|
||||||
|
))
|
||||||
|
case _ =>
|
||||||
|
InternalServerError(Json.obj(
|
||||||
|
"status" -> "failure",
|
||||||
|
"errorMessage" -> throwable.getMessage
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case None =>
|
||||||
|
NotFound(Json.obj(
|
||||||
|
"status" -> "failure",
|
||||||
|
"errorMessage" -> "Game not found"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -20,6 +20,7 @@ class JavaScriptRoutingController @Inject()(
|
|||||||
routes.javascript.IngameController.kickPlayer,
|
routes.javascript.IngameController.kickPlayer,
|
||||||
routes.javascript.IngameController.leaveGame,
|
routes.javascript.IngameController.leaveGame,
|
||||||
routes.javascript.IngameController.playCard,
|
routes.javascript.IngameController.playCard,
|
||||||
|
routes.javascript.IngameController.playDogCard,
|
||||||
routes.javascript.PollingController.polling
|
routes.javascript.PollingController.polling
|
||||||
)
|
)
|
||||||
).as("text/javascript")
|
).as("text/javascript")
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package controllers
|
|||||||
import auth.{AuthAction, AuthenticatedRequest}
|
import auth.{AuthAction, AuthenticatedRequest}
|
||||||
import controllers.PollingController.{scheduler, timeoutDuration}
|
import controllers.PollingController.{scheduler, timeoutDuration}
|
||||||
import de.knockoutwhist.cards.Hand
|
import de.knockoutwhist.cards.Hand
|
||||||
|
import de.knockoutwhist.player.AbstractPlayer
|
||||||
import logic.PodManager
|
import logic.PodManager
|
||||||
import logic.game.{GameLobby, PollingEvents}
|
import logic.game.{GameLobby, PollingEvents}
|
||||||
import logic.game.PollingEvents.{CardPlayed, LobbyCreation, LobbyUpdate, NewRound, ReloadEvent}
|
import logic.game.PollingEvents.{CardPlayed, LobbyCreation, LobbyUpdate, NewRound, ReloadEvent}
|
||||||
@@ -28,7 +29,7 @@ class PollingController @Inject() (
|
|||||||
implicit val ec: ExecutionContext
|
implicit val ec: ExecutionContext
|
||||||
) extends AbstractController(cc) {
|
) extends AbstractController(cc) {
|
||||||
|
|
||||||
private def buildCardPlayResponse(game: GameLobby, hand: Option[Hand], newRound: Boolean): JsValue = {
|
private def buildCardPlayResponse(game: GameLobby, hand: Option[Hand], player: AbstractPlayer, newRound: Boolean): JsValue = {
|
||||||
val currentRound = game.logic.getCurrentRound.get
|
val currentRound = game.logic.getCurrentRound.get
|
||||||
val currentTrick = game.logic.getCurrentTrick.get
|
val currentTrick = game.logic.getCurrentTrick.get
|
||||||
|
|
||||||
@@ -57,6 +58,7 @@ class PollingController @Inject() (
|
|||||||
"status" -> "cardPlayed",
|
"status" -> "cardPlayed",
|
||||||
"animation" -> newRound,
|
"animation" -> newRound,
|
||||||
"handData" -> stringHand,
|
"handData" -> stringHand,
|
||||||
|
"dog" -> player.isInDogLife,
|
||||||
"currentPlayerName" -> game.logic.getCurrentPlayer.get.name,
|
"currentPlayerName" -> game.logic.getCurrentPlayer.get.name,
|
||||||
"trumpSuit" -> currentRound.trumpSuit.toString,
|
"trumpSuit" -> currentRound.trumpSuit.toString,
|
||||||
"trickCards" -> trickCardsJson,
|
"trickCards" -> trickCardsJson,
|
||||||
@@ -84,12 +86,12 @@ class PollingController @Inject() (
|
|||||||
case NewRound =>
|
case NewRound =>
|
||||||
val player = game.getPlayerByUser(userSession.user)
|
val player = game.getPlayerByUser(userSession.user)
|
||||||
val hand = player.currentHand()
|
val hand = player.currentHand()
|
||||||
val jsonResponse = buildCardPlayResponse(game, hand, true)
|
val jsonResponse = buildCardPlayResponse(game, hand, player, true)
|
||||||
Ok(jsonResponse)
|
Ok(jsonResponse)
|
||||||
case CardPlayed =>
|
case CardPlayed =>
|
||||||
val player = game.getPlayerByUser(userSession.user)
|
val player = game.getPlayerByUser(userSession.user)
|
||||||
val hand = player.currentHand()
|
val hand = player.currentHand()
|
||||||
val jsonResponse = buildCardPlayResponse(game, hand, false)
|
val jsonResponse = buildCardPlayResponse(game, hand, player, false)
|
||||||
Ok(jsonResponse)
|
Ok(jsonResponse)
|
||||||
case LobbyUpdate =>
|
case LobbyUpdate =>
|
||||||
Ok(buildLobbyUsersResponse(game, userSession))
|
Ok(buildLobbyUsersResponse(game, userSession))
|
||||||
|
|||||||
@@ -88,12 +88,7 @@ class GameLobby private(
|
|||||||
if (event.oldState == MainMenu && event.newState == Lobby) {
|
if (event.oldState == MainMenu && event.newState == Lobby) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (event.oldState == Lobby && event.newState == InGame) {
|
|
||||||
addToQueue(ReloadEvent)
|
addToQueue(ReloadEvent)
|
||||||
return
|
|
||||||
} else {
|
|
||||||
addToQueue(ReloadEvent)
|
|
||||||
}
|
|
||||||
users.values.foreach(session => session.updatePlayer(event))
|
users.values.foreach(session => session.updatePlayer(event))
|
||||||
case event: SessionClosed =>
|
case event: SessionClosed =>
|
||||||
users.values.foreach(session => session.updatePlayer(event))
|
users.values.foreach(session => session.updatePlayer(event))
|
||||||
@@ -198,7 +193,7 @@ class GameLobby private(
|
|||||||
throw new CantPlayCardException("You are not in dog life!")
|
throw new CantPlayCardException("You are not in dog life!")
|
||||||
}
|
}
|
||||||
if (cardIndex == -1) {
|
if (cardIndex == -1) {
|
||||||
if (!MatchUtil.dogNeedsToPlay(getMatch, getRound)) {
|
if (MatchUtil.dogNeedsToPlay(getMatch, getRound)) {
|
||||||
throw new CantPlayCardException("You can't skip this round!")
|
throw new CantPlayCardException("You can't skip this round!")
|
||||||
}
|
}
|
||||||
logic.playerInputLogic.receivedDog(None)
|
logic.playerInputLogic.receivedDog(None)
|
||||||
@@ -233,6 +228,19 @@ class GameLobby private(
|
|||||||
logic.playerTieLogic.receivedTieBreakerCard(tieNumber)
|
logic.playerTieLogic.receivedTieBreakerCard(tieNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def returnToLobby(userSession: UserSession): Unit = {
|
||||||
|
if (users.contains(userSession.id)) {
|
||||||
|
throw new NotInThisGameException("You are not in this game!")
|
||||||
|
}
|
||||||
|
val session = users(userSession.id)
|
||||||
|
if (session != userSession) {
|
||||||
|
throw new IllegalArgumentException("User session does not match!")
|
||||||
|
}
|
||||||
|
if (!session.host)
|
||||||
|
throw new NotHostException("Only the host can return to the lobby!")
|
||||||
|
logic.createSession()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//-------------------
|
//-------------------
|
||||||
|
|
||||||
|
|||||||
@@ -75,9 +75,14 @@
|
|||||||
<div class="row justify-content-center ingame-cards-slide" id="card-slide">
|
<div class="row justify-content-center ingame-cards-slide" id="card-slide">
|
||||||
@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')">
|
<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)">
|
||||||
@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) {
|
||||||
|
<div class="mt-2">
|
||||||
|
<button class="btn btn-danger" onclick="handleSkipDogLife('@gamelobby.id')">Skip Dog Life</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,13 +18,13 @@
|
|||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="d-flex flex-column min-vh-100">
|
<body class="d-flex flex-column min-vh-100" id="main-body">
|
||||||
@* And here's where we render the `Html` object containing
|
@* And here's where we render the `Html` object containing
|
||||||
* the page content. *@
|
* the page content. *@
|
||||||
@content
|
@content
|
||||||
|
</body>
|
||||||
<script src="@routes.JavaScriptRoutingController.javascriptRoutes()" type="text/javascript"></script>
|
<script src="@routes.JavaScriptRoutingController.javascriptRoutes()" type="text/javascript"></script>
|
||||||
<script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
|
<script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -26,12 +26,16 @@ GET /logout controllers.UserController.logout()
|
|||||||
GET /game/:id controllers.IngameController.game(id: String)
|
GET /game/:id controllers.IngameController.game(id: String)
|
||||||
GET /game/:id/join controllers.IngameController.joinGame(id: String)
|
GET /game/:id/join controllers.IngameController.joinGame(id: String)
|
||||||
GET /game/:id/start controllers.IngameController.startGame(id: String)
|
GET /game/:id/start controllers.IngameController.startGame(id: String)
|
||||||
POST /game/:id/kickPlayer controllers.IngameController.kickPlayer(id: String, playerId: String)
|
POST /game/:id/kickPlayer/:playerToKick controllers.IngameController.kickPlayer(id: String, playerToKick: String)
|
||||||
|
|
||||||
POST /game/:id/trump controllers.IngameController.playTrump(id: String)
|
POST /game/:id/trump controllers.IngameController.playTrump(id: String)
|
||||||
POST /game/:id/tie controllers.IngameController.playTie(id: String)
|
POST /game/:id/tie controllers.IngameController.playTie(id: String)
|
||||||
|
|
||||||
GET /game/:id/leaveGame controllers.IngameController.leaveGame(id: String)
|
GET /game/:id/leaveGame controllers.IngameController.leaveGame(id: String)
|
||||||
POST /game/:id/playCard controllers.IngameController.playCard(id: String)
|
POST /game/:id/playCard controllers.IngameController.playCard(id: String)
|
||||||
|
POST /game/:id/dogPlayCard controllers.IngameController.playDogCard(id: String)
|
||||||
|
|
||||||
|
POST /game/:id/returnToLobby controllers.IngameController.returnToLobby(id: String)
|
||||||
|
|
||||||
# Polling
|
# Polling
|
||||||
GET /polling controllers.PollingController.polling(gameId: String)
|
GET /polling/:gameId controllers.PollingController.polling(gameId: String)
|
||||||
@@ -89,7 +89,7 @@ function pollForUpdates(gameId) {
|
|||||||
const $lobbyElement = $('#lobbybackground');
|
const $lobbyElement = $('#lobbybackground');
|
||||||
const $mainmenuElement = $('#main-menu-screen')
|
const $mainmenuElement = $('#main-menu-screen')
|
||||||
if (!$handElement.length && !$lobbyElement.length && !$mainmenuElement.length) {
|
if (!$handElement.length && !$lobbyElement.length && !$mainmenuElement.length) {
|
||||||
setTimeout(() => pollForUpdates(gameId), 5000);
|
setTimeout(() => pollForUpdates(gameId), 1000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const route = jsRoutes.controllers.PollingController.polling(gameId);
|
const route = jsRoutes.controllers.PollingController.polling(gameId);
|
||||||
@@ -115,13 +115,15 @@ function pollForUpdates(gameId) {
|
|||||||
$handElement.removeClass('ingame-cards-slide');
|
$handElement.removeClass('ingame-cards-slide');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dog = data.dog;
|
||||||
|
|
||||||
newHand.forEach((cardId, index) => {
|
newHand.forEach((cardId, index) => {
|
||||||
const cardHtml = `
|
const cardHtml = `
|
||||||
<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"
|
<div class="btn btn-outline-light p-0 border-0 shadow-none"
|
||||||
data-card-id="${index}"
|
data-card-id="${index}"
|
||||||
style="border-radius: 6px"
|
style="border-radius: 6px"
|
||||||
onclick="handlePlayCard(this, '${gameId}')">
|
onclick="handlePlayCard(this, '${gameId}', '${dog}')">
|
||||||
|
|
||||||
<img src="/assets/images/cards/${cardId}.png" width="120px" style="border-radius: 6px"/>
|
<img src="/assets/images/cards/${cardId}.png" width="120px" style="border-radius: 6px"/>
|
||||||
</div>
|
</div>
|
||||||
@@ -130,6 +132,14 @@ function pollForUpdates(gameId) {
|
|||||||
newHandHTML += cardHtml;
|
newHandHTML += cardHtml;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (dog) {
|
||||||
|
newHandHTML += `
|
||||||
|
<div class="mt-2">
|
||||||
|
<button class="btn btn-danger" onclick="handleSkipDogLife(this, '${gameId}')">Skip Dog Life</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
$handElement.html(newHandHTML);
|
$handElement.html(newHandHTML);
|
||||||
$('#current-player-name').text(data.currentPlayerName)
|
$('#current-player-name').text(data.currentPlayerName)
|
||||||
if (data.nextPlayer) {
|
if (data.nextPlayer) {
|
||||||
@@ -248,12 +258,12 @@ function pollForUpdates(gameId) {
|
|||||||
console.error(`Something unexpected happened while polling. ${jqXHR.status}, ${errorThrown}`)
|
console.error(`Something unexpected happened while polling. ${jqXHR.status}, ${errorThrown}`)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
complete: ((jqXHR, textStatus) => {
|
complete: (() => {
|
||||||
if (!window.location.href.includes("game")) {
|
if (!window.location.href.includes("game")) {
|
||||||
console.log("[DEBUG] Page URL changed. Stopping poll restart.");
|
console.log("[DEBUG] Page URL changed. Stopping poll restart.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setTimeout(() => pollForUpdates(gameId), 500);
|
setTimeout(() => pollForUpdates(gameId), 200);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -374,7 +384,7 @@ function sendLeavePlayerRequest(gameId) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePlayCard(cardobject, gameId) {
|
function handlePlayCard(cardobject, gameId, dog = false) {
|
||||||
const cardId = cardobject.dataset.cardId;
|
const cardId = cardobject.dataset.cardId;
|
||||||
const jsonObj = {
|
const jsonObj = {
|
||||||
cardID: cardId
|
cardID: cardId
|
||||||
@@ -382,6 +392,33 @@ function handlePlayCard(cardobject, gameId) {
|
|||||||
sendPlayCardRequest(jsonObj, gameId, cardobject)
|
sendPlayCardRequest(jsonObj, gameId, cardobject)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleSkipDogLife(cardobject, gameId) {
|
||||||
|
const route = jsRoutes.controllers.IngameController.playDogCard(gameId);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: route.url,
|
||||||
|
type: route.type,
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
data: JSON.stringify({
|
||||||
|
cardID: 'skip'
|
||||||
|
}),
|
||||||
|
error: (jqXHR => {
|
||||||
|
let error;
|
||||||
|
try {
|
||||||
|
error = JSON.parse(jqXHR.responseText);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse error response:", e);
|
||||||
|
}
|
||||||
|
if (error?.errorMessage) {
|
||||||
|
alert(`${error.errorMessage}`);
|
||||||
|
} else {
|
||||||
|
alert('An unexpected error occurred. Please try again.');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function sendPlayCardRequest(jsonObj, gameId, cardobject) {
|
function sendPlayCardRequest(jsonObj, gameId, cardobject) {
|
||||||
const wiggleKeyframes = [
|
const wiggleKeyframes = [
|
||||||
{ transform: 'translateX(0)' },
|
{ transform: 'translateX(0)' },
|
||||||
@@ -405,10 +442,6 @@ function sendPlayCardRequest(jsonObj, gameId, cardobject) {
|
|||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
data: JSON.stringify(jsonObj),
|
data: JSON.stringify(jsonObj),
|
||||||
success: (data => {
|
|
||||||
if (data.status === 'success') {
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
error: (jqXHR => {
|
error: (jqXHR => {
|
||||||
try {
|
try {
|
||||||
error = JSON.parse(jqXHR.responseText);
|
error = JSON.parse(jqXHR.responseText);
|
||||||
|
|||||||
Reference in New Issue
Block a user