+
+ diff --git a/knockoutwhist b/knockoutwhist index 5aa1cef..a5dcf3e 160000 --- a/knockoutwhist +++ b/knockoutwhist @@ -1 +1 @@ -Subproject commit 5aa1cef35689d2df8a89e2d8864fc5fcf9c30e33 +Subproject commit a5dcf3ee904ab548479e23ca7b146df14a835b80 diff --git a/knockoutwhistweb/app/assets/stylesheets/main.less b/knockoutwhistweb/app/assets/stylesheets/main.less index 447f7c9..5179c42 100644 --- a/knockoutwhistweb/app/assets/stylesheets/main.less +++ b/knockoutwhistweb/app/assets/stylesheets/main.less @@ -22,6 +22,7 @@ 0% { transform: translateX(-100vw); } 100% { transform: translateX(0); } } + .game-field-background { background-image: @background-image; max-width: 1400px; @@ -33,10 +34,6 @@ width: 100%; height: 100vh; } -.lobby-background { - background-color: @background-color; - -} .navbar-header{ text-align:center; @@ -192,11 +189,6 @@ body { font-size: 20px; } -#trumpsuit { - display: flex; - flex-direction: row; - margin-left: 4%; -} #nextPlayers { display: flex; flex-direction: column; diff --git a/knockoutwhistweb/app/controllers/IngameController.scala b/knockoutwhistweb/app/controllers/IngameController.scala index 6f301b8..6a5ece1 100644 --- a/knockoutwhistweb/app/controllers/IngameController.scala +++ b/knockoutwhistweb/app/controllers/IngameController.scala @@ -1,23 +1,25 @@ 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.Json +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 - - -/** - * This controller creates an `Action` to handle HTTP requests to the - * application's home page. - */ +import scala.concurrent.ExecutionContext @Singleton class IngameController @Inject()( val controllerComponents: ControllerComponents, @@ -39,14 +41,12 @@ class IngameController @Inject()( case SelectTrump => Ok(views.html.ingame.selecttrump( g.getPlayerByUser(request.user), - g.logic, - gameId + g.logic )) case TieBreak => Ok(views.html.ingame.tie( g.getPlayerByUser(request.user), - g.logic, - gameId + g.logic )) case _ => InternalServerError(s"Invalid game state for in-game view. GameId: $gameId" + s" State: ${g.logic.getCurrentState}") @@ -67,70 +67,30 @@ class IngameController @Inject()( } } if (result.isSuccess) { - Ok(Json.obj( - "status" -> "success", - "redirectUrl" -> routes.IngameController.game(gameId).url - )) + Redirect(routes.IngameController.game(gameId)) } else { val throwable = result.failed.get throwable match { case _: NotInThisGameException => - BadRequest(Json.obj( - "status" -> "failure", - "errorMessage" -> throwable.getMessage - )) + BadRequest(throwable.getMessage) case _: NotHostException => - Forbidden(Json.obj( - "status" -> "failure", - "errorMessage" -> throwable.getMessage - )) + Forbidden(throwable.getMessage) case _: NotEnoughPlayersException => - BadRequest(Json.obj( - "status" -> "failure", - "errorMessage" -> throwable.getMessage - )) + BadRequest(throwable.getMessage) case _ => - InternalServerError(Json.obj( - "status" -> "failure", - "errorMessage" -> throwable.getMessage - )) + InternalServerError(throwable.getMessage) } } } - def kickPlayer(gameId: String, playerToKick: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => + def kickPlayer(gameId: String, playerToKick: UUID): 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." - )) - } + game.get.leaveGame(playerToKick) + Redirect(routes.IngameController.game(gameId)) } 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." - )) - } + game.get.leaveGame(request.user.id) + Redirect(routes.MainMenuController.mainMenu()) } def joinGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => val game = podManager.getGame(gameId) @@ -162,10 +122,7 @@ class IngameController @Inject()( 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] - } + val cardIdOpt = request.body.asFormUrlEncoded.flatMap(_.get("cardId").flatMap(_.headOption)) cardIdOpt match { case Some(cardId) => var optSession: Option[UserSession] = None @@ -177,51 +134,27 @@ class IngameController @Inject()( } optSession.foreach(_.lock.unlock()) if (result.isSuccess) { - Ok(Json.obj( - "status" -> "success", - "redirectUrl" -> routes.IngameController.game(gameId).url - )) + NoContent } else { val throwable = result.failed.get throwable match { case _: CantPlayCardException => - BadRequest(Json.obj( - "status" -> "failure", - "errorMessage" -> throwable.getMessage - )) + BadRequest(throwable.getMessage) case _: NotInThisGameException => - BadRequest(Json.obj( - "status" -> "failure", - "errorMessage" -> throwable.getMessage - )) + BadRequest(throwable.getMessage) case _: IllegalArgumentException => - BadRequest(Json.obj( - "status" -> "failure", - "errorMessage" -> throwable.getMessage - )) + BadRequest(throwable.getMessage) case _: IllegalStateException => - BadRequest(Json.obj( - "status" -> "failure", - "errorMessage" -> throwable.getMessage - )) + BadRequest(throwable.getMessage) case _ => - InternalServerError(Json.obj( - "status" -> "failure", - "errorMessage" -> throwable.getMessage - )) + InternalServerError(throwable.getMessage) } } case None => - BadRequest(Json.obj( - "status" -> "failure", - "errorMessage" -> "cardId Parameter is missing" - )) + BadRequest("cardId parameter is missing") } case None => - NotFound(Json.obj( - "status" -> "failure", - "errorMessage" -> "Game not found" - )) + NotFound("Game not found") } } } diff --git a/knockoutwhistweb/app/controllers/JavaScriptRoutingController.scala b/knockoutwhistweb/app/controllers/JavaScriptRoutingController.scala index 2cd5f06..48c2f9f 100644 --- a/knockoutwhistweb/app/controllers/JavaScriptRoutingController.scala +++ b/knockoutwhistweb/app/controllers/JavaScriptRoutingController.scala @@ -19,7 +19,8 @@ class JavaScriptRoutingController @Inject()( routes.javascript.IngameController.startGame, routes.javascript.IngameController.kickPlayer, routes.javascript.IngameController.leaveGame, - routes.javascript.IngameController.playCard + routes.javascript.IngameController.playCard, + routes.javascript.IngameController.polling ) ).as("text/javascript") } diff --git a/knockoutwhistweb/app/logic/game/GameLobby.scala b/knockoutwhistweb/app/logic/game/GameLobby.scala index 0e8ac9f..cd28c35 100644 --- a/knockoutwhistweb/app/logic/game/GameLobby.scala +++ b/knockoutwhistweb/app/logic/game/GameLobby.scala @@ -2,21 +2,23 @@ package logic.game import de.knockoutwhist.cards.{Hand, Suit} import de.knockoutwhist.control.GameLogic -import de.knockoutwhist.control.GameState.{Lobby, MainMenu} +import de.knockoutwhist.control.GameState.{InGame, Lobby, MainMenu} import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.{MatchUtil, PlayerUtil} -import de.knockoutwhist.events.global.{GameStateChangeEvent, SessionClosed} +import de.knockoutwhist.events.global.{CardPlayedEvent, GameStateChangeEvent, SessionClosed} import de.knockoutwhist.events.player.PlayerEvent import de.knockoutwhist.player.Playertype.HUMAN import de.knockoutwhist.player.{AbstractPlayer, PlayerFactory} import de.knockoutwhist.rounds.{Match, Round, Trick} import de.knockoutwhist.utils.events.{EventListener, SimpleEvent} import exceptions.* +import logic.game.PollingEvents.{CardPlayed, GameStarted} import model.sessions.{InteractionType, UserSession} import model.users.User import java.util.UUID import scala.collection.mutable import scala.collection.mutable.ListBuffer +import scala.concurrent.{Promise => ScalaPromise} class GameLobby private( val logic: GameLogic, @@ -29,7 +31,19 @@ class GameLobby private( logic.createSession() private val users: mutable.Map[UUID, UserSession] = mutable.Map() - + private var pollingState: mutable.Queue[PollingEvents] = mutable.Queue() + private val waitingPromises: mutable.Map[UUID, ScalaPromise[PollingEvents]] = mutable.Map() + + def registerWaiter(playerId: UUID): ScalaPromise[PollingEvents] = { + val promise = ScalaPromise[PollingEvents]() + waitingPromises.put(playerId, promise) + promise + } + + def removeWaiter(playerId: UUID): Unit = { + waitingPromises.remove(playerId) + } + def addUser(user: User): UserSession = { if (users.size >= maxPlayers) throw new GameFullException("The game is full!") if (users.contains(user.id)) throw new IllegalArgumentException("User is already in the game!") @@ -44,12 +58,29 @@ class GameLobby private( override def listen(event: SimpleEvent): Unit = { event match { + case event: CardPlayedEvent => + val newEvent = PollingEvents.CardPlayed + if (waitingPromises.nonEmpty) { + waitingPromises.values.foreach(_.success(newEvent)) + waitingPromises.clear() + } else { + pollingState.enqueue(newEvent) + } case event: PlayerEvent => users.get(event.playerId).foreach(session => session.updatePlayer(event)) case event: GameStateChangeEvent => if (event.oldState == MainMenu && event.newState == Lobby) { return } + if (event.oldState == Lobby && event.newState == InGame) { + val newEvent = PollingEvents.GameStarted + if (waitingPromises.nonEmpty) { + waitingPromises.values.foreach(_.success(newEvent)) + waitingPromises.clear() + } else { + pollingState.enqueue(newEvent) + } + } users.values.foreach(session => session.updatePlayer(event)) case event: SessionClosed => users.values.foreach(session => session.updatePlayer(event)) @@ -186,7 +217,9 @@ class GameLobby private( def getLogic: GameLogic = { logic } - + def getPollingState: mutable.Queue[PollingEvents] = { + pollingState + } private def getPlayerBySession(userSession: UserSession): AbstractPlayer = { val playerOption = getMatch.totalplayers.find(_.id == userSession.id) if (playerOption.isEmpty) { diff --git a/knockoutwhistweb/app/logic/game/PollingEvents.scala b/knockoutwhistweb/app/logic/game/PollingEvents.scala new file mode 100644 index 0000000..933cf82 --- /dev/null +++ b/knockoutwhistweb/app/logic/game/PollingEvents.scala @@ -0,0 +1,6 @@ +package logic.game + +enum PollingEvents { + case CardPlayed + case GameStarted +} \ No newline at end of file diff --git a/knockoutwhistweb/app/views/ingame/ingame.scala.html b/knockoutwhistweb/app/views/ingame/ingame.scala.html index 271fc28..50f45f6 100644 --- a/knockoutwhistweb/app/views/ingame/ingame.scala.html +++ b/knockoutwhistweb/app/views/ingame/ingame.scala.html @@ -10,18 +10,18 @@
@gamelobby.getLogic.getCurrentPlayer.get.name
+@gamelobby.getLogic.getCurrentPlayer.get.name
@if(!TrickUtil.isOver(gamelobby.getLogic.getCurrentMatch.get, gamelobby.getLogic.getPlayerQueue.get)) {@nextplayer
+@nextplayer
} }@gamelobby.getLogic.getCurrentRound.get.trumpSuit
+@gamelobby.getLogic.getCurrentRound.get.trumpSuit
+
+