feat(ci): Polling

Added polling for when the game starts and a card gets played
This commit is contained in:
LQ63
2025-11-12 11:46:21 +01:00
parent 6d958cdd9e
commit 5c8fd8510e
9 changed files with 342 additions and 34 deletions

View File

@@ -1,30 +1,121 @@
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,
val authAction: AuthAction,
val podManager: PodManager
) extends BaseController {
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 {

View File

@@ -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")
}