package controllers import auth.{AuthAction, AuthenticatedRequest} import de.knockoutwhist.cards.Hand import de.knockoutwhist.control.GameState.{InGame, Lobby, SelectTrump, TieBreak} import exceptions.{CantPlayCardException, GameFullException, NotEnoughPlayersException, NotHostException, NotInThisGameException} import logic.PodManager import logic.game.PollingEvents.CardPlayed import logic.game.PollingEvents.GameStarted import logic.game.{GameLobby, PollingEvents} import model.sessions.{PlayerSession, UserSession} import model.users.User import play.api.* import play.api.libs.json.{JsArray, JsValue, Json} import play.api.mvc.* import util.WebUIUtils import java.util.UUID import javax.inject.* import scala.concurrent.Future import scala.util.Try import scala.concurrent.ExecutionContext @Singleton class IngameController @Inject() ( val cc: ControllerComponents, val podManager: PodManager, val authAction: AuthAction, implicit val ec: ExecutionContext ) extends AbstractController(cc) { // --- Helper function (defined outside match/if for scope) --- def buildSuccessResponse(game: GameLobby, hand: Option[Hand]): JsValue = { // NOTE: Replace the unsafe .get calls here if game state is not guaranteed val currentRound = game.logic.getCurrentRound.get val currentTrick = game.logic.getCurrentTrick.get // JSON Building Logic: val trickCardsJson = Json.toJson( currentTrick.cards.map { case (card, player) => Json.obj("cardId" -> WebUIUtils.cardtoString(card), "player" -> player.name) } ) val scoreTableJson = Json.toJson( game.getLogic.getPlayerQueue.get.toList.map { player => Json.obj( "name" -> player.name, "tricks" -> currentRound.tricklist.count(_.winner.contains(player)) ) } ) val stringHand = hand.map { h => val cardStrings = h.cards.map(WebUIUtils.cardtoString(_)) Json.toJson(cardStrings).as[JsArray] }.getOrElse(Json.arr()) val firstCardId = currentTrick.firstCard.map(WebUIUtils.cardtoString).getOrElse("BLANK") val nextPlayer = game.getLogic.getPlayerQueue.get.duplicate().nextPlayer().name Json.obj( "status" -> "cardPlayed", "handData" -> stringHand, "currentPlayerName" -> game.logic.getCurrentPlayer.get.name, "trumpSuit" -> currentRound.trumpSuit.toString, "trickCards" -> trickCardsJson, "scoreTable" -> scoreTableJson, "firstCardId" -> firstCardId, "nextPlayer" -> nextPlayer ) } def handleEvent(event: PollingEvents, game: GameLobby, user: User): Result = { event match { case CardPlayed => val player = game.getPlayerByUser(user) val hand = player.currentHand() val jsonResponse = buildSuccessResponse(game, hand) Ok(jsonResponse) case GameStarted => val jsonResponse = Json.obj( "status" -> "gameStart", "redirectUrl" -> routes.IngameController.game(game.id).url ) Ok(jsonResponse) } } // --- Main Polling Action --- def polling(gameId: String): Action[AnyContent] = authAction.async { implicit request: AuthenticatedRequest[AnyContent] => val playerId = request.user.id // 1. Safely look up the game podManager.getGame(gameId) match { case Some(game) => // 2. Short-Poll Check (Check for missed events) if (game.getPollingState.nonEmpty) { val event = game.getPollingState.dequeue() Future.successful(handleEvent(event, game, request.user)) } else { val eventPromise = game.registerWaiter(playerId) eventPromise.future.map { event => game.removeWaiter(playerId) handleEvent(event, game, request.user) }.recover { case _: Throwable => game.removeWaiter(playerId) NoContent } } case None => // Game not found Future.successful(NotFound("Game not found.")) } } def game(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => val game = podManager.getGame(gameId) game match { case Some(g) => g.logic.getCurrentState match { case Lobby => Ok(views.html.lobby.lobby(Some(request.user), g)) case InGame => Ok(views.html.ingame.ingame( g.getPlayerByUser(request.user), g )) case SelectTrump => Ok(views.html.ingame.selecttrump( g.getPlayerByUser(request.user), g.logic )) case TieBreak => Ok(views.html.ingame.tie( g.getPlayerByUser(request.user), g.logic )) case _ => InternalServerError(s"Invalid game state for in-game view. GameId: $gameId" + s" State: ${g.logic.getCurrentState}") } case None => NotFound("Game not found") } //NotFound(s"Reached end of game method unexpectedly. GameId: $gameId") } def startGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => val game = podManager.getGame(gameId) val result = Try { game match { case Some(g) => g.startGame(request.user) case None => NotFound("Game not found") } } 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 _: NotHostException => Forbidden(Json.obj( "status" -> "failure", "errorMessage" -> throwable.getMessage )) case _: NotEnoughPlayersException => BadRequest(Json.obj( "status" -> "failure", "errorMessage" -> throwable.getMessage )) case _ => InternalServerError(Json.obj( "status" -> "failure", "errorMessage" -> throwable.getMessage )) } } } def kickPlayer(gameId: String, playerToKick: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => val game = podManager.getGame(gameId) val playerToKickUUID = UUID.fromString(playerToKick) val result = Try { game.get.leaveGame(playerToKickUUID) } if(result.isSuccess) { Ok(Json.obj( "status" -> "success", "redirectUrl" -> routes.IngameController.game(gameId).url )) } else { InternalServerError(Json.obj( "status" -> "failure", "errorMessage" -> "Something went wrong." )) } } def leaveGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => val game = podManager.getGame(gameId) val result = Try { game.get.leaveGame(request.user.id) } if (result.isSuccess) { Ok(Json.obj( "status" -> "success", "redirectUrl" -> routes.MainMenuController.mainMenu().url )) } else { InternalServerError(Json.obj( "status" -> "failure", "errorMessage" -> "Something went wrong." )) } } def joinGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => val game = podManager.getGame(gameId) val result = Try { game match { case Some(g) => g.addUser(request.user) case None => NotFound("Game not found") } } if (result.isSuccess) { Redirect(routes.IngameController.game(gameId)) } else { val throwable = result.failed.get throwable match { case _: GameFullException => BadRequest(throwable.getMessage) case _: IllegalArgumentException => BadRequest(throwable.getMessage) case _: IllegalStateException => BadRequest(throwable.getMessage) case _ => InternalServerError(throwable.getMessage) } } } def playCard(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => { val game = podManager.getGame(gameId) game match { case Some(g) => val jsonBody = request.body.asJson val cardIdOpt: Option[String] = jsonBody.flatMap { jsValue => (jsValue \ "cardID").asOpt[String] } cardIdOpt match { case Some(cardId) => var optSession: Option[UserSession] = None val result = Try { val session = g.getUserSession(request.user.id) optSession = Some(session) session.lock.lock() g.playCard(session, cardId.toInt) } optSession.foreach(_.lock.unlock()) if (result.isSuccess) { Ok(Json.obj( "status" -> "success", "redirectUrl" -> routes.IngameController.game(gameId).url )) } else { val throwable = result.failed.get throwable match { case _: CantPlayCardException => BadRequest(Json.obj( "status" -> "failure", "errorMessage" -> throwable.getMessage )) case _: NotInThisGameException => BadRequest(Json.obj( "status" -> "failure", "errorMessage" -> throwable.getMessage )) case _: IllegalArgumentException => 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 => BadRequest(Json.obj( "status" -> "failure", "errorMessage" -> "cardId Parameter is missing" )) } case None => NotFound(Json.obj( "status" -> "failure", "errorMessage" -> "Game not found" )) } } } def playDogCard(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => { val game = podManager.getGame(gameId) game match { case Some(g) => { val cardIdOpt = request.body.asFormUrlEncoded.flatMap(_.get("cardId").flatMap(_.headOption)) var optSession: Option[UserSession] = None val result = Try { cardIdOpt match { case Some(cardId) if cardId == "skip" => val session = g.getUserSession(request.user.id) optSession = Some(session) session.lock.lock() g.playDogCard(session, -1) case Some(cardId) => val session = g.getUserSession(request.user.id) optSession = Some(session) session.lock.lock() g.playDogCard(session, cardId.toInt) case None => throw new IllegalArgumentException("cardId parameter is missing") } } optSession.foreach(_.lock.unlock()) if (result.isSuccess) { NoContent } else { val throwable = result.failed.get throwable match { case _: CantPlayCardException => BadRequest(throwable.getMessage) case _: NotInThisGameException => BadRequest(throwable.getMessage) case _: IllegalArgumentException => BadRequest(throwable.getMessage) case _: IllegalStateException => BadRequest(throwable.getMessage) case _ => InternalServerError(throwable.getMessage) } } } case None => NotFound("Game not found") } } } def playTrump(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => val game = podManager.getGame(gameId) game match { case Some(g) => val trumpOpt = request.body.asFormUrlEncoded.flatMap(_.get("trump").flatMap(_.headOption)) trumpOpt match { case Some(trump) => var optSession: Option[UserSession] = None val result = Try { val session = g.getUserSession(request.user.id) optSession = Some(session) session.lock.lock() g.selectTrump(session, trump.toInt) } optSession.foreach(_.lock.unlock()) if (result.isSuccess) { NoContent } else { val throwable = result.failed.get throwable match { case _: IllegalArgumentException => BadRequest(throwable.getMessage) case _: NotInThisGameException => BadRequest(throwable.getMessage) case _: IllegalStateException => BadRequest(throwable.getMessage) case _ => InternalServerError(throwable.getMessage) } } case None => BadRequest("trump parameter is missing") } case None => NotFound("Game not found") } } def playTie(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => val game = podManager.getGame(gameId) game match { case Some(g) => val tieOpt = request.body.asFormUrlEncoded.flatMap(_.get("tie").flatMap(_.headOption)) tieOpt match { case Some(tie) => var optSession: Option[UserSession] = None val result = Try { val session = g.getUserSession(request.user.id) optSession = Some(session) session.lock.lock() g.selectTie(g.getUserSession(request.user.id), tie.toInt) } optSession.foreach(_.lock.unlock()) if (result.isSuccess) { NoContent } else { val throwable = result.failed.get throwable match { case _: IllegalArgumentException => BadRequest(throwable.getMessage) case _: NotInThisGameException => BadRequest(throwable.getMessage) case _: IllegalStateException => BadRequest(throwable.getMessage) case _ => InternalServerError(throwable.getMessage) } } case None => BadRequest("tie parameter is missing") } case None => NotFound("Game not found") } } }