diff --git a/knockoutwhistweb/app/controllers/IngameController.scala b/knockoutwhistweb/app/controllers/IngameController.scala index 6f93a83..83cc988 100644 --- a/knockoutwhistweb/app/controllers/IngameController.scala +++ b/knockoutwhistweb/app/controllers/IngameController.scala @@ -227,7 +227,10 @@ class IngameController @Inject() ( val game = podManager.getGame(gameId) game match { 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 val result = Try { cardIdOpt match { @@ -343,5 +346,47 @@ class IngameController @Inject() ( NotFound("Game not found") } } + + + 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" + )) + } + } } \ No newline at end of file diff --git a/knockoutwhistweb/app/controllers/JavaScriptRoutingController.scala b/knockoutwhistweb/app/controllers/JavaScriptRoutingController.scala index eb7e82a..92beeab 100644 --- a/knockoutwhistweb/app/controllers/JavaScriptRoutingController.scala +++ b/knockoutwhistweb/app/controllers/JavaScriptRoutingController.scala @@ -20,6 +20,7 @@ class JavaScriptRoutingController @Inject()( routes.javascript.IngameController.kickPlayer, routes.javascript.IngameController.leaveGame, routes.javascript.IngameController.playCard, + routes.javascript.IngameController.playDogCard, routes.javascript.PollingController.polling ) ).as("text/javascript") diff --git a/knockoutwhistweb/app/controllers/PollingController.scala b/knockoutwhistweb/app/controllers/PollingController.scala index 4ed62d1..bce96b2 100644 --- a/knockoutwhistweb/app/controllers/PollingController.scala +++ b/knockoutwhistweb/app/controllers/PollingController.scala @@ -3,6 +3,7 @@ package controllers import auth.{AuthAction, AuthenticatedRequest} import controllers.PollingController.{scheduler, timeoutDuration} import de.knockoutwhist.cards.Hand +import de.knockoutwhist.player.AbstractPlayer import logic.PodManager import logic.game.{GameLobby, PollingEvents} import logic.game.PollingEvents.{CardPlayed, LobbyCreation, LobbyUpdate, NewRound, ReloadEvent} @@ -28,7 +29,7 @@ class PollingController @Inject() ( implicit val ec: ExecutionContext ) 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 currentTrick = game.logic.getCurrentTrick.get @@ -57,6 +58,7 @@ class PollingController @Inject() ( "status" -> "cardPlayed", "animation" -> newRound, "handData" -> stringHand, + "dog" -> player.isInDogLife, "currentPlayerName" -> game.logic.getCurrentPlayer.get.name, "trumpSuit" -> currentRound.trumpSuit.toString, "trickCards" -> trickCardsJson, @@ -84,12 +86,12 @@ class PollingController @Inject() ( case NewRound => val player = game.getPlayerByUser(userSession.user) val hand = player.currentHand() - val jsonResponse = buildCardPlayResponse(game, hand, true) + val jsonResponse = buildCardPlayResponse(game, hand, player, true) Ok(jsonResponse) case CardPlayed => val player = game.getPlayerByUser(userSession.user) val hand = player.currentHand() - val jsonResponse = buildCardPlayResponse(game, hand, false) + val jsonResponse = buildCardPlayResponse(game, hand, player, false) Ok(jsonResponse) case LobbyUpdate => Ok(buildLobbyUsersResponse(game, userSession)) diff --git a/knockoutwhistweb/app/logic/game/GameLobby.scala b/knockoutwhistweb/app/logic/game/GameLobby.scala index 3f638a8..418f685 100644 --- a/knockoutwhistweb/app/logic/game/GameLobby.scala +++ b/knockoutwhistweb/app/logic/game/GameLobby.scala @@ -88,12 +88,7 @@ class GameLobby private( if (event.oldState == MainMenu && event.newState == Lobby) { return } - if (event.oldState == Lobby && event.newState == InGame) { - addToQueue(ReloadEvent) - return - } else { - addToQueue(ReloadEvent) - } + addToQueue(ReloadEvent) users.values.foreach(session => session.updatePlayer(event)) case event: SessionClosed => users.values.foreach(session => session.updatePlayer(event)) @@ -198,7 +193,7 @@ class GameLobby private( throw new CantPlayCardException("You are not in dog life!") } if (cardIndex == -1) { - if (!MatchUtil.dogNeedsToPlay(getMatch, getRound)) { + if (MatchUtil.dogNeedsToPlay(getMatch, getRound)) { throw new CantPlayCardException("You can't skip this round!") } logic.playerInputLogic.receivedDog(None) @@ -233,6 +228,19 @@ class GameLobby private( 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() + } + //------------------- diff --git a/knockoutwhistweb/app/views/ingame/ingame.scala.html b/knockoutwhistweb/app/views/ingame/ingame.scala.html index 4ab4f17..c1c4557 100644 --- a/knockoutwhistweb/app/views/ingame/ingame.scala.html +++ b/knockoutwhistweb/app/views/ingame/ingame.scala.html @@ -75,9 +75,14 @@
@for(i <- player.currentHand().get.cards.indices) {
-
+
@util.WebUIUtils.cardtoImage(player.currentHand().get.cards(i)) width="120px" style="border-radius: 6px"/>
+ @if(player.isInDogLife) { +
+ +
+ }
}
diff --git a/knockoutwhistweb/app/views/main.scala.html b/knockoutwhistweb/app/views/main.scala.html index 4d8c0b2..84b363f 100644 --- a/knockoutwhistweb/app/views/main.scala.html +++ b/knockoutwhistweb/app/views/main.scala.html @@ -18,13 +18,13 @@ - + @* And here's where we render the `Html` object containing * the page content. *@ @content - - - - + + + + diff --git a/knockoutwhistweb/conf/routes b/knockoutwhistweb/conf/routes index 3c2a6e6..3ff29a9 100644 --- a/knockoutwhistweb/conf/routes +++ b/knockoutwhistweb/conf/routes @@ -4,34 +4,38 @@ # ~~~~ # For the javascript routing -GET /assets/js/routes controllers.JavaScriptRoutingController.javascriptRoutes() +GET /assets/js/routes controllers.JavaScriptRoutingController.javascriptRoutes() # Primary routes -GET / controllers.MainMenuController.index() -GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) +GET / controllers.MainMenuController.index() +GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) # Main menu routes -GET /mainmenu controllers.MainMenuController.mainMenu() -GET /rules controllers.MainMenuController.rules() +GET /mainmenu controllers.MainMenuController.mainMenu() +GET /rules controllers.MainMenuController.rules() -POST /createGame controllers.MainMenuController.createGame() -POST /joinGame controllers.MainMenuController.joinGame() +POST /createGame controllers.MainMenuController.createGame() +POST /joinGame controllers.MainMenuController.joinGame() # User authentication routes -GET /login controllers.UserController.login() -POST /login controllers.UserController.login_Post() +GET /login controllers.UserController.login() +POST /login controllers.UserController.login_Post() -GET /logout controllers.UserController.logout() +GET /logout controllers.UserController.logout() # In-game routes -GET /game/:id controllers.IngameController.game(id: String) -GET /game/:id/join controllers.IngameController.joinGame(id: String) -GET /game/:id/start controllers.IngameController.startGame(id: String) -POST /game/:id/kickPlayer controllers.IngameController.kickPlayer(id: String, playerId: String) +GET /game/:id controllers.IngameController.game(id: String) +GET /game/:id/join controllers.IngameController.joinGame(id: String) +GET /game/:id/start controllers.IngameController.startGame(id: 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/tie controllers.IngameController.playTie(id: String) +POST /game/:id/trump controllers.IngameController.playTrump(id: String) +POST /game/:id/tie controllers.IngameController.playTie(id: String) + +GET /game/:id/leaveGame controllers.IngameController.leaveGame(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) -GET /game/:id/leaveGame controllers.IngameController.leaveGame(id: String) -POST /game/:id/playCard controllers.IngameController.playCard(id: String) # Polling -GET /polling controllers.PollingController.polling(gameId: String) \ No newline at end of file +GET /polling/:gameId controllers.PollingController.polling(gameId: String) \ No newline at end of file diff --git a/knockoutwhistweb/public/javascripts/main.js b/knockoutwhistweb/public/javascripts/main.js index 434149a..20f0bb1 100644 --- a/knockoutwhistweb/public/javascripts/main.js +++ b/knockoutwhistweb/public/javascripts/main.js @@ -89,7 +89,7 @@ function pollForUpdates(gameId) { const $lobbyElement = $('#lobbybackground'); const $mainmenuElement = $('#main-menu-screen') if (!$handElement.length && !$lobbyElement.length && !$mainmenuElement.length) { - setTimeout(() => pollForUpdates(gameId), 5000); + setTimeout(() => pollForUpdates(gameId), 1000); return; } const route = jsRoutes.controllers.PollingController.polling(gameId); @@ -115,13 +115,15 @@ function pollForUpdates(gameId) { $handElement.removeClass('ingame-cards-slide'); } + const dog = data.dog; + newHand.forEach((cardId, index) => { const cardHtml = `
+ onclick="handlePlayCard(this, '${gameId}', '${dog}')">
@@ -130,6 +132,14 @@ function pollForUpdates(gameId) { newHandHTML += cardHtml; }); + if (dog) { + newHandHTML += ` +
+ +
+ `; + } + $handElement.html(newHandHTML); $('#current-player-name').text(data.currentPlayerName) if (data.nextPlayer) { @@ -248,12 +258,12 @@ function pollForUpdates(gameId) { console.error(`Something unexpected happened while polling. ${jqXHR.status}, ${errorThrown}`) } }), - complete: ((jqXHR, textStatus) => { + complete: (() => { if (!window.location.href.includes("game")) { console.log("[DEBUG] Page URL changed. Stopping poll restart."); 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 jsonObj = { cardID: cardId @@ -382,6 +392,33 @@ function handlePlayCard(cardobject, gameId) { 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) { const wiggleKeyframes = [ { transform: 'translateX(0)' }, @@ -405,10 +442,6 @@ function sendPlayCardRequest(jsonObj, gameId, cardobject) { contentType: 'application/json', dataType: 'json', data: JSON.stringify(jsonObj), - success: (data => { - if (data.status === 'success') { - } - }), error: (jqXHR => { try { error = JSON.parse(jqXHR.responseText);