diff --git a/bruno/KnockOutWhist/Login.bru b/bruno/KnockOutWhist/Login.bru index 4c266ed..2aecc28 100644 --- a/bruno/KnockOutWhist/Login.bru +++ b/bruno/KnockOutWhist/Login.bru @@ -1,7 +1,7 @@ meta { name: Login type: http - seq: 1 + seq: 2 } post { @@ -22,4 +22,5 @@ body:multipart-form { settings { encodeUrl: true + timeout: 0 } diff --git a/knockoutwhistweb/app/auth/Auth.scala b/knockoutwhistweb/app/auth/Auth.scala index 8ec161e..b956035 100644 --- a/knockoutwhistweb/app/auth/Auth.scala +++ b/knockoutwhistweb/app/auth/Auth.scala @@ -14,16 +14,14 @@ class AuthAction @Inject()(val sessionManager: SessionManager, val parser: BodyP extends ActionBuilder[AuthenticatedRequest, AnyContent] { override def executionContext: ExecutionContext = ec - - // This simulates checking if a user is logged in (e.g. via session) + private def getUserFromSession(request: RequestHeader): Option[User] = { val session = request.cookies.get("sessionId") if (session.isDefined) return sessionManager.getUserBySession(session.get.value) None } - - // Transform a normal request into an AuthenticatedRequest + override def invokeBlock[A]( request: Request[A], block: AuthenticatedRequest[A] => Future[Result] diff --git a/knockoutwhistweb/app/controllers/IngameController.scala b/knockoutwhistweb/app/controllers/IngameController.scala index 02544ee..ef588a8 100644 --- a/knockoutwhistweb/app/controllers/IngameController.scala +++ b/knockoutwhistweb/app/controllers/IngameController.scala @@ -1,11 +1,14 @@ package controllers import auth.{AuthAction, AuthenticatedRequest} -import logic.user.{SessionManager, UserManager} +import de.knockoutwhist.control.GameState.{InGame, Lobby, SelectTrump, TieBreak} +import exceptions.{CantPlayCardException, GameFullException, NotEnoughPlayersException, NotHostException, NotInThisGameException} +import logic.PodManager import play.api.* import play.api.mvc.* import javax.inject.* +import scala.util.Try /** @@ -14,12 +17,228 @@ import javax.inject.* */ @Singleton class IngameController @Inject()( - val controllerComponents: ControllerComponents, - val authAction: AuthAction - ) extends BaseController { - - def mainMenu(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => - Ok("Main Menu for user: " + request.user.name) + val controllerComponents: ControllerComponents, + val authAction: AuthAction, + val podManager: PodManager + ) extends BaseController { + + 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("Lobby: " + gameId) + case InGame => + Ok(views.html.ingame.ingame( + g.getPlayerByUser(request.user), + g.logic + )) + 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) { + NoContent + } else { + val throwable = result.failed.get + throwable match { + case _: NotInThisGameException => + BadRequest(throwable.getMessage) + case _: NotHostException => + Forbidden(throwable.getMessage) + case _: NotEnoughPlayersException => + BadRequest(throwable.getMessage) + case _ => + InternalServerError(throwable.getMessage) + } + } + } + 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 cardIdOpt = request.body.asFormUrlEncoded.flatMap(_.get("cardId").flatMap(_.headOption)) + cardIdOpt match { + case Some(cardId) => + val result = Try { + g.playCard(g.getUserSession(request.user.id), cardId.toInt) + } + 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 => + BadRequest("cardId parameter is missing") + } + case None => + NotFound("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)) + val result = Try { + cardIdOpt match { + case Some(cardId) if cardId == "skip" => + g.playDogCard(g.getUserSession(request.user.id), -1) + case Some(cardId) => + g.playDogCard(g.getUserSession(request.user.id), cardId.toInt) + case None => + throw new IllegalArgumentException("cardId parameter is missing") + } + } + 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) => + val result = Try { + g.selectTrump(g.getUserSession(request.user.id), trump.toInt) + } + 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) => + val result = Try { + g.selectTie(g.getUserSession(request.user.id), tie.toInt) + } + 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") + } } } \ No newline at end of file diff --git a/knockoutwhistweb/app/controllers/MainMenuController.scala b/knockoutwhistweb/app/controllers/MainMenuController.scala index 5d85e17..a7a1637 100644 --- a/knockoutwhistweb/app/controllers/MainMenuController.scala +++ b/knockoutwhistweb/app/controllers/MainMenuController.scala @@ -1,6 +1,7 @@ package controllers import auth.{AuthAction, AuthenticatedRequest} +import logic.PodManager import play.api.* import play.api.mvc.* @@ -13,23 +14,32 @@ import javax.inject.* */ @Singleton class MainMenuController @Inject()( - val controllerComponents: ControllerComponents, - val authAction: AuthAction + val controllerComponents: ControllerComponents, + val authAction: AuthAction, + val podManager: PodManager ) extends BaseController { // Pass the request-handling function directly to authAction (no nested Action) def mainMenu(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => Ok("Main Menu for user: " + request.user.name) } - + def index(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => - Redirect("/mainmenu") + Redirect(routes.MainMenuController.mainMenu()) } - + + def createGame(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => + val gameLobby = podManager.createGame( + host = request.user, + name = s"${request.user.name}'s Game", + maxPlayers = 4 + ) + Redirect(routes.IngameController.game(gameLobby.id)) + } + def rules(): Action[AnyContent] = { Action { implicit request => - Ok(views.html.rules()) + Ok(views.html.mainmenu.rules()) } } - } \ No newline at end of file diff --git a/knockoutwhistweb/app/controllers/UserController.scala b/knockoutwhistweb/app/controllers/UserController.scala index e10bffd..361826f 100644 --- a/knockoutwhistweb/app/controllers/UserController.scala +++ b/knockoutwhistweb/app/controllers/UserController.scala @@ -28,10 +28,10 @@ class UserController @Inject()( if (possibleUser.isDefined) { Redirect(routes.MainMenuController.mainMenu()) } else { - Ok(views.html.login()) + Ok(views.html.login.login()) } } else { - Ok(views.html.login()) + Ok(views.html.login.login()) } } } diff --git a/knockoutwhistweb/app/exceptions/NotEnoughPlayersException.java b/knockoutwhistweb/app/exceptions/NotEnoughPlayersException.java new file mode 100644 index 0000000..35c8d44 --- /dev/null +++ b/knockoutwhistweb/app/exceptions/NotEnoughPlayersException.java @@ -0,0 +1,7 @@ +package exceptions; + +public class NotEnoughPlayersException extends GameException { + public NotEnoughPlayersException(String message) { + super(message); + } +} diff --git a/knockoutwhistweb/app/logic/PodManager.scala b/knockoutwhistweb/app/logic/PodManager.scala index ebf046d..ad3e9c8 100644 --- a/knockoutwhistweb/app/logic/PodManager.scala +++ b/knockoutwhistweb/app/logic/PodManager.scala @@ -6,6 +6,7 @@ import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic import di.KnockOutWebConfigurationModule import logic.game.GameLobby import model.users.User +import util.GameUtil import javax.inject.Singleton import scala.collection.mutable @@ -27,7 +28,7 @@ class PodManager { ): GameLobby = { val gameLobby = GameLobby( logic = BaseGameLogic(injector.getInstance(classOf[Configuration])), - id = java.util.UUID.randomUUID().toString, + id = GameUtil.generateCode(), internalId = java.util.UUID.randomUUID(), name = name, maxPlayers = maxPlayers, diff --git a/knockoutwhistweb/app/logic/game/GameLobby.scala b/knockoutwhistweb/app/logic/game/GameLobby.scala index 53e5560..7dadf4a 100644 --- a/knockoutwhistweb/app/logic/game/GameLobby.scala +++ b/knockoutwhistweb/app/logic/game/GameLobby.scala @@ -2,9 +2,9 @@ package logic.game import de.knockoutwhist.cards.{Hand, Suit} import de.knockoutwhist.control.GameLogic -import de.knockoutwhist.control.GameState.Lobby +import de.knockoutwhist.control.GameState.{Lobby, MainMenu} import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.{MatchUtil, PlayerUtil} -import de.knockoutwhist.events.global.SessionClosed +import de.knockoutwhist.events.global.{GameStateChangeEvent, SessionClosed} import de.knockoutwhist.events.player.PlayerEvent import de.knockoutwhist.player.Playertype.HUMAN import de.knockoutwhist.player.{AbstractPlayer, PlayerFactory} @@ -46,9 +46,13 @@ class GameLobby private( event match { case event: PlayerEvent => users.get(event.playerId).foreach(session => session.updatePlayer(event)) + case event: GameStateChangeEvent => + if (event.oldState == MainMenu && event.newState == Lobby) { + return + } + users.values.foreach(session => session.updatePlayer(event)) case event: SessionClosed => users.values.foreach(session => session.updatePlayer(event)) - case event: SimpleEvent => users.values.foreach(session => session.updatePlayer(event)) } @@ -70,6 +74,9 @@ class GameLobby private( users.values.foreach { player => playerNamesList += PlayerFactory.createPlayer(player.name, player.id, HUMAN) } + if (playerNamesList.size < 2) { + throw new NotEnoughPlayersException("Not enough players to start the game!") + } logic.createMatch(playerNamesList.toList) logic.controlMatch() } @@ -150,13 +157,25 @@ class GameLobby private( //------------------- - private def getUserSession(userId: UUID): UserSession = { + def getUserSession(userId: UUID): UserSession = { val sessionOpt = users.get(userId) if (sessionOpt.isEmpty) { throw new NotInThisGameException("You are not in this game!") } sessionOpt.get } + + def getPlayerByUser(user: User): AbstractPlayer = { + getPlayerBySession(getUserSession(user.id)) + } + + private def getPlayerBySession(userSession: UserSession): AbstractPlayer = { + val playerOption = getMatch.totalplayers.find(_.id == userSession.id) + if (playerOption.isEmpty) { + throw new NotInThisGameException("You are not in this game!") + } + playerOption.get + } private def getPlayerInteractable(userSession: UserSession, iType: InteractionType): AbstractPlayer = { if (!Thread.holdsLock(userSession.lock)) { @@ -165,11 +184,7 @@ class GameLobby private( if (userSession.canInteract.isEmpty || userSession.canInteract.get != iType) { throw new NotInteractableException("You can't play a card!") } - val playerOption = getMatch.totalplayers.find(_.id == userSession.id) - if (playerOption.isEmpty) { - throw new NotInThisGameException("You are not in this game!") - } - playerOption.get + getPlayerBySession(userSession) } private def getHand(player: AbstractPlayer): Hand = { diff --git a/knockoutwhistweb/app/views/ingame/ingame.scala.html b/knockoutwhistweb/app/views/ingame/ingame.scala.html index 7ce7c26..cb24ac0 100644 --- a/knockoutwhistweb/app/views/ingame/ingame.scala.html +++ b/knockoutwhistweb/app/views/ingame/ingame.scala.html @@ -1,4 +1,4 @@ -@(player: de.knockoutwhist.player.AbstractPlayer, logic: de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic) +@(player: de.knockoutwhist.player.AbstractPlayer, logic: de.knockoutwhist.control.GameLogic) @main("Ingame") {