feat(ui): Fixed polling, added JQuery Ajax
Fixed Race Condition problems with polling, added JQuery
This commit is contained in:
@@ -172,8 +172,7 @@ class IngameController @Inject() (
|
||||
optSession.foreach(_.lock.unlock())
|
||||
if (result.isSuccess) {
|
||||
Ok(Json.obj(
|
||||
"status" -> "success",
|
||||
"redirectUrl" -> routes.IngameController.game(gameId).url
|
||||
"status" -> "success"
|
||||
))
|
||||
} else {
|
||||
val throwable = result.failed.get
|
||||
@@ -198,6 +197,11 @@ class IngameController @Inject() (
|
||||
"status" -> "failure",
|
||||
"errorMessage" -> throwable.getMessage
|
||||
))
|
||||
case _: NotInteractableException =>
|
||||
BadRequest(Json.obj(
|
||||
"status" -> "failure",
|
||||
"errorMessage" -> throwable.getMessage
|
||||
))
|
||||
case _ =>
|
||||
InternalServerError(Json.obj(
|
||||
"status" -> "failure",
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package controllers
|
||||
|
||||
import auth.{AuthAction, AuthenticatedRequest}
|
||||
import controllers.PollingController.{scheduler, timeoutDuration}
|
||||
import de.knockoutwhist.cards.Hand
|
||||
import logic.PodManager
|
||||
import logic.game.{GameLobby, PollingEvents}
|
||||
import logic.game.PollingEvents.{CardPlayed, LobbyUpdate, NewRound, ReloadEvent}
|
||||
import logic.game.PollingEvents.{CardPlayed, LobbyCreation, LobbyUpdate, NewRound, ReloadEvent}
|
||||
import model.sessions.UserSession
|
||||
import model.users.User
|
||||
import play.api.libs.json.{JsArray, JsValue, Json}
|
||||
@@ -13,7 +14,12 @@ import util.WebUIUtils
|
||||
|
||||
import javax.inject.{Inject, Singleton}
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
import java.util.concurrent.{Executors, ScheduledExecutorService, TimeUnit}
|
||||
import scala.concurrent.duration.*
|
||||
object PollingController {
|
||||
private val scheduler: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor()
|
||||
private val timeoutDuration = 25.seconds
|
||||
}
|
||||
@Singleton
|
||||
class PollingController @Inject() (
|
||||
val cc: ControllerComponents,
|
||||
@@ -96,25 +102,28 @@ class PollingController @Inject() (
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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()
|
||||
|
||||
val playerEventQueue = game.getEventsOfPlayer(playerId)
|
||||
if (playerEventQueue.nonEmpty) {
|
||||
val event = playerEventQueue.dequeue()
|
||||
Future.successful(handleEvent(event, game, game.getUserSession(playerId)))
|
||||
} else {
|
||||
|
||||
val eventPromise = game.registerWaiter(playerId)
|
||||
|
||||
val scheduledFuture = scheduler.schedule(
|
||||
new Runnable {
|
||||
override def run(): Unit =
|
||||
eventPromise.tryFailure(new java.util.concurrent.TimeoutException("Polling Timeout"))
|
||||
},
|
||||
timeoutDuration.toMillis,
|
||||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
eventPromise.future.map { event =>
|
||||
scheduledFuture.cancel(false)
|
||||
game.removeWaiter(playerId)
|
||||
handleEvent(event, game, game.getUserSession(playerId))
|
||||
}.recover {
|
||||
@@ -125,7 +134,6 @@ class PollingController @Inject() (
|
||||
}
|
||||
|
||||
case None =>
|
||||
// Game not found
|
||||
Future.successful(NotFound("Game not found."))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ 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, LobbyUpdate, NewRound, ReloadEvent}
|
||||
import logic.game.PollingEvents.{CardPlayed, LobbyCreation, LobbyUpdate, NewRound, ReloadEvent}
|
||||
import model.sessions.{InteractionType, UserSession}
|
||||
import model.users.User
|
||||
|
||||
@@ -27,21 +27,39 @@ class GameLobby private(
|
||||
val name: String,
|
||||
val maxPlayers: Int
|
||||
) extends EventListener {
|
||||
logic.addListener(this)
|
||||
logic.createSession()
|
||||
|
||||
|
||||
|
||||
private val users: mutable.Map[UUID, UserSession] = mutable.Map()
|
||||
private val pollingState: mutable.Queue[PollingEvents] = mutable.Queue()
|
||||
private val eventsPerPlayer: mutable.Map[UUID, mutable.Queue[PollingEvents]] = mutable.Map()
|
||||
private val waitingPromises: mutable.Map[UUID, ScalaPromise[PollingEvents]] = mutable.Map()
|
||||
private val lock = new Object
|
||||
lock.synchronized {
|
||||
logic.addListener(this)
|
||||
logic.createSession()
|
||||
}
|
||||
|
||||
|
||||
def registerWaiter(playerId: UUID): ScalaPromise[PollingEvents] = {
|
||||
val promise = ScalaPromise[PollingEvents]()
|
||||
waitingPromises.put(playerId, promise)
|
||||
promise
|
||||
lock.synchronized {
|
||||
val queue = eventsPerPlayer.getOrElseUpdate(playerId, mutable.Queue())
|
||||
|
||||
if (queue.nonEmpty) {
|
||||
val evt = queue.dequeue()
|
||||
promise.success(evt)
|
||||
promise
|
||||
} else {
|
||||
waitingPromises.put(playerId, promise)
|
||||
promise
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def removeWaiter(playerId: UUID): Unit = {
|
||||
waitingPromises.remove(playerId)
|
||||
lock.synchronized {
|
||||
waitingPromises.remove(playerId)
|
||||
}
|
||||
}
|
||||
|
||||
def addUser(user: User): UserSession = {
|
||||
@@ -72,7 +90,8 @@ class GameLobby private(
|
||||
}
|
||||
if (event.oldState == Lobby && event.newState == InGame) {
|
||||
addToQueue(ReloadEvent)
|
||||
}else {
|
||||
return
|
||||
} else {
|
||||
addToQueue(ReloadEvent)
|
||||
}
|
||||
users.values.foreach(session => session.updatePlayer(event))
|
||||
@@ -84,11 +103,29 @@ class GameLobby private(
|
||||
}
|
||||
|
||||
private def addToQueue(event: PollingEvents): Unit = {
|
||||
if (waitingPromises.nonEmpty) {
|
||||
waitingPromises.values.foreach(_.success(event))
|
||||
waitingPromises.clear()
|
||||
} else {
|
||||
pollingState.enqueue(event)
|
||||
lock.synchronized {
|
||||
users.keys.foreach { playerId =>
|
||||
val q = eventsPerPlayer.getOrElseUpdate(playerId, mutable.Queue())
|
||||
q.enqueue(event)
|
||||
}
|
||||
val waiterIds = waitingPromises.keys.toList
|
||||
waiterIds.foreach { playerId =>
|
||||
val q = eventsPerPlayer.getOrElseUpdate(playerId, mutable.Queue())
|
||||
if (q.nonEmpty) {
|
||||
val evt = q.dequeue()
|
||||
val p = waitingPromises.remove(playerId)
|
||||
p.foreach(_.success(evt))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
waitingPromises.keys.foreach { playerId =>
|
||||
val queue = eventsPerPlayer(playerId)
|
||||
if (queue.nonEmpty) {
|
||||
val promise = waitingPromises(playerId)
|
||||
promise.success(queue.dequeue())
|
||||
waitingPromises.remove(playerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,8 +255,8 @@ class GameLobby private(
|
||||
def getLogic: GameLogic = {
|
||||
logic
|
||||
}
|
||||
def getPollingState: mutable.Queue[PollingEvents] = {
|
||||
pollingState
|
||||
def getEventsOfPlayer(playerId: UUID): mutable.Queue[PollingEvents] = {
|
||||
eventsPerPlayer.getOrElseUpdate(playerId, mutable.Queue())
|
||||
}
|
||||
private def getPlayerBySession(userSession: UserSession): AbstractPlayer = {
|
||||
val playerOption = getMatch.totalplayers.find(_.id == userSession.id)
|
||||
|
||||
@@ -5,4 +5,5 @@ enum PollingEvents {
|
||||
case NewRound
|
||||
case ReloadEvent
|
||||
case LobbyUpdate
|
||||
case LobbyCreation
|
||||
}
|
||||
@@ -25,5 +25,6 @@
|
||||
<script src="@routes.JavaScriptRoutingController.javascriptRoutes()" type="text/javascript"></script>
|
||||
<script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -80,210 +80,192 @@
|
||||
})()
|
||||
|
||||
function pollForUpdates(gameId) {
|
||||
console.log(`[DEBUG] Starting poll cycle for Game ID: ${gameId} at ${new Date().toISOString()}`);
|
||||
if (!gameId) {
|
||||
console.error("Game ID is missing. Stopping poll.");
|
||||
console.error("[DEBUG] Game ID is missing. Stopping poll.");
|
||||
return;
|
||||
}
|
||||
const element = document.getElementById('card-slide');
|
||||
const element2 = document.getElementById('lobbybackground');
|
||||
// Safety check for the target element
|
||||
if (!element && !element2) {
|
||||
console.error("Polling target element not found. Stopping poll.");
|
||||
// Use a timeout to retry in case the DOM loads late, passing gameId.
|
||||
const $handElement = $('#card-slide');
|
||||
const $lobbyElement = $('#lobbybackground');
|
||||
const $mainmenuElement = $('#main-menu-screen')
|
||||
if (!$handElement.length && !$lobbyElement.length && !$mainmenuElement.length) {
|
||||
setTimeout(() => pollForUpdates(gameId), 5000);
|
||||
return;
|
||||
}
|
||||
const route = jsRoutes.controllers.PollingController.polling(gameId);
|
||||
$.ajax({
|
||||
url: route.url,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
|
||||
// Call your specific controller endpoint
|
||||
fetch(route.url)
|
||||
.then(response => {
|
||||
if (response.status === 204) {
|
||||
console.log("Polling: Timeout reached. Restarting poll.");
|
||||
success: (data => {
|
||||
if (!data) {
|
||||
console.log("[DEBUG] Received 204 No Content (Timeout). Restarting poll.");
|
||||
return;
|
||||
}
|
||||
if (data.status === "cardPlayed" && data.handData) {
|
||||
console.log("Event received: Card played. Redrawing hand.");
|
||||
const newHand = data.handData;
|
||||
let newHandHTML = '';
|
||||
$handElement.empty();
|
||||
|
||||
// CRITICAL: Pass gameId in the recursive call
|
||||
setTimeout(() => pollForUpdates(gameId), 5000);
|
||||
} else if (response.ok && response.status === 200) {
|
||||
response.json().then(data => {
|
||||
if(data.animation) {
|
||||
$handElement.addClass('ingame-cards-slide');
|
||||
} else {
|
||||
$handElement.removeClass('ingame-cards-slide');
|
||||
}
|
||||
|
||||
if (data.status === "cardPlayed" && data.handData && element) {
|
||||
console.log("Event received: Card played. Redrawing hand.");
|
||||
newHand.forEach((cardId, index) => {
|
||||
const cardHtml = `
|
||||
<div class="col-auto handcard" style="border-radius: 6px">
|
||||
<div class="btn btn-outline-light p-0 border-0 shadow-none"
|
||||
data-card-id="${index}"
|
||||
style="border-radius: 6px"
|
||||
onclick="handlePlayCard(this, '${gameId}')">
|
||||
|
||||
<img src="/assets/images/cards/${cardId}.png" width="120px" style="border-radius: 6px"/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
newHandHTML += cardHtml;
|
||||
});
|
||||
|
||||
const newHand = data.handData;
|
||||
let newHandHTML = '';
|
||||
element.innerHTML = '';
|
||||
$handElement.html(newHandHTML);
|
||||
$('#current-player-name').text(data.currentPlayerName)
|
||||
if (data.nextPlayer) {
|
||||
$('#next-player-name').text(data.nextPlayer);
|
||||
} else if (nextPlayerElement) {
|
||||
$('#next-player-name').text("");
|
||||
} else {
|
||||
console.warn("[DEBUG] 'current-player-name' element missing in DOM");
|
||||
}
|
||||
$('#trump-suit').text(data.trumpSuit);
|
||||
if ($('#trick-cards-container').length) {
|
||||
let trickHTML = '';
|
||||
|
||||
if(data.animation) {
|
||||
if (!element.classList.contains('ingame-cards-slide')) {
|
||||
element.classList.add('ingame-cards-slide');
|
||||
}
|
||||
} else {
|
||||
element.classList.remove('ingame-cards-slide');
|
||||
}
|
||||
|
||||
newHand.forEach((cardId, index) => {
|
||||
const cardHtml = `
|
||||
<div class="col-auto handcard" style="border-radius: 6px">
|
||||
<div class="btn btn-outline-light p-0 border-0 shadow-none"
|
||||
data-card-id="${index}"
|
||||
style="border-radius: 6px"
|
||||
onclick="handlePlayCard(this, '${gameId}')">
|
||||
|
||||
<img src="/assets/images/cards/${cardId}.png" width="120px" style="border-radius: 6px"/>
|
||||
data.trickCards.forEach(trickCard => {
|
||||
trickHTML += `
|
||||
<div class="col-auto">
|
||||
<div class="card text-center shadow-sm border-0 bg-transparent" style="width: 7rem; backdrop-filter: blur(4px);">
|
||||
<div class="p-2">
|
||||
<img src="/assets/images/cards/${trickCard.cardId}.png" width="100%"/>
|
||||
</div>
|
||||
<div class="card-body p-2 bg-transparent">
|
||||
<small class="fw-semibold text-secondary">${trickCard.player}</small>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
newHandHTML += cardHtml;
|
||||
});
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
$('#trick-cards-container').html(trickHTML);
|
||||
}
|
||||
if ($('#score-table-body').length && data.scoreTable) {
|
||||
let scoreHTML = '';
|
||||
scoreHTML += `<h4 class="fw-bold mb-3 text-black">Tricks Won</h4>
|
||||
|
||||
element.innerHTML = newHandHTML;
|
||||
<div class="d-flex justify-content-between score-header pb-1">
|
||||
<div style="width: 50%">PLAYER</div>
|
||||
<div style="width: 50%">TRICKS</div>
|
||||
</div>`
|
||||
data.scoreTable.forEach(score => {
|
||||
scoreHTML += `
|
||||
<div class="d-flex justify-content-between score-row pt-1">
|
||||
<div style="width: 50%" class="text-truncate">${score.name}</div>
|
||||
<div style="width: 50%">${score.tricks}</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
$('#score-table-body').html(scoreHTML);
|
||||
}
|
||||
const cardId = data.firstCardId;
|
||||
if ($('#first-card-container').length) {
|
||||
let imageSrc = '';
|
||||
let altText = 'First Card';
|
||||
|
||||
const currentPlayerElement = document.getElementById('current-player-name');
|
||||
if (currentPlayerElement) {
|
||||
currentPlayerElement.textContent = data.currentPlayerName;
|
||||
}
|
||||
const nextPlayerElement = document.getElementById('next-player-name');
|
||||
if (nextPlayerElement && data.nextPlayer) {
|
||||
// Use the correctly named field from the server response
|
||||
nextPlayerElement.textContent = data.nextPlayer;
|
||||
} else {
|
||||
// Case 2: Player name is empty or null (signal to clear display).
|
||||
nextPlayerElement.textContent = "";
|
||||
}
|
||||
|
||||
const trumpElement = document.getElementById('trump-suit');
|
||||
if (trumpElement) {
|
||||
trumpElement.textContent = data.trumpSuit;
|
||||
}
|
||||
const trickContainer = document.getElementById('trick-cards-container');
|
||||
if (trickContainer) {
|
||||
let trickHTML = '';
|
||||
|
||||
// Iterate over the array of played cards received from the server
|
||||
data.trickCards.forEach(trickCard => {
|
||||
// Reconstruct the HTML structure from your template
|
||||
trickHTML += `
|
||||
<div class="col-auto">
|
||||
<div class="card text-center shadow-sm border-0 bg-transparent" style="width: 7rem; backdrop-filter: blur(4px);">
|
||||
<div class="p-2">
|
||||
<img src="/assets/images/cards/${trickCard.cardId}.png" width="100%"/>
|
||||
</div>
|
||||
<div class="card-body p-2 bg-transparent">
|
||||
<small class="fw-semibold text-secondary">${trickCard.player}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
trickContainer.innerHTML = trickHTML;
|
||||
}
|
||||
const scoreBody = document.getElementById('score-table-body');
|
||||
if (scoreBody && data.scoreTable) {
|
||||
let scoreHTML = '';
|
||||
scoreHTML += `<h4 class="fw-bold mb-3 text-black">Tricks Won</h4>
|
||||
|
||||
<div class="d-flex justify-content-between score-header pb-1">
|
||||
<div style="width: 50%">PLAYER</div>
|
||||
<div style="width: 50%">TRICKS</div>
|
||||
</div>`
|
||||
data.scoreTable.forEach(score => {
|
||||
scoreHTML += `
|
||||
<div class="d-flex justify-content-between score-row pt-1">
|
||||
<div style="width: 50%" class="text-truncate">${score.name}</div>
|
||||
<div style="width: 50%">${score.tricks}</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
scoreBody.innerHTML = scoreHTML;
|
||||
}
|
||||
const firstCardContainer = document.getElementById('first-card-container');
|
||||
const cardId = data.firstCardId; // This will be "KH", "S7", or "BLANK"
|
||||
|
||||
if (firstCardContainer) {
|
||||
let imageSrc = '';
|
||||
let altText = 'First Card';
|
||||
|
||||
// Check if a card was actually played or if it's the start of a trick
|
||||
if (cardId === "BLANK") {
|
||||
imageSrc = "/assets/images/cards/1B.png";
|
||||
altText = "Blank Card";
|
||||
} else {
|
||||
imageSrc = `/assets/images/cards/${cardId}.png`;
|
||||
}
|
||||
|
||||
// Reconstruct the image HTML (assuming the inner element needs replacement)
|
||||
const newImageHTML = `
|
||||
<img src="${imageSrc}" alt="${altText}" width="80px" style="border-radius: 6px"/>
|
||||
`;
|
||||
|
||||
// Clear the container and insert the new image
|
||||
firstCardContainer.innerHTML = newImageHTML;
|
||||
}
|
||||
} else if (data.status === "reloadEvent") {
|
||||
window.location.href = data.redirectUrl;
|
||||
} else if (data.status === "lobbyUpdate") {
|
||||
const players = document.getElementById("players");
|
||||
let newHtml = ''
|
||||
|
||||
if (data.host) {
|
||||
data.users.forEach(user => {
|
||||
|
||||
const inner = user.self ? `<h5 class="card-title">${user.name} (You)</h5>
|
||||
<a href="#" class="btn btn-danger disabled" aria-disabled="true" tabindex="-1">Remove</a>`
|
||||
: ` <h5 class="card-title">${user.name}</h5>
|
||||
<div class="btn btn-danger" onclick="removePlayer('${gameId}', '${user.id}')">Remove</div>`
|
||||
|
||||
newHtml += `<div class="col-auto my-auto m-3">
|
||||
<div class="card" style="width: 18rem;">
|
||||
<img src="/assets/images/profile.png" alt="Profile" class="card-img-top w-50 mx-auto mt-3" />
|
||||
<div class="card-body">
|
||||
${inner}
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
} else {
|
||||
data.users.forEach(user => {
|
||||
|
||||
const inner = user.self ? `<h5 class="card-title">${user.name} (You)</h5>` : ` <h5 class="card-title">${user.name}</h5>`
|
||||
|
||||
newHtml += `<div class="col-auto my-auto m-3">
|
||||
<div class="card" style="width: 18rem;">
|
||||
<img src="/assets/images/profile.png" alt="Profile" class="card-img-top w-50 mx-auto mt-3" />
|
||||
<div class="card-body">
|
||||
${inner}
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
}
|
||||
players.innerHTML = newHtml;
|
||||
if (cardId === "BLANK") {
|
||||
imageSrc = "/assets/images/cards/1B.png";
|
||||
altText = "Blank Card";
|
||||
} else {
|
||||
imageSrc = `/assets/images/cards/${cardId}.png`;
|
||||
}
|
||||
pollForUpdates(gameId);
|
||||
});
|
||||
} else {
|
||||
// Handle network or server errors
|
||||
console.error(`Polling error: Status ${response.status}`);
|
||||
// Wait before retrying, passing gameId correctly
|
||||
setTimeout(() => pollForUpdates(gameId), 5000);
|
||||
|
||||
const newImageHTML = `
|
||||
<img src="${imageSrc}" alt="${altText}" width="80px" style="border-radius: 6px"/>
|
||||
`;
|
||||
|
||||
$('#first-card-container').html(newImageHTML);
|
||||
}
|
||||
} else if (data.status === "reloadEvent") {
|
||||
console.log("[DEBUG] Reload event received. Redirecting...");
|
||||
window.location.href = data.redirectUrl;
|
||||
}
|
||||
else if (data.status === "lobbyUpdate") {
|
||||
console.log("[DEBUG] Entering 'lobbyUpdate' logic.");
|
||||
let newHtml = ''
|
||||
|
||||
if (data.host) {
|
||||
data.users.forEach(user => {
|
||||
|
||||
const inner = user.self ? `<h5 class="card-title">${user.name} (You)</h5>
|
||||
<a href="#" class="btn btn-danger disabled" aria-disabled="true" tabindex="-1">Remove</a>`
|
||||
: ` <h5 class="card-title">${user.name}</h5>
|
||||
<div class="btn btn-danger" onclick="removePlayer('${gameId}', '${user.id}')">Remove</div>`
|
||||
|
||||
newHtml += `<div class="col-auto my-auto m-3">
|
||||
<div class="card" style="width: 18rem;">
|
||||
<img src="/assets/images/profile.png" alt="Profile" class="card-img-top w-50 mx-auto mt-3" />
|
||||
<div class="card-body">
|
||||
${inner}
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
} else {
|
||||
data.users.forEach(user => {
|
||||
|
||||
const inner = user.self ? `<h5 class="card-title">${user.name} (You)</h5>` : ` <h5 class="card-title">${user.name}</h5>`
|
||||
|
||||
newHtml += `<div class="col-auto my-auto m-3">
|
||||
<div class="card" style="width: 18rem;">
|
||||
<img src="/assets/images/profile.png" alt="Profile" class="card-img-top w-50 mx-auto mt-3" />
|
||||
<div class="card-body">
|
||||
${inner}
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
}
|
||||
$("#players").html(newHtml);
|
||||
} else {
|
||||
console.warn(`[DEBUG] Received unknown status: ${data.status}`);
|
||||
}
|
||||
}),
|
||||
error: ((jqXHR, textStatus, errorThrown) => {
|
||||
if (jqXHR.status >= 400) {
|
||||
console.error(`Server error: ${jqXHR.status}, ${errorThrown}`);
|
||||
}
|
||||
else {
|
||||
console.error(`Something unexpected happened while polling. ${jqXHR.status}, ${errorThrown}`)
|
||||
}
|
||||
}),
|
||||
complete: ((jqXHR, textStatus) => {
|
||||
if (!window.location.href.includes("game")) {
|
||||
console.log("[DEBUG] Page URL changed. Stopping poll restart.");
|
||||
return;
|
||||
}
|
||||
setTimeout(() => pollForUpdates(gameId), 500);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Network error during polling:", error);
|
||||
// Wait before retrying on network failure, passing gameId correctly
|
||||
setTimeout(() => pollForUpdates(gameId), 5000);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function createGameJS() {
|
||||
let lobbyName = document.getElementById("lobbyname").value;
|
||||
if (lobbyName === "") {
|
||||
let lobbyName = $('#lobbyname').val();
|
||||
if ($.trim(lobbyName) === "") {
|
||||
lobbyName = "DefaultLobby"
|
||||
}
|
||||
const playerAmount = document.getElementById("playeramount").value;
|
||||
const jsonObj = {
|
||||
lobbyname: lobbyName,
|
||||
playeramount: playerAmount
|
||||
playeramount: $("#playeramount").val()
|
||||
}
|
||||
sendGameCreationRequest(jsonObj);
|
||||
}
|
||||
@@ -291,33 +273,26 @@ function createGameJS() {
|
||||
function sendGameCreationRequest(dataObject) {
|
||||
const route = jsRoutes.controllers.MainMenuController.createGame();
|
||||
|
||||
fetch(route.url, {
|
||||
method: route.type,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(dataObject)
|
||||
})
|
||||
.then(response => {
|
||||
return response.json().then(data => {
|
||||
if (!response.ok) {
|
||||
return Promise.reject(data);
|
||||
}
|
||||
return data;
|
||||
});
|
||||
})
|
||||
.then(data => {
|
||||
$.ajax({
|
||||
url: route.url,
|
||||
type: route.type,
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(dataObject),
|
||||
dataType: 'json',
|
||||
success: (data => {
|
||||
if (data.status === 'success') {
|
||||
window.location.href = data.redirectUrl;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (error && error.errorMessage) {
|
||||
alert(`${error.errorMessage}`);
|
||||
}),
|
||||
error: ((jqXHR) => {
|
||||
const errorData = JSON.parse(jqXHR.responseText);
|
||||
if (errorData && errorData.errorMessage) {
|
||||
alert(`${errorData.errorMessage}`);
|
||||
} else {
|
||||
alert('An unexpected error occurred. Please try again.');
|
||||
alert(`An unexpected error occurred. Please try again. Status: ${jqXHR.status}`);
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
function startGame(gameId) {
|
||||
sendGameStartRequest(gameId)
|
||||
@@ -325,30 +300,24 @@ function startGame(gameId) {
|
||||
function sendGameStartRequest(gameId) {
|
||||
const route = jsRoutes.controllers.IngameController.startGame(gameId);
|
||||
|
||||
fetch(route.url, {
|
||||
method: route.type,
|
||||
})
|
||||
.then(response => {
|
||||
return response.json().then(data => {
|
||||
if (!response.ok) {
|
||||
return Promise.reject(data);
|
||||
}
|
||||
return data;
|
||||
});
|
||||
})
|
||||
.then(data => {
|
||||
// SUCCESS BLOCK: data is the { status: 'success', ... } object
|
||||
$.ajax({
|
||||
url: route.url,
|
||||
type: route.type,
|
||||
dataType: 'json',
|
||||
success: (data => {
|
||||
if (data.status === 'success') {
|
||||
window.location.href = data.redirectUrl;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (error && error.errorMessage) {
|
||||
alert(`${error.errorMessage}`);
|
||||
}),
|
||||
error: ((jqXHR) => {
|
||||
const errorData = JSON.parse(jqXHR.responseText);
|
||||
if (errorData && errorData.errorMessage) {
|
||||
alert(`${errorData.errorMessage}`);
|
||||
} else {
|
||||
alert('An unexpected error occurred. Please try again.');
|
||||
alert(`An unexpected error occurred. Please try again. Status: ${jqXHR.status}`);
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
function removePlayer(gameid, playersessionId) {
|
||||
sendRemovePlayerRequest(gameid, playersessionId)
|
||||
@@ -357,33 +326,25 @@ function removePlayer(gameid, playersessionId) {
|
||||
function sendRemovePlayerRequest(gameId, playersessionId) {
|
||||
const route = jsRoutes.controllers.IngameController.kickPlayer(gameId, playersessionId);
|
||||
|
||||
fetch(route.url, {
|
||||
method: route.type,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
return response.json().then(data => {
|
||||
if (!response.ok) {
|
||||
return Promise.reject(data);
|
||||
}
|
||||
return data;
|
||||
});
|
||||
})
|
||||
.then(data => {
|
||||
// SUCCESS BLOCK: data is the { status: 'success', ... } object
|
||||
$.ajax({
|
||||
url: route.url,
|
||||
type: route.type,
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
success: (data => {
|
||||
if (data.status === 'success') {
|
||||
window.location.href = data.redirectUrl;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (error && error.errorMessage) {
|
||||
alert(`${error.errorMessage}`);
|
||||
}),
|
||||
error: ((jqXHR) => {
|
||||
const errorData = JSON.parse(jqXHR.responseText);
|
||||
if (errorData && errorData.errorMessage) {
|
||||
alert(`${errorData.errorMessage}`);
|
||||
} else {
|
||||
alert('An unexpected error occurred. Please try again.');
|
||||
alert(`An unexpected error occurred. Please try again. Status: ${jqXHR.status}`);
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function leaveGame(gameId) {
|
||||
@@ -393,30 +354,24 @@ function leaveGame(gameId) {
|
||||
function sendLeavePlayerRequest(gameId) {
|
||||
|
||||
const route = jsRoutes.controllers.IngameController.leaveGame(gameId);
|
||||
fetch(route.url, {
|
||||
method: route.type,
|
||||
})
|
||||
.then(response => {
|
||||
return response.json().then(data => {
|
||||
if (!response.ok) {
|
||||
return Promise.reject(data);
|
||||
}
|
||||
return data;
|
||||
});
|
||||
})
|
||||
.then(data => {
|
||||
// SUCCESS BLOCK: data is the { status: 'success', ... } object
|
||||
$.ajax({
|
||||
url: route.url,
|
||||
type: route.type,
|
||||
dataType: 'json',
|
||||
success: (data => {
|
||||
if (data.status === 'success') {
|
||||
window.location.href = data.redirectUrl;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (error && error.errorMessage) {
|
||||
alert(`${error.errorMessage}`);
|
||||
}),
|
||||
error: ((jqXHR) => {
|
||||
const errorData = JSON.parse(jqXHR.responseText);
|
||||
if (errorData && errorData.errorMessage) {
|
||||
alert(`${errorData.errorMessage}`);
|
||||
} else {
|
||||
alert('An unexpected error occurred. Please try again.');
|
||||
alert(`An unexpected error occurred. Please try again. Status: ${jqXHR.status}`);
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function handlePlayCard(cardobject, gameId) {
|
||||
@@ -436,37 +391,30 @@ function sendPlayCardRequest(jsonObj, gameId, cardobject) {
|
||||
{ transform: 'translateX(0)' }
|
||||
];
|
||||
|
||||
// Define the timing options
|
||||
const wiggleTiming = {
|
||||
duration: 400, // 0.4 seconds
|
||||
duration: 400,
|
||||
iterations: 1,
|
||||
easing: 'ease-in-out',
|
||||
// Fill mode ensures the final state is applied until reset
|
||||
fill: 'forwards'
|
||||
};
|
||||
const route = jsRoutes.controllers.IngameController.playCard(gameId);
|
||||
|
||||
fetch(route.url, {
|
||||
method: route.type,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(jsonObj)
|
||||
})
|
||||
.then(response => {
|
||||
return response.json().then(data => {
|
||||
if (!response.ok) {
|
||||
return Promise.reject(data);
|
||||
}
|
||||
return data;
|
||||
});
|
||||
})
|
||||
.then(data => {
|
||||
$.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;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
}),
|
||||
error: (jqXHR => {
|
||||
try {
|
||||
error = JSON.parse(jqXHR.responseText);
|
||||
} catch (e) {
|
||||
console.error("Failed to parse error response:", e);
|
||||
}
|
||||
if (error && error.errorMessage.includes("You can't play this card!")) {
|
||||
cardobject.parentElement.animate(wiggleKeyframes, wiggleTiming);
|
||||
} else if (error && error.errorMessage) {
|
||||
@@ -474,6 +422,7 @@ function sendPlayCardRequest(jsonObj, gameId, cardobject) {
|
||||
} else {
|
||||
alert('An unexpected error occurred. Please try again.');
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user