feat(game)!: Add winner display and return to lobby functionality
This commit is contained in:
Submodule knockoutwhist updated: a5dcf3ee90...372f20ca6c
@@ -49,6 +49,16 @@
|
||||
box-shadow: 3px 3px 3px @highlightcolor;
|
||||
}
|
||||
|
||||
.inactive::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0; /* cover the whole container */
|
||||
background: rgba(0, 0, 0, 0.50);
|
||||
z-index: 10;
|
||||
border-radius: 6px;
|
||||
pointer-events: none; /* user can't click through overlay */
|
||||
}
|
||||
|
||||
.bottom-div {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
package controllers
|
||||
|
||||
import auth.{AuthAction, AuthenticatedRequest}
|
||||
import de.knockoutwhist.control.GameState.{InGame, Lobby, SelectTrump, TieBreak}
|
||||
import de.knockoutwhist.control.GameState.{FinishedMatch, InGame, Lobby, SelectTrump, TieBreak}
|
||||
import exceptions.*
|
||||
import logic.PodManager
|
||||
import logic.game.GameLobby
|
||||
import model.sessions.UserSession
|
||||
import model.users.User
|
||||
import play.api.*
|
||||
import play.api.libs.json.{JsValue, Json}
|
||||
import play.api.mvc.*
|
||||
import play.twirl.api.Html
|
||||
|
||||
import java.util.UUID
|
||||
import javax.inject.*
|
||||
@@ -22,29 +24,47 @@ class IngameController @Inject() (
|
||||
val authAction: AuthAction,
|
||||
implicit val ec: ExecutionContext
|
||||
) extends AbstractController(cc) {
|
||||
|
||||
def returnInnerHTML(gameLobby: GameLobby, user: User): Html = {
|
||||
gameLobby.logic.getCurrentState match {
|
||||
case Lobby => views.html.lobby.lobby(Some(user), gameLobby)
|
||||
case InGame =>
|
||||
views.html.ingame.ingame(
|
||||
gameLobby.getPlayerByUser(user),
|
||||
gameLobby
|
||||
)
|
||||
case SelectTrump =>
|
||||
views.html.ingame.selecttrump(
|
||||
gameLobby.getPlayerByUser(user),
|
||||
gameLobby
|
||||
)
|
||||
case TieBreak =>
|
||||
views.html.ingame.tie(
|
||||
gameLobby.getPlayerByUser(user),
|
||||
gameLobby
|
||||
)
|
||||
case FinishedMatch =>
|
||||
views.html.ingame.finishedMatch(
|
||||
Some(user),
|
||||
gameLobby
|
||||
)
|
||||
case _ =>
|
||||
throw new IllegalStateException(s"Invalid game state for in-game view. GameId: ${gameLobby.id}" + s" State: ${gameLobby.logic.getCurrentState}")
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
))
|
||||
case TieBreak =>
|
||||
Ok(views.html.ingame.tie(
|
||||
g.getPlayerByUser(request.user),
|
||||
g
|
||||
))
|
||||
case _ =>
|
||||
InternalServerError(s"Invalid game state for in-game view. GameId: $gameId" + s" State: ${g.logic.getCurrentState}")
|
||||
val results = Try {
|
||||
returnInnerHTML(g, request.user)
|
||||
|
||||
}
|
||||
if (results.isSuccess) {
|
||||
Ok(views.html.main("In-Game - Knockout Whist")(results.get))
|
||||
} else {
|
||||
InternalServerError(results.failed.get.getMessage)
|
||||
}
|
||||
case None =>
|
||||
NotFound("Game not found")
|
||||
@@ -126,32 +146,7 @@ class IngameController @Inject() (
|
||||
))
|
||||
}
|
||||
}
|
||||
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 {
|
||||
@@ -255,15 +250,30 @@ class IngameController @Inject() (
|
||||
val throwable = result.failed.get
|
||||
throwable match {
|
||||
case _: CantPlayCardException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
BadRequest(Json.obj(
|
||||
"status" -> "failure",
|
||||
"errorMessage" -> throwable.getMessage
|
||||
))
|
||||
case _: NotInThisGameException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
BadRequest(Json.obj(
|
||||
"status" -> "failure",
|
||||
"errorMessage" -> throwable.getMessage
|
||||
))
|
||||
case _: IllegalArgumentException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
BadRequest(Json.obj(
|
||||
"status" -> "failure",
|
||||
"errorMessage" -> throwable.getMessage
|
||||
))
|
||||
case _: IllegalStateException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
BadRequest(Json.obj(
|
||||
"status" -> "failure",
|
||||
"errorMessage" -> throwable.getMessage
|
||||
))
|
||||
case _ =>
|
||||
InternalServerError(throwable.getMessage)
|
||||
InternalServerError(Json.obj(
|
||||
"status" -> "failure",
|
||||
"errorMessage" -> throwable.getMessage
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,7 +286,10 @@ class IngameController @Inject() (
|
||||
val game = podManager.getGame(gameId)
|
||||
game match {
|
||||
case Some(g) =>
|
||||
val trumpOpt = request.body.asFormUrlEncoded.flatMap(_.get("trump").flatMap(_.headOption))
|
||||
val jsonBody = request.body.asJson
|
||||
val trumpOpt: Option[String] = jsonBody.flatMap { jsValue =>
|
||||
(jsValue \ "trump").asOpt[String]
|
||||
}
|
||||
trumpOpt match {
|
||||
case Some(trump) =>
|
||||
var optSession: Option[UserSession] = None
|
||||
@@ -293,13 +306,25 @@ class IngameController @Inject() (
|
||||
val throwable = result.failed.get
|
||||
throwable match {
|
||||
case _: IllegalArgumentException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
BadRequest(Json.obj(
|
||||
"status" -> "failure",
|
||||
"errorMessage" -> throwable.getMessage
|
||||
))
|
||||
case _: NotInThisGameException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
BadRequest(Json.obj(
|
||||
"status" -> "failure",
|
||||
"errorMessage" -> throwable.getMessage
|
||||
))
|
||||
case _: IllegalStateException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
BadRequest(Json.obj(
|
||||
"status" -> "failure",
|
||||
"errorMessage" -> throwable.getMessage
|
||||
))
|
||||
case _ =>
|
||||
InternalServerError(throwable.getMessage)
|
||||
InternalServerError(Json.obj(
|
||||
"status" -> "failure",
|
||||
"errorMessage" -> throwable.getMessage
|
||||
))
|
||||
}
|
||||
}
|
||||
case None =>
|
||||
@@ -313,7 +338,10 @@ class IngameController @Inject() (
|
||||
val game = podManager.getGame(gameId)
|
||||
game match {
|
||||
case Some(g) =>
|
||||
val tieOpt = request.body.asFormUrlEncoded.flatMap(_.get("tie").flatMap(_.headOption))
|
||||
val jsonBody = request.body.asJson
|
||||
val tieOpt: Option[String] = jsonBody.flatMap { jsValue =>
|
||||
(jsValue \ "tie").asOpt[String]
|
||||
}
|
||||
tieOpt match {
|
||||
case Some(tie) =>
|
||||
var optSession: Option[UserSession] = None
|
||||
@@ -330,13 +358,25 @@ class IngameController @Inject() (
|
||||
val throwable = result.failed.get
|
||||
throwable match {
|
||||
case _: IllegalArgumentException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
BadRequest(Json.obj(
|
||||
"status" -> "failure",
|
||||
"errorMessage" -> throwable.getMessage
|
||||
))
|
||||
case _: NotInThisGameException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
BadRequest(Json.obj(
|
||||
"status" -> "failure",
|
||||
"errorMessage" -> throwable.getMessage
|
||||
))
|
||||
case _: IllegalStateException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
BadRequest(Json.obj(
|
||||
"status" -> "failure",
|
||||
"errorMessage" -> throwable.getMessage
|
||||
))
|
||||
case _ =>
|
||||
InternalServerError(throwable.getMessage)
|
||||
InternalServerError(Json.obj(
|
||||
"status" -> "failure",
|
||||
"errorMessage" -> throwable.getMessage
|
||||
))
|
||||
}
|
||||
}
|
||||
case None =>
|
||||
|
||||
@@ -12,16 +12,22 @@ class JavaScriptRoutingController @Inject()(
|
||||
val authAction: AuthAction,
|
||||
val podManager: PodManager
|
||||
) extends BaseController {
|
||||
def javascriptRoutes(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||
def javascriptRoutes(): Action[AnyContent] =
|
||||
Action { implicit request =>
|
||||
Ok(
|
||||
JavaScriptReverseRouter("jsRoutes")(
|
||||
routes.javascript.MainMenuController.createGame,
|
||||
routes.javascript.IngameController.startGame,
|
||||
routes.javascript.MainMenuController.joinGame,
|
||||
routes.javascript.IngameController.kickPlayer,
|
||||
routes.javascript.IngameController.leaveGame,
|
||||
routes.javascript.IngameController.playCard,
|
||||
routes.javascript.IngameController.playDogCard,
|
||||
routes.javascript.PollingController.polling
|
||||
routes.javascript.IngameController.playTrump,
|
||||
routes.javascript.IngameController.playTie,
|
||||
routes.javascript.IngameController.returnToLobby,
|
||||
routes.javascript.PollingController.polling,
|
||||
routes.javascript.UserController.login_Post
|
||||
)
|
||||
).as("text/javascript")
|
||||
}
|
||||
|
||||
@@ -17,12 +17,13 @@ import javax.inject.*
|
||||
class MainMenuController @Inject()(
|
||||
val controllerComponents: ControllerComponents,
|
||||
val authAction: AuthAction,
|
||||
val podManager: PodManager
|
||||
val podManager: PodManager,
|
||||
val ingameController: IngameController
|
||||
) extends BaseController {
|
||||
|
||||
// Pass the request-handling function directly to authAction (no nested Action)
|
||||
def mainMenu(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||
Ok(views.html.mainmenu.creategame(Some(request.user)))
|
||||
Ok(views.html.main("KnockOutWhist")(views.html.mainmenu.creategame(Some(request.user))))
|
||||
}
|
||||
|
||||
def index(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||
@@ -45,7 +46,8 @@ class MainMenuController @Inject()(
|
||||
)
|
||||
Ok(Json.obj(
|
||||
"status" -> "success",
|
||||
"redirectUrl" -> routes.IngameController.game(gameLobby.id).url
|
||||
"redirectUrl" -> routes.IngameController.game(gameLobby.id).url,
|
||||
"content" -> ingameController.returnInnerHTML(gameLobby, request.user).toString
|
||||
))
|
||||
} else {
|
||||
BadRequest(Json.obj(
|
||||
@@ -57,18 +59,31 @@ class MainMenuController @Inject()(
|
||||
}
|
||||
|
||||
def joinGame(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||
val postData = request.body.asFormUrlEncoded
|
||||
if (postData.isDefined) {
|
||||
val gameId = postData.get.get("gameId").flatMap(_.headOption).getOrElse("")
|
||||
val game = podManager.getGame(gameId)
|
||||
val jsonBody = request.body.asJson
|
||||
val gameId: Option[String] = jsonBody.flatMap { jsValue =>
|
||||
(jsValue \ "gameId").asOpt[String]
|
||||
}
|
||||
if (gameId.isDefined) {
|
||||
val game = podManager.getGame(gameId.get)
|
||||
game match {
|
||||
case Some(g) =>
|
||||
Redirect(routes.IngameController.joinGame(gameId))
|
||||
g.addUser(request.user)
|
||||
Ok(Json.obj(
|
||||
"status" -> "success",
|
||||
"redirectUrl" -> routes.IngameController.game(g.id).url,
|
||||
"content" -> ingameController.returnInnerHTML(g, request.user).toString
|
||||
))
|
||||
case None =>
|
||||
NotFound("Game not found")
|
||||
NotFound(Json.obj(
|
||||
"status" -> "failure",
|
||||
"errorMessage" -> "No Game found"
|
||||
))
|
||||
}
|
||||
} else {
|
||||
BadRequest("Invalid form submission")
|
||||
BadRequest(Json.obj(
|
||||
"status" -> "failure",
|
||||
"errorMessage" -> "Invalid form submission"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ 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}
|
||||
import logic.game.PollingEvents.{CardPlayed, LobbyCreation, LobbyUpdate, NewRound, NewTrick, ReloadEvent}
|
||||
import model.sessions.UserSession
|
||||
import model.users.User
|
||||
import play.api.libs.json.{JsArray, JsValue, Json}
|
||||
@@ -26,6 +26,7 @@ class PollingController @Inject() (
|
||||
val cc: ControllerComponents,
|
||||
val podManager: PodManager,
|
||||
val authAction: AuthAction,
|
||||
val ingameController: IngameController,
|
||||
implicit val ec: ExecutionContext
|
||||
) extends AbstractController(cc) {
|
||||
|
||||
@@ -64,7 +65,8 @@ class PollingController @Inject() (
|
||||
"trickCards" -> trickCardsJson,
|
||||
"scoreTable" -> scoreTableJson,
|
||||
"firstCardId" -> firstCardId,
|
||||
"nextPlayer" -> nextPlayer
|
||||
"nextPlayer" -> nextPlayer,
|
||||
"yourTurn" -> (game.logic.getCurrentPlayer.get == player)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -88,6 +90,11 @@ class PollingController @Inject() (
|
||||
val hand = player.currentHand()
|
||||
val jsonResponse = buildCardPlayResponse(game, hand, player, true)
|
||||
Ok(jsonResponse)
|
||||
case NewTrick =>
|
||||
val player = game.getPlayerByUser(userSession.user)
|
||||
val hand = player.currentHand()
|
||||
val jsonResponse = buildCardPlayResponse(game, hand, player, false)
|
||||
Ok(jsonResponse)
|
||||
case CardPlayed =>
|
||||
val player = game.getPlayerByUser(userSession.user)
|
||||
val hand = player.currentHand()
|
||||
@@ -98,7 +105,8 @@ class PollingController @Inject() (
|
||||
case ReloadEvent =>
|
||||
val jsonResponse = Json.obj(
|
||||
"status" -> "reloadEvent",
|
||||
"redirectUrl" -> routes.IngameController.game(game.id).url
|
||||
"redirectUrl" -> routes.IngameController.game(game.id).url,
|
||||
"content" -> ingameController.returnInnerHTML(game, userSession.user).toString
|
||||
)
|
||||
Ok(jsonResponse)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package controllers
|
||||
import auth.{AuthAction, AuthenticatedRequest}
|
||||
import logic.user.{SessionManager, UserManager}
|
||||
import play.api.*
|
||||
import play.api.libs.json.Json
|
||||
import play.api.mvc.*
|
||||
|
||||
import javax.inject.*
|
||||
@@ -28,28 +29,35 @@ class UserController @Inject()(
|
||||
if (possibleUser.isDefined) {
|
||||
Redirect(routes.MainMenuController.mainMenu())
|
||||
} else {
|
||||
Ok(views.html.login.login())
|
||||
Ok(views.html.main("Login")(views.html.login.login()))
|
||||
}
|
||||
} else {
|
||||
Ok(views.html.login.login())
|
||||
Ok(views.html.main("Login")(views.html.login.login()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def login_Post(): Action[AnyContent] = {
|
||||
Action { implicit request =>
|
||||
val postData = request.body.asFormUrlEncoded
|
||||
if (postData.isDefined) {
|
||||
val jsonBody = request.body.asJson
|
||||
val username: Option[String] = jsonBody.flatMap { jsValue =>
|
||||
(jsValue \ "username").asOpt[String]
|
||||
}
|
||||
val password: Option[String] = jsonBody.flatMap { jsValue =>
|
||||
(jsValue \ "password").asOpt[String]
|
||||
}
|
||||
if (username.isDefined && password.isDefined) {
|
||||
// Extract username and password from form data
|
||||
val username = postData.get.get("username").flatMap(_.headOption).getOrElse("")
|
||||
val password = postData.get.get("password").flatMap(_.headOption).getOrElse("")
|
||||
val possibleUser = userManager.authenticate(username, password)
|
||||
val possibleUser = userManager.authenticate(username.get, password.get)
|
||||
if (possibleUser.isDefined) {
|
||||
Redirect(routes.MainMenuController.mainMenu()).withCookies(
|
||||
Ok(Json.obj(
|
||||
"status" -> "success",
|
||||
"redirectUrl" -> routes.MainMenuController.mainMenu().url,
|
||||
"content" -> views.html.mainmenu.creategame(possibleUser).toString
|
||||
)).withCookies(
|
||||
Cookie("sessionId", sessionManager.createSession(possibleUser.get))
|
||||
)
|
||||
} else {
|
||||
println("Failed login attempt for user: " + username)
|
||||
Unauthorized("Invalid username or password")
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -4,14 +4,15 @@ import de.knockoutwhist.cards.{Hand, Suit}
|
||||
import de.knockoutwhist.control.GameLogic
|
||||
import de.knockoutwhist.control.GameState.{InGame, Lobby, MainMenu}
|
||||
import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.{MatchUtil, PlayerUtil}
|
||||
import de.knockoutwhist.events.global.{CardPlayedEvent, GameStateChangeEvent, SessionClosed}
|
||||
import de.knockoutwhist.events.global.tie.TieTurnEvent
|
||||
import de.knockoutwhist.events.global.{CardPlayedEvent, GameStateChangeEvent, NewTrickEvent, SessionClosed}
|
||||
import de.knockoutwhist.events.player.{PlayerEvent, ReceivedHandEvent}
|
||||
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, LobbyCreation, LobbyUpdate, NewRound, ReloadEvent}
|
||||
import logic.game.PollingEvents.{CardPlayed, LobbyCreation, LobbyUpdate, NewRound, NewTrick, ReloadEvent}
|
||||
import model.sessions.{InteractionType, UserSession}
|
||||
import model.users.User
|
||||
|
||||
@@ -82,8 +83,13 @@ class GameLobby private(
|
||||
users.get(event.playerId).foreach(session => session.updatePlayer(event))
|
||||
case event: CardPlayedEvent =>
|
||||
addToQueue(CardPlayed)
|
||||
case event: TieTurnEvent =>
|
||||
addToQueue(ReloadEvent)
|
||||
users.get(event.player.id).foreach(session => session.updatePlayer(event))
|
||||
case event: PlayerEvent =>
|
||||
users.get(event.playerId).foreach(session => session.updatePlayer(event))
|
||||
case event: NewTrickEvent =>
|
||||
addToQueue(NewTrick)
|
||||
case event: GameStateChangeEvent =>
|
||||
if (event.oldState == MainMenu && event.newState == Lobby) {
|
||||
return
|
||||
@@ -197,6 +203,7 @@ class GameLobby private(
|
||||
throw new CantPlayCardException("You can't skip this round!")
|
||||
}
|
||||
logic.playerInputLogic.receivedDog(None)
|
||||
return
|
||||
}
|
||||
val hand = getHand(player)
|
||||
val card = hand.cards(cardIndex)
|
||||
@@ -229,7 +236,7 @@ class GameLobby private(
|
||||
}
|
||||
|
||||
def returnToLobby(userSession: UserSession): Unit = {
|
||||
if (users.contains(userSession.id)) {
|
||||
if (!users.contains(userSession.id)) {
|
||||
throw new NotInThisGameException("You are not in this game!")
|
||||
}
|
||||
val session = users(userSession.id)
|
||||
|
||||
@@ -3,6 +3,7 @@ package logic.game
|
||||
enum PollingEvents {
|
||||
case CardPlayed
|
||||
case NewRound
|
||||
case NewTrick
|
||||
case ReloadEvent
|
||||
case LobbyUpdate
|
||||
case LobbyCreation
|
||||
|
||||
37
knockoutwhistweb/app/views/ingame/finishedMatch.scala.html
Normal file
37
knockoutwhistweb/app/views/ingame/finishedMatch.scala.html
Normal file
@@ -0,0 +1,37 @@
|
||||
@(user: Option[model.users.User], gamelobby: logic.game.GameLobby)
|
||||
|
||||
<main class="lobby-background vh-100" id="lobbybackground">
|
||||
<div class="container d-flex flex-column" style="height: calc(100vh - 1rem);">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="p-3 text-center fs-4">Winner: @gamelobby.getLogic.getWinner</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center align-items-center flex-grow-1">
|
||||
@if((gamelobby.getUserSession(user.get.id).host)) {
|
||||
<div class="col-12 text-center mb-5">
|
||||
<div class="btn btn-success" onclick="backToLobby('@gamelobby.id')">Return to lobby</div>
|
||||
</div>
|
||||
} else {
|
||||
<div class="col-12 text-center mt-3">
|
||||
<div class="spinner-border mt-1" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
function waitForFunction(name, checkInterval = 100) {
|
||||
return new Promise(resolve => {
|
||||
const timer = setInterval(() => {
|
||||
if (typeof window[name] === "function") {
|
||||
clearInterval(timer);
|
||||
resolve(window[name]);
|
||||
}
|
||||
}, checkInterval);
|
||||
});
|
||||
}
|
||||
waitForFunction("pollForUpdates").then(fn => fn('@gamelobby.id'));
|
||||
</script>
|
||||
@@ -1,8 +1,8 @@
|
||||
@import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.TrickUtil
|
||||
@import de.knockoutwhist.utils.Implicits.*
|
||||
|
||||
@(player: de.knockoutwhist.player.AbstractPlayer, gamelobby: logic.game.GameLobby)
|
||||
|
||||
@main("Ingame") {
|
||||
<div class="lobby-background vh-100">
|
||||
<main class="game-field-background vh-100 ingame-side-shadow">
|
||||
<div class="py-5 container-xxl">
|
||||
@@ -72,15 +72,17 @@
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center g-2 mt-4 bottom-div" style="backdrop-filter: blur(4px); margin-left: 0; margin-right: 0;">
|
||||
<div class="row justify-content-center ingame-cards-slide" id="card-slide">
|
||||
<div class="row justify-content-center ingame-cards-slide @{
|
||||
!gamelobby.logic.getCurrentPlayer.contains(player) ? "inactive" |: ""
|
||||
}" id="card-slide">
|
||||
@for(i <- player.currentHand().get.cards.indices) {
|
||||
<div class="col-auto handcard" style="border-radius: 6px">
|
||||
<div class="btn btn-outline-light p-0 border-0 shadow-none" data-card-id="@i" style="border-radius: 6px" onclick="handlePlayCard(this, '@gamelobby.id', @player.isInDogLife)">
|
||||
<div class="btn btn-outline-light p-0 border-0 shadow-none" data-card-id="@i" style="border-radius: 6px" onclick="handlePlayCard(this, '@gamelobby.id', '@player.isInDogLife')">
|
||||
@util.WebUIUtils.cardtoImage(player.currentHand().get.cards(i)) width="120px" style="border-radius: 6px"/>
|
||||
</div>
|
||||
@if(player.isInDogLife) {
|
||||
<div class="mt-2">
|
||||
<button class="btn btn-danger" onclick="handleSkipDogLife('@gamelobby.id')">Skip Dog Life</button>
|
||||
<button class="btn btn-danger" onclick="handleSkipDogLife(this, '@gamelobby.id')">Skip Dog Life</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -91,8 +93,15 @@
|
||||
</main>
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
pollForUpdates('@gamelobby.id');
|
||||
function waitForFunction(name, checkInterval = 100) {
|
||||
return new Promise(resolve => {
|
||||
const timer = setInterval(() => {
|
||||
if (typeof window[name] === "function") {
|
||||
clearInterval(timer);
|
||||
resolve(window[name]);
|
||||
}
|
||||
}, checkInterval);
|
||||
});
|
||||
}
|
||||
waitForFunction("pollForUpdates").then(fn => fn('@gamelobby.id'));
|
||||
</script>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
@(player: de.knockoutwhist.player.AbstractPlayer, gamelobby: logic.game.GameLobby)
|
||||
|
||||
@main("Selecting Trumpsuit...") {
|
||||
<div id="selecttrumpsuit" class="game-field game-field-background">
|
||||
<div class="ingame-stage blur-sides">
|
||||
<div class="container py-4">
|
||||
@@ -18,36 +17,24 @@
|
||||
|
||||
<div class="row justify-content-center col-auto mb-5">
|
||||
<div class="col-auto handcard">
|
||||
<form action="@routes.IngameController.playTrump(gamelobby.id)" method="post">
|
||||
<input type="hidden" name="cardId" value="0" />
|
||||
<button type="submit" class="btn btn-outline-light p-0 border-0 shadow-none" name="trump" value="0" style="border-radius: 6px">
|
||||
<div class="btn btn-outline-light p-0 border-0 shadow-none" data-trump="0" style="border-radius: 6px" onclick="handleTrumpSelection(this, '@gamelobby.id')">
|
||||
@util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Spades)) width="120px" style="border-radius: 6px"/>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto handcard">
|
||||
<form action="@routes.IngameController.playTrump(gamelobby.id)" method="post">
|
||||
<input type="hidden" name="cardId" value="1" />
|
||||
<button type="submit" class="btn btn-outline-light p-0 border-0 shadow-none" name="trump" value="1" style="border-radius: 6px">
|
||||
<div class="btn btn-outline-light p-0 border-0 shadow-none" data-trump="1" style="border-radius: 6px" onclick="handleTrumpSelection(this, '@gamelobby.id')">
|
||||
@util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Hearts)) width="120px" style="border-radius: 6px"/>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto handcard">
|
||||
<form action="@routes.IngameController.playTrump(gamelobby.id)" method="post">
|
||||
<input type="hidden" name="cardId" value="2" />
|
||||
<button type="submit" class="btn btn-outline-light p-0 border-0 shadow-none" name="trump" value="2" style="border-radius: 6px">
|
||||
<div class="btn btn-outline-light p-0 border-0 shadow-none" data-trump="2" style="border-radius: 6px" onclick="handleTrumpSelection(this, '@gamelobby.id')">
|
||||
@util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Diamonds)) width="120px" style="border-radius: 6px"/>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto handcard">
|
||||
<form action="@routes.IngameController.playTrump(gamelobby.id)" method="post">
|
||||
<input type="hidden" name="cardId" value="3" />
|
||||
<button type="submit" class="btn btn-outline-light p-0 border-0 shadow-none" name="trump" value="3" style="border-radius: 6px">
|
||||
<div class="btn btn-outline-light p-0 border-0 shadow-none" data-trump="3" style="border-radius: 6px" onclick="handleTrumpSelection(this, '@gamelobby.id')">
|
||||
@util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Clubs)) width="120px" style="border-radius: 6px"/>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center ingame-cards-slide" id="card-slide">
|
||||
@@ -69,4 +56,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<script>
|
||||
function waitForFunction(name, checkInterval = 100) {
|
||||
return new Promise(resolve => {
|
||||
const timer = setInterval(() => {
|
||||
if (typeof window[name] === "function") {
|
||||
clearInterval(timer);
|
||||
resolve(window[name]);
|
||||
}
|
||||
}, checkInterval);
|
||||
});
|
||||
}
|
||||
waitForFunction("pollForUpdates").then(fn => fn('@gamelobby.id'));
|
||||
</script>
|
||||
@@ -1,6 +1,4 @@
|
||||
@(player: de.knockoutwhist.player.AbstractPlayer, gamelobby: logic.game.GameLobby)
|
||||
|
||||
@main("Tie") {
|
||||
<div id="tie" class="game-field game-field-background">
|
||||
<div class="ingame-stage blur-sides">
|
||||
<div class="container py-4">
|
||||
@@ -28,7 +26,7 @@
|
||||
Pick a number between 1 and @{maxNum + 1}. The resulting card will be your card for the cut.
|
||||
</div>
|
||||
|
||||
<form class="row g-2 align-items-center" method="post" action="@routes.IngameController.playTie(gamelobby.id)">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-auto">
|
||||
<label for="tieNumber" class="col-form-label">Your number</label>
|
||||
</div>
|
||||
@@ -36,9 +34,9 @@
|
||||
<input type="number" id="tieNumber" class="form-control" name="tie" min="1" max="@{maxNum + 1}" placeholder="1" required>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="submit" class="btn btn-primary">Confirm</button>
|
||||
<button onclick="selectTie('@gamelobby.id')" class="btn btn-primary">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<h6 class="mt-4 mb-3">Currently Picked Cards</h6>
|
||||
|
||||
<div id="cardsplayed" class="row g-3 justify-content-center">
|
||||
@@ -104,4 +102,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<script>
|
||||
function waitForFunction(name, checkInterval = 100) {
|
||||
return new Promise(resolve => {
|
||||
const timer = setInterval(() => {
|
||||
if (typeof window[name] === "function") {
|
||||
clearInterval(timer);
|
||||
resolve(window[name]);
|
||||
}
|
||||
}, checkInterval);
|
||||
});
|
||||
}
|
||||
waitForFunction("pollForUpdates").then(fn => fn('@gamelobby.id'));
|
||||
</script>
|
||||
@@ -1,6 +1,5 @@
|
||||
@(user: Option[model.users.User], gamelobby: logic.game.GameLobby)
|
||||
|
||||
@main("Lobby") {
|
||||
<main class="lobby-background vh-100" id="lobbybackground">
|
||||
<div class="container d-flex flex-column" style="height: calc(100vh - 1rem);">
|
||||
<div class="row">
|
||||
@@ -68,8 +67,15 @@
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
pollForUpdates('@gamelobby.id');
|
||||
function waitForFunction(name, checkInterval = 100) {
|
||||
return new Promise(resolve => {
|
||||
const timer = setInterval(() => {
|
||||
if (typeof window[name] === "function") {
|
||||
clearInterval(timer);
|
||||
resolve(window[name]);
|
||||
}
|
||||
}, checkInterval);
|
||||
});
|
||||
}
|
||||
waitForFunction("pollForUpdates").then(fn => fn('@gamelobby.id'));
|
||||
</script>
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
@()
|
||||
|
||||
@main("Login") {
|
||||
|
||||
<div class="login-box">
|
||||
<div class="login-box">
|
||||
<div class="card login-card p-4">
|
||||
<div class="card-body">
|
||||
<h3 class="text-center mb-4 text-body">Login</h3>
|
||||
|
||||
<form action="@routes.UserController.login_Post()" method="post">
|
||||
<form onsubmit="login(); return false;">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label text-body">Username</label>
|
||||
<input type="text" class="form-control text-body" id="username" name="username" placeholder="Enter Username" required>
|
||||
@@ -22,7 +18,7 @@
|
||||
<a href="#" class="text-decoration-none">Forgot password?</a>
|
||||
</div>
|
||||
|
||||
<div class="d-grid">
|
||||
<div class="d-grid" >
|
||||
<button type="submit" class="btn btn-primary">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -33,15 +29,14 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="@routes.Assets.versioned("/javascripts/particles.js")"></script>
|
||||
<script>
|
||||
</div>
|
||||
<script src="@routes.Assets.versioned("/javascripts/particles.js")"></script>
|
||||
<script>
|
||||
particlesJS.load('particles-js', 'assets/conf/particlesjs-config.json', function() {
|
||||
console.log('callback - particles.js config loaded');
|
||||
});
|
||||
</script>
|
||||
<div id="particles-js" style="background-color: rgb(11, 8, 8);
|
||||
</script>
|
||||
<div id="particles-js" style="background-color: rgb(11, 8, 8);
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;"></div>
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
@(user: Option[model.users.User])
|
||||
|
||||
@main("Create Game") {
|
||||
@navbar(user)
|
||||
<main class="lobby-background flex-grow-1">
|
||||
<div class="w-25 mx-auto">
|
||||
@@ -29,4 +28,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
}
|
||||
@@ -23,8 +23,8 @@
|
||||
<a class="nav-link active" href="@routes.MainMenuController.rules()">Rules</a>
|
||||
</li>
|
||||
</ul>
|
||||
<form class="navbar-nav me-auto mb-2 mb-lg-0" method="post" action="@routes.MainMenuController.joinGame()">
|
||||
<input class="form-control me-2" type="text" placeholder="Enter GameCode" name="gameId" aria-label="Join Game"/>
|
||||
<form class="navbar-nav me-auto mb-2 mb-lg-0" onsubmit="joinGame(); return false;">
|
||||
<input class="form-control me-2" type="text" placeholder="Enter GameCode" id="gameId" aria-label="Join Game"/>
|
||||
<button class="btn btn-outline-success" type="submit">Join</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -24,8 +24,7 @@ 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/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)
|
||||
|
||||
@@ -88,7 +88,8 @@ function pollForUpdates(gameId) {
|
||||
const $handElement = $('#card-slide');
|
||||
const $lobbyElement = $('#lobbybackground');
|
||||
const $mainmenuElement = $('#main-menu-screen')
|
||||
if (!$handElement.length && !$lobbyElement.length && !$mainmenuElement.length) {
|
||||
const $mainbody = $('#main-body')
|
||||
if (!$handElement.length && !$lobbyElement.length && !$mainmenuElement.length && !$mainbody.length) {
|
||||
setTimeout(() => pollForUpdates(gameId), 1000);
|
||||
return;
|
||||
}
|
||||
@@ -141,6 +142,13 @@ function pollForUpdates(gameId) {
|
||||
}
|
||||
|
||||
$handElement.html(newHandHTML);
|
||||
|
||||
if (data.yourTurn) {
|
||||
$handElement.removeClass('inactive');
|
||||
} else {
|
||||
$handElement.addClass('inactive');
|
||||
}
|
||||
|
||||
$('#current-player-name').text(data.currentPlayerName)
|
||||
if (data.nextPlayer) {
|
||||
$('#next-player-name').text(data.nextPlayer);
|
||||
@@ -207,7 +215,7 @@ function pollForUpdates(gameId) {
|
||||
}
|
||||
} else if (data.status === "reloadEvent") {
|
||||
console.log("[DEBUG] Reload event received. Redirecting...");
|
||||
window.location.href = data.redirectUrl;
|
||||
exchangeBody(data.content, "Knockout Whist - Ingame", data.redirectUrl);
|
||||
}
|
||||
else if (data.status === "lobbyUpdate") {
|
||||
console.log("[DEBUG] Entering 'lobbyUpdate' logic.");
|
||||
@@ -280,6 +288,26 @@ function createGameJS() {
|
||||
sendGameCreationRequest(jsonObj);
|
||||
}
|
||||
|
||||
function backToLobby(gameId) {
|
||||
const route = jsRoutes.controllers.IngameController.returnToLobby(gameId);
|
||||
|
||||
$.ajax({
|
||||
url: route.url,
|
||||
type: route.type,
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
error: ((jqXHR) => {
|
||||
const errorData = JSON.parse(jqXHR.responseText);
|
||||
if (errorData && errorData.errorMessage) {
|
||||
alert(`${errorData.errorMessage}`);
|
||||
} else {
|
||||
alert(`An unexpected error occurred. Please try again. Status: ${jqXHR.status}`);
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function sendGameCreationRequest(dataObject) {
|
||||
const route = jsRoutes.controllers.MainMenuController.createGame();
|
||||
|
||||
@@ -291,7 +319,7 @@ function sendGameCreationRequest(dataObject) {
|
||||
dataType: 'json',
|
||||
success: (data => {
|
||||
if (data.status === 'success') {
|
||||
window.location.href = data.redirectUrl;
|
||||
exchangeBody(data.content, "Knockout Whist - Lobby", data.redirectUrl);
|
||||
}
|
||||
}),
|
||||
error: ((jqXHR) => {
|
||||
@@ -304,9 +332,19 @@ function sendGameCreationRequest(dataObject) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function exchangeBody(content, title = "Knockout Whist", url = null) {
|
||||
if (url) {
|
||||
window.history.pushState({}, title, url);
|
||||
}
|
||||
$("#main-body").html(content);
|
||||
document.title = title;
|
||||
}
|
||||
|
||||
function startGame(gameId) {
|
||||
sendGameStartRequest(gameId)
|
||||
}
|
||||
|
||||
function sendGameStartRequest(gameId) {
|
||||
const route = jsRoutes.controllers.IngameController.startGame(gameId);
|
||||
|
||||
@@ -357,21 +395,28 @@ function sendRemovePlayerRequest(gameId, playersessionId) {
|
||||
})
|
||||
}
|
||||
|
||||
function leaveGame(gameId) {
|
||||
sendLeavePlayerRequest(gameId)
|
||||
}
|
||||
function login() {
|
||||
const username = $('#username').val();
|
||||
const password = $('#password').val();
|
||||
|
||||
function sendLeavePlayerRequest(gameId) {
|
||||
const jsonObj = {
|
||||
username: username,
|
||||
password: password
|
||||
};
|
||||
|
||||
const route = jsRoutes.controllers.IngameController.leaveGame(gameId);
|
||||
const route = jsRoutes.controllers.UserController.login_Post();
|
||||
$.ajax({
|
||||
url: route.url,
|
||||
type: route.type,
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
data: JSON.stringify(jsonObj),
|
||||
success: (data => {
|
||||
if (data.status === 'success') {
|
||||
window.location.href = data.redirectUrl;
|
||||
exchangeBody(data.content, "Knockout Whist - Main Menu", data.redirectUrl);
|
||||
return
|
||||
}
|
||||
alert('Login failed. Please check your credentials and try again.');
|
||||
}),
|
||||
error: ((jqXHR) => {
|
||||
const errorData = JSON.parse(jqXHR.responseText);
|
||||
@@ -381,6 +426,118 @@ function sendLeavePlayerRequest(gameId) {
|
||||
alert(`An unexpected error occurred. Please try again. Status: ${jqXHR.status}`);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function joinGame() {
|
||||
const gameId = $('#gameId').val();
|
||||
|
||||
const jsonObj = {
|
||||
gameId: gameId
|
||||
};
|
||||
|
||||
const route = jsRoutes.controllers.MainMenuController.joinGame();
|
||||
$.ajax({
|
||||
url: route.url,
|
||||
type: route.type,
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
data: JSON.stringify(jsonObj),
|
||||
success: (data => {
|
||||
if (data.status === 'success') {
|
||||
exchangeBody(data.content, "Knockout Whist - Lobby", data.redirectUrl);
|
||||
return
|
||||
}
|
||||
alert('Could not join the game. Please check the Game ID and try again.');
|
||||
}),
|
||||
error: ((jqXHR) => {
|
||||
const errorData = JSON.parse(jqXHR.responseText);
|
||||
if (errorData && errorData.errorMessage) {
|
||||
alert(`${errorData.errorMessage}`);
|
||||
} else {
|
||||
alert(`An unexpected error occurred. Please try again. Status: ${jqXHR.status}`);
|
||||
}
|
||||
})
|
||||
});
|
||||
return false
|
||||
}
|
||||
|
||||
function selectTie(gameId) {
|
||||
const route = jsRoutes.controllers.IngameController.playTie(gameId);
|
||||
const jsonObj = {
|
||||
tie: $('#tieNumber').val()
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: route.url,
|
||||
type: route.type,
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
data: JSON.stringify(jsonObj),
|
||||
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 leaveGame(gameId) {
|
||||
sendLeavePlayerRequest(gameId)
|
||||
}
|
||||
|
||||
function sendLeavePlayerRequest(gameId) {
|
||||
const route = jsRoutes.controllers.IngameController.leaveGame(gameId);
|
||||
$.ajax({
|
||||
url: route.url,
|
||||
type: route.type,
|
||||
dataType: 'json',
|
||||
error: ((jqXHR) => {
|
||||
const errorData = JSON.parse(jqXHR.responseText);
|
||||
if (errorData && errorData.errorMessage) {
|
||||
alert(`${errorData.errorMessage}`);
|
||||
} else {
|
||||
alert(`An unexpected error occurred. Please try again. Status: ${jqXHR.status}`);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function handleTrumpSelection(cardobject, gameId) {
|
||||
const trumpId = cardobject.dataset.trump;
|
||||
const jsonObj = {
|
||||
trump: trumpId
|
||||
}
|
||||
|
||||
const route = jsRoutes.controllers.IngameController.playTrump(gameId);
|
||||
|
||||
$.ajax({
|
||||
url: route.url,
|
||||
type: route.type,
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
data: JSON.stringify(jsonObj),
|
||||
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.');
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -393,6 +550,22 @@ function handlePlayCard(cardobject, gameId, dog = false) {
|
||||
}
|
||||
|
||||
function handleSkipDogLife(cardobject, gameId) {
|
||||
|
||||
const wiggleKeyframes = [
|
||||
{ transform: 'translateX(0)' },
|
||||
{ transform: 'translateX(-5px)' },
|
||||
{ transform: 'translateX(5px)' },
|
||||
{ transform: 'translateX(-5px)' },
|
||||
{ transform: 'translateX(0)' }
|
||||
];
|
||||
|
||||
const wiggleTiming = {
|
||||
duration: 400,
|
||||
iterations: 1,
|
||||
easing: 'ease-in-out',
|
||||
fill: 'forwards'
|
||||
};
|
||||
|
||||
const route = jsRoutes.controllers.IngameController.playDogCard(gameId);
|
||||
|
||||
$.ajax({
|
||||
@@ -410,7 +583,9 @@ function handleSkipDogLife(cardobject, gameId) {
|
||||
} catch (e) {
|
||||
console.error("Failed to parse error response:", e);
|
||||
}
|
||||
if (error?.errorMessage) {
|
||||
if (error?.errorMessage.includes("You can't skip this round!")) {
|
||||
cardobject.parentElement.animate(wiggleKeyframes, wiggleTiming);
|
||||
} else if (error?.errorMessage) {
|
||||
alert(`${error.errorMessage}`);
|
||||
} else {
|
||||
alert('An unexpected error occurred. Please try again.');
|
||||
@@ -434,7 +609,7 @@ function sendPlayCardRequest(jsonObj, gameId, cardobject, dog) {
|
||||
easing: 'ease-in-out',
|
||||
fill: 'forwards'
|
||||
};
|
||||
const route = dog ? jsRoutes.controllers.IngameController.playCard(gameId) : jsRoutes.controllers.IngameController.playDogCard(gameId);
|
||||
const route = dog === "true" ? jsRoutes.controllers.IngameController.playDogCard(gameId) : jsRoutes.controllers.IngameController.playCard(gameId);
|
||||
|
||||
$.ajax({
|
||||
url: route.url,
|
||||
|
||||
Reference in New Issue
Block a user