feat(ui): changed Background color, centered Lobby #53
Submodule knockoutwhist updated: b9a7b0a2af...5aa1cef356
@@ -79,6 +79,10 @@ body {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar-drop-shadow {
|
||||||
|
box-shadow: 0 1px 15px 0 #000000
|
||||||
|
}
|
||||||
|
|
||||||
#sessions {
|
#sessions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -225,3 +229,35 @@ body {
|
|||||||
.score-row {
|
.score-row {
|
||||||
color: #000000;
|
color: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* In-game centered stage and blurred sides overlay */
|
||||||
|
.ingame-stage {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wrapper that adds a backdrop blur to the background outside the centered card */
|
||||||
|
.blur-sides {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create an overlay that blurs everything behind it, except the central content area */
|
||||||
|
.blur-sides::before {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
/* fallback: subtle vignette if backdrop-filter unsupported */
|
||||||
|
background: radial-gradient(ellipse at center, rgba(0,0,0,0) 30%, rgba(0,0,0,0.35) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports ((-webkit-backdrop-filter: blur(8px)) or (backdrop-filter: blur(8px))) {
|
||||||
|
.blur-sides::before {
|
||||||
|
background: rgba(0,0,0,0.08);
|
||||||
|
-webkit-backdrop-filter: blur(10px) saturate(110%);
|
||||||
|
backdrop-filter: blur(10px) saturate(110%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,100 +22,11 @@ import scala.util.Try
|
|||||||
import scala.concurrent.ExecutionContext
|
import scala.concurrent.ExecutionContext
|
||||||
@Singleton
|
@Singleton
|
||||||
class IngameController @Inject()(
|
class IngameController @Inject()(
|
||||||
val cc: ControllerComponents,
|
val controllerComponents: ControllerComponents,
|
||||||
val podManager: PodManager,
|
|
||||||
val authAction: AuthAction,
|
val authAction: AuthAction,
|
||||||
implicit val ec: ExecutionContext
|
val podManager: PodManager
|
||||||
) extends AbstractController(cc) {
|
) extends BaseController {
|
||||||
|
|
||||||
// --- 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] =>
|
def game(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||||
val game = podManager.getGame(gameId)
|
val game = podManager.getGame(gameId)
|
||||||
game match {
|
game match {
|
||||||
@@ -156,70 +67,30 @@ class IngameController @Inject() (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
Ok(Json.obj(
|
Redirect(routes.IngameController.game(gameId))
|
||||||
"status" -> "success",
|
|
||||||
"redirectUrl" -> routes.IngameController.game(gameId).url
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
val throwable = result.failed.get
|
val throwable = result.failed.get
|
||||||
throwable match {
|
throwable match {
|
||||||
case _: NotInThisGameException =>
|
case _: NotInThisGameException =>
|
||||||
BadRequest(Json.obj(
|
BadRequest(throwable.getMessage)
|
||||||
"status" -> "failure",
|
|
||||||
"errorMessage" -> throwable.getMessage
|
|
||||||
))
|
|
||||||
case _: NotHostException =>
|
case _: NotHostException =>
|
||||||
Forbidden(Json.obj(
|
Forbidden(throwable.getMessage)
|
||||||
"status" -> "failure",
|
|
||||||
"errorMessage" -> throwable.getMessage
|
|
||||||
))
|
|
||||||
case _: NotEnoughPlayersException =>
|
case _: NotEnoughPlayersException =>
|
||||||
BadRequest(Json.obj(
|
BadRequest(throwable.getMessage)
|
||||||
"status" -> "failure",
|
|
||||||
"errorMessage" -> throwable.getMessage
|
|
||||||
))
|
|
||||||
case _ =>
|
case _ =>
|
||||||
InternalServerError(Json.obj(
|
InternalServerError(throwable.getMessage)
|
||||||
"status" -> "failure",
|
|
||||||
"errorMessage" -> 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 game = podManager.getGame(gameId)
|
||||||
val playerToKickUUID = UUID.fromString(playerToKick)
|
game.get.leaveGame(playerToKick)
|
||||||
val result = Try {
|
Redirect(routes.IngameController.game(gameId))
|
||||||
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."
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
def leaveGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
def leaveGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||||
val game = podManager.getGame(gameId)
|
val game = podManager.getGame(gameId)
|
||||||
val result = Try {
|
|
||||||
game.get.leaveGame(request.user.id)
|
game.get.leaveGame(request.user.id)
|
||||||
}
|
Redirect(routes.MainMenuController.mainMenu())
|
||||||
if (result.isSuccess) {
|
|
||||||
Ok(Json.obj(
|
|
||||||
"status" -> "success",
|
|
||||||
"redirectUrl" -> routes.MainMenuController.mainMenu().url
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
InternalServerError(Json.obj(
|
|
||||||
"status" -> "failure",
|
|
||||||
"errorMessage" -> "Something went wrong."
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
def joinGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
def joinGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||||
val game = podManager.getGame(gameId)
|
val game = podManager.getGame(gameId)
|
||||||
@@ -251,10 +122,7 @@ class IngameController @Inject() (
|
|||||||
val game = podManager.getGame(gameId)
|
val game = podManager.getGame(gameId)
|
||||||
game match {
|
game match {
|
||||||
case Some(g) =>
|
case Some(g) =>
|
||||||
val jsonBody = request.body.asJson
|
val cardIdOpt = request.body.asFormUrlEncoded.flatMap(_.get("cardId").flatMap(_.headOption))
|
||||||
val cardIdOpt: Option[String] = jsonBody.flatMap { jsValue =>
|
|
||||||
(jsValue \ "cardID").asOpt[String]
|
|
||||||
}
|
|
||||||
cardIdOpt match {
|
cardIdOpt match {
|
||||||
case Some(cardId) =>
|
case Some(cardId) =>
|
||||||
var optSession: Option[UserSession] = None
|
var optSession: Option[UserSession] = None
|
||||||
@@ -266,51 +134,27 @@ class IngameController @Inject() (
|
|||||||
}
|
}
|
||||||
optSession.foreach(_.lock.unlock())
|
optSession.foreach(_.lock.unlock())
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
Ok(Json.obj(
|
NoContent
|
||||||
"status" -> "success",
|
|
||||||
"redirectUrl" -> routes.IngameController.game(gameId).url
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
val throwable = result.failed.get
|
val throwable = result.failed.get
|
||||||
throwable match {
|
throwable match {
|
||||||
case _: CantPlayCardException =>
|
case _: CantPlayCardException =>
|
||||||
BadRequest(Json.obj(
|
BadRequest(throwable.getMessage)
|
||||||
"status" -> "failure",
|
|
||||||
"errorMessage" -> throwable.getMessage
|
|
||||||
))
|
|
||||||
case _: NotInThisGameException =>
|
case _: NotInThisGameException =>
|
||||||
BadRequest(Json.obj(
|
BadRequest(throwable.getMessage)
|
||||||
"status" -> "failure",
|
|
||||||
"errorMessage" -> throwable.getMessage
|
|
||||||
))
|
|
||||||
case _: IllegalArgumentException =>
|
case _: IllegalArgumentException =>
|
||||||
BadRequest(Json.obj(
|
BadRequest(throwable.getMessage)
|
||||||
"status" -> "failure",
|
|
||||||
"errorMessage" -> throwable.getMessage
|
|
||||||
))
|
|
||||||
case _: IllegalStateException =>
|
case _: IllegalStateException =>
|
||||||
BadRequest(Json.obj(
|
BadRequest(throwable.getMessage)
|
||||||
"status" -> "failure",
|
|
||||||
"errorMessage" -> throwable.getMessage
|
|
||||||
))
|
|
||||||
case _ =>
|
case _ =>
|
||||||
InternalServerError(Json.obj(
|
InternalServerError(throwable.getMessage)
|
||||||
"status" -> "failure",
|
|
||||||
"errorMessage" -> throwable.getMessage
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case None =>
|
case None =>
|
||||||
BadRequest(Json.obj(
|
BadRequest("cardId parameter is missing")
|
||||||
"status" -> "failure",
|
|
||||||
"errorMessage" -> "cardId Parameter is missing"
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
case None =>
|
case None =>
|
||||||
NotFound(Json.obj(
|
NotFound("Game not found")
|
||||||
"status" -> "failure",
|
|
||||||
"errorMessage" -> "Game not found"
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -409,7 +253,7 @@ class IngameController @Inject() (
|
|||||||
val session = g.getUserSession(request.user.id)
|
val session = g.getUserSession(request.user.id)
|
||||||
optSession = Some(session)
|
optSession = Some(session)
|
||||||
session.lock.lock()
|
session.lock.lock()
|
||||||
g.selectTie(g.getUserSession(request.user.id), tie.toInt)
|
g.selectTie(g.getUserSession(request.user.id), tie.toInt - 1)
|
||||||
}
|
}
|
||||||
optSession.foreach(_.lock.unlock())
|
optSession.foreach(_.lock.unlock())
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
|
|||||||
@@ -188,6 +188,9 @@ class GameLobby private(
|
|||||||
*/
|
*/
|
||||||
def selectTie(userSession: UserSession, tieNumber: Int): Unit = {
|
def selectTie(userSession: UserSession, tieNumber: Int): Unit = {
|
||||||
val player = getPlayerInteractable(userSession, InteractionType.TieChoice)
|
val player = getPlayerInteractable(userSession, InteractionType.TieChoice)
|
||||||
|
val highestNumber = logic.playerTieLogic.highestAllowedNumber()
|
||||||
|
if (tieNumber < 0 || tieNumber > highestNumber)
|
||||||
|
throw new IllegalArgumentException(s"Selected number $tieNumber is out of allowed range (0 to $highestNumber)")
|
||||||
userSession.resetCanInteract()
|
userSession.resetCanInteract()
|
||||||
logic.playerTieLogic.receivedTieBreakerCard(tieNumber)
|
logic.playerTieLogic.receivedTieBreakerCard(tieNumber)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,72 @@
|
|||||||
@(player: de.knockoutwhist.player.AbstractPlayer, logic: de.knockoutwhist.control.GameLogic)
|
@(player: de.knockoutwhist.player.AbstractPlayer, logic: de.knockoutwhist.control.GameLogic, gameId: String)
|
||||||
|
|
||||||
@main("Selecting Trumpsuit...") {
|
@main("Selecting Trumpsuit...") {
|
||||||
<div id="selecttrumpsuit" class="game-field game-field-background">
|
<div id="selecttrumpsuit" class="game-field game-field-background">
|
||||||
@if(player.equals(logic.getCurrentMatch.get.roundlist.last.winner.get)) {
|
<div class="ingame-stage blur-sides">
|
||||||
<h1>Knockout Whist</h1>
|
<div class="container py-4">
|
||||||
<p>You (@player.toString) have won the last round! Select a trumpsuit for the next round!</p>
|
<div class="row justify-content-center">
|
||||||
<p>Available trumpsuits are displayed below:</p>
|
<div class="col-12">
|
||||||
<div id="playercards">
|
<div class="card shadow-sm">
|
||||||
@util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Spades))
|
<div class="card-header text-center">
|
||||||
@util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Clubs))
|
<h3 class="mb-0">Select Trump Suit</h3>
|
||||||
@util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Hearts))
|
</div>
|
||||||
@util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Diamonds))
|
<div class="card-body">
|
||||||
|
@if(player.equals(logic.getCurrentMatch.get.roundlist.last.winner.get)) {
|
||||||
|
<div class="alert alert-info" role="alert" aria-live="polite">
|
||||||
|
You (@player.toString) won the last round. Choose the trump suit for the next round.
|
||||||
</div>
|
</div>
|
||||||
<p>Your cards</p>
|
|
||||||
|
|
||||||
<div id="playercards">
|
<div class="row justify-content-center col-auto mb-5">
|
||||||
@for(card <- player.currentHand().get.cards) {
|
<div class="col-auto handcard">
|
||||||
@util.WebUIUtils.cardtoImage(card)
|
<form action="@routes.IngameController.playTrump(gameId)" 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">
|
||||||
|
@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 class="col-auto handcard">
|
||||||
|
<form action="@routes.IngameController.playTrump(gameId)" 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">
|
||||||
|
@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 class="col-auto handcard">
|
||||||
|
<form action="@routes.IngameController.playTrump(gameId)" 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">
|
||||||
|
@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 class="col-auto handcard">
|
||||||
|
<form action="@routes.IngameController.playTrump(gameId)" 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">
|
||||||
|
@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 class="row justify-content-center" id="card-slide">
|
||||||
|
@for(i <- player.currentHand().get.cards.indices) {
|
||||||
|
<div class="col-auto" style="border-radius: 6px">
|
||||||
|
@util.WebUIUtils.cardtoImage(player.currentHand().get.cards(i)) width="120px" style="border-radius: 6px"/>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
} else {
|
} else {
|
||||||
<h1>Knockout Whist</h1>
|
<div class="alert alert-warning" role="alert" aria-live="polite">
|
||||||
<p>@player.toString is choosing a trumpsuit. Starting new round when @player.toString picked a trumpsuit...</p>
|
@logic.getCurrentMatch.get.roundlist.last.winner.get.name is choosing a trumpsuit. The new round will start once a suit is picked.
|
||||||
}
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,107 @@
|
|||||||
@(player: de.knockoutwhist.player.AbstractPlayer, logic: de.knockoutwhist.control.GameLogic)
|
@(player: de.knockoutwhist.player.AbstractPlayer, logic: de.knockoutwhist.control.GameLogic, gameId: String)
|
||||||
|
|
||||||
@main("Tie") {
|
@main("Tie") {
|
||||||
<div id="tie" class="game-field game-field-background">
|
<div id="tie" class="game-field game-field-background">
|
||||||
<h1>Knockout Whist</h1>
|
<div class="ingame-stage blur-sides">
|
||||||
<p>The last Round was tied between
|
<div class="container py-4">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-12 col-md-10 col-lg-8">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header text-center">
|
||||||
|
<h3 class="mb-0">Tie Break</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<p class="card-text">
|
||||||
|
The last round was tied between:
|
||||||
|
<span class="ms-1">
|
||||||
@for(players <- logic.playerTieLogic.getTiedPlayers) {
|
@for(players <- logic.playerTieLogic.getTiedPlayers) {
|
||||||
@players
|
<span class="badge text-bg-secondary me-1">@players</span>
|
||||||
}
|
}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
@if(player.equals(logic.playerTieLogic.currentTiePlayer())) {
|
@if(player.equals(logic.playerTieLogic.currentTiePlayer())) {
|
||||||
<p>Pick a card between 1 and @logic.playerTieLogic.highestAllowedNumber()! The resulting card will be your card for the cut.</p>
|
@defining(logic.playerTieLogic.highestAllowedNumber()) { maxNum =>
|
||||||
} else {
|
<div class="alert alert-info" role="alert" aria-live="polite">
|
||||||
<p>@logic.playerTieLogic.currentTiePlayer() is currently picking his number for the cut.</p>
|
Pick a number between 1 and @{maxNum + 1}. The resulting card will be your card for the cut.
|
||||||
<p>Currently picked Cards:</p>
|
</div>
|
||||||
<div id="cardsplayed">
|
|
||||||
|
<form class="row g-2 align-items-center" method="post" action="@routes.IngameController.playTie(gameId)">
|
||||||
|
<div class="col-auto">
|
||||||
|
<label for="tieNumber" class="col-form-label">Your number</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<h6 class="mt-4 mb-3">Currently Picked Cards</h6>
|
||||||
|
|
||||||
|
<div id="cardsplayed" class="row g-3 justify-content-center">
|
||||||
|
@if(logic.playerTieLogic.getSelectedCard.nonEmpty) {
|
||||||
@for((player, card) <- logic.playerTieLogic.getSelectedCard) {
|
@for((player, card) <- logic.playerTieLogic.getSelectedCard) {
|
||||||
<div id="playedcardplayer">
|
<div class="col-6">
|
||||||
<p>@player</p>
|
<div class="card shadow-sm border-0 h-100 text-center">
|
||||||
|
<div class="card-body d-flex flex-column justify-content-between">
|
||||||
|
<p class="card-text fw-semibold mb-2 text-primary">@player</p>
|
||||||
|
<div class="card-img-top">
|
||||||
@util.WebUIUtils.cardtoImage(card)
|
@util.WebUIUtils.cardtoImage(card)
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="alert alert-info text-center" role="alert">
|
||||||
|
<i class="bi bi-info-circle me-2"></i>
|
||||||
|
No cards have been selected yet.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
<div class="alert alert-warning" role="alert" aria-live="polite">
|
||||||
|
<strong>@logic.playerTieLogic.currentTiePlayer()</strong> is currently picking a number for the cut.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h6 class="mt-4 mb-3">Currently Picked Cards</h6>
|
||||||
|
|
||||||
|
<div id="cardsplayed" class="row g-3 justify-content-center">
|
||||||
|
@if(logic.playerTieLogic.getSelectedCard.nonEmpty) {
|
||||||
|
@for((player, card) <- logic.playerTieLogic.getSelectedCard) {
|
||||||
|
<div class="col-6 col-sm-4 col-md-3 col-lg-2">
|
||||||
|
<div class="card shadow-sm border-0 h-100 text-center">
|
||||||
|
<div class="card-body d-flex flex-column justify-content-between">
|
||||||
|
<p class="card-text fw-semibold mb-2 text-primary">@player</p>
|
||||||
|
<div class="card-img-top">
|
||||||
|
@util.WebUIUtils.cardtoImage(card)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="alert alert-info text-center" role="alert">
|
||||||
|
<i class="bi bi-info-circle me-2"></i>
|
||||||
|
No cards have been selected yet.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
@main("Create Game") {
|
@main("Create Game") {
|
||||||
@navbar(user)
|
@navbar(user)
|
||||||
<main class="lobby-background flex-grow-1">
|
<main class="lobby-background flex-grow-1">
|
||||||
<div class="w-50 mx-auto">
|
<div class="w-25 mx-auto">
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<label for="lobbyname" class="form-label">Lobby-Name</label>
|
<label for="lobbyname" class="form-label">Lobby-Name</label>
|
||||||
<input type="text" class="form-control" id="lobbyname" name="lobbyname" placeholder="Lobby 1" required>
|
<input type="text" class="form-control" id="lobbyname" name="lobbyname" placeholder="Lobby 1" required>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@(user: Option[model.users.User])
|
@(user: Option[model.users.User])
|
||||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||||
<div class="container d-flex justify-content-left">
|
<div class="container d-flex justify-content-start">
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navBar" aria-controls="navBar" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navBar" aria-controls="navBar" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -2,75 +2,179 @@
|
|||||||
|
|
||||||
@main("Rules") {
|
@main("Rules") {
|
||||||
@navbar(user)
|
@navbar(user)
|
||||||
<div id="rules">
|
|
||||||
<div class="container my-4">
|
<main class="lobby-background flex-grow-1">
|
||||||
<div class="card shadow-sm rounded-3">
|
<div class="container my-4" style="max-width:980px;">
|
||||||
<div class="card-header text-white text-center">
|
<div class="card rules-card shadow-sm rounded-3 overflow-hidden">
|
||||||
<h4 class="mb-0 text-body">Game Rules Overview</h4>
|
<div class="card-header text-center py-3 border-0">
|
||||||
|
<h3 class="mb-0 rules-title">Game Rules Overview</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<div class="table-responsive">
|
<style>
|
||||||
<table class="table table-striped table-hover mb-0 align-middle">
|
|
||||||
<thead class="table-dark">
|
</style>
|
||||||
<tr>
|
|
||||||
<th scope="col">Section</th>
|
<div class="accordion rules-accordion" id="rulesAccordion">
|
||||||
<th scope="col">Details</th>
|
<div class="accordion-item">
|
||||||
</tr>
|
<h2 class="accordion-header" id="headingPlayers">
|
||||||
</thead>
|
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapsePlayers" aria-expanded="true" aria-controls="collapsePlayers">
|
||||||
<tbody>
|
Players
|
||||||
<tr>
|
</button>
|
||||||
<td>Players</td>
|
</h2>
|
||||||
<td>Two to seven players. The aim is to be the last player left in the game.</td>
|
<div id="collapsePlayers" class="accordion-collapse collapse show" aria-labelledby="headingPlayers" data-bs-parent="#rulesAccordion">
|
||||||
</tr>
|
<div class="accordion-body">
|
||||||
<tr>
|
Two to seven players. The aim is to be the last player left in the game.
|
||||||
<td>Aim</td>
|
</div>
|
||||||
<td>To be the last player left at the end of the game, with the object in each hand being to win a majority of tricks.</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
|
||||||
<td>Equipment</td>
|
<div class="accordion-item">
|
||||||
<td>A standard 52-card pack is used.</td>
|
<h2 class="accordion-header" id="headingAim">
|
||||||
</tr>
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAim" aria-expanded="false" aria-controls="collapseAim">
|
||||||
<tr>
|
Aim
|
||||||
<td>Card Ranks</td>
|
</button>
|
||||||
<td>In each suit, cards rank from highest to lowest: A K Q J 10 9 8 7 6 5 4 3 2.</td>
|
</h2>
|
||||||
</tr>
|
<div id="collapseAim" class="accordion-collapse collapse" aria-labelledby="headingAim" data-bs-parent="#rulesAccordion">
|
||||||
<tr>
|
<div class="accordion-body">
|
||||||
<td>Deal (First Hand)</td>
|
To be the last player left at the end of the game, with the object in each hand being to win a majority of tricks.
|
||||||
<td>The dealer deals seven cards to each player. One card is turned up to determine the trump suit for the round.</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<td>Deal (Subsequent Hands)</td>
|
|
||||||
<td>The deal rotates clockwise. The player who took the most tricks in the previous hand selects the trump suit. If there's a tie, players cut cards to decide who calls trumps. One fewer card is dealt in each successive hand until the final hand consists of one card each.</td>
|
<div class="accordion-item">
|
||||||
</tr>
|
<h2 class="accordion-header" id="headingEquipment">
|
||||||
<tr>
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseEquipment" aria-expanded="false" aria-controls="collapseEquipment">
|
||||||
<td>Play</td>
|
Equipment
|
||||||
<td>The player to the dealer's left (eldest hand) leads the first trick. Any card can be led. Other players must follow suit if possible. A player with no cards of the suit led may play any card.</td>
|
</button>
|
||||||
</tr>
|
</h2>
|
||||||
<tr>
|
<div id="collapseEquipment" class="accordion-collapse collapse" aria-labelledby="headingEquipment" data-bs-parent="#rulesAccordion">
|
||||||
<td>Winning a Trick</td>
|
<div class="accordion-body">
|
||||||
<td>The highest card of the suit led wins, unless a trump is played, in which case the highest trump wins. The winner of the trick leads the next.</td>
|
A standard 52-card pack is used.
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<td>Leading Trumps</td>
|
</div>
|
||||||
<td>Some rules disallow leading trumps before the suit has been 'broken' (a trump has been played to the lead of another suit). Leading trumps is always permissible if a player holds only trumps.</td>
|
|
||||||
</tr>
|
<div class="accordion-item">
|
||||||
<tr>
|
<h2 class="accordion-header" id="headingRanks">
|
||||||
<td>Knockout</td>
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseRanks" aria-expanded="false" aria-controls="collapseRanks">
|
||||||
<td>At the end of each hand, any player who took no tricks is knocked out and takes no further part in the game.</td>
|
Card Ranks
|
||||||
</tr>
|
</button>
|
||||||
<tr>
|
</h2>
|
||||||
<td>Winning the Game</td>
|
<div id="collapseRanks" class="accordion-collapse collapse" aria-labelledby="headingRanks" data-bs-parent="#rulesAccordion">
|
||||||
<td>The game is won when a player takes all the tricks in a round, as all other players are knocked out, leaving only one player remaining.</td>
|
<div class="accordion-body">
|
||||||
</tr>
|
In each suit, cards rank from highest to lowest: A K Q J 10 9 8 7 6 5 4 3 2.
|
||||||
<tr>
|
</div>
|
||||||
<td>Dog Life</td>
|
</div>
|
||||||
<td>The first player who takes no tricks is awarded a "dog's life". In the next hand, that player is dealt one card and can decide which trick to play it to. Each time a trick is played the "dog" may either play the card or knock on the table and wait to play it later. If the dog wins a trick, the player to the left leads the next and the dog re-enters the game properly in the next hand. If the dog fails, they are knocked out.</td>
|
</div>
|
||||||
</tr>
|
|
||||||
</tbody>
|
<div class="accordion-item">
|
||||||
</table>
|
<h2 class="accordion-header" id="headingDealFirst">
|
||||||
</div>
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseDealFirst" aria-expanded="false" aria-controls="collapseDealFirst">
|
||||||
|
Deal (First Hand)
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseDealFirst" class="accordion-collapse collapse" aria-labelledby="headingDealFirst" data-bs-parent="#rulesAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
The dealer deals seven cards to each player. One card is turned up to determine the trump suit for the round.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingDealSubsequent">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseDealSubsequent" aria-expanded="false" aria-controls="collapseDealSubsequent">
|
||||||
|
Deal (Subsequent Hands)
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseDealSubsequent" class="accordion-collapse collapse" aria-labelledby="headingDealSubsequent" data-bs-parent="#rulesAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
The deal rotates clockwise. The player who took the most tricks in the previous hand selects the trump suit. If there's a tie, players cut cards to decide who calls trumps. One fewer card is dealt in each successive hand until the final hand consists of one card each.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingPlay">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapsePlay" aria-expanded="false" aria-controls="collapsePlay">
|
||||||
|
Play
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapsePlay" class="accordion-collapse collapse" aria-labelledby="headingPlay" data-bs-parent="#rulesAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
The player to the dealer's left (eldest hand) leads the first trick. Any card can be led. Other players must follow suit if possible. A player with no cards of the suit led may play any card.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingWinningTrick">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseWinningTrick" aria-expanded="false" aria-controls="collapseWinningTrick">
|
||||||
|
Winning a Trick
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseWinningTrick" class="accordion-collapse collapse" aria-labelledby="headingWinningTrick" data-bs-parent="#rulesAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
The highest card of the suit led wins, unless a trump is played, in which case the highest trump wins. The winner of the trick leads the next.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingLeadingTrumps">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseLeadingTrumps" aria-expanded="false" aria-controls="collapseLeadingTrumps">
|
||||||
|
Leading Trumps
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseLeadingTrumps" class="accordion-collapse collapse" aria-labelledby="headingLeadingTrumps" data-bs-parent="#rulesAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
Some rules disallow leading trumps before the suit has been 'broken' (a trump has been played to the lead of another suit). Leading trumps is always permissible if a player holds only trumps.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingKnockout">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseKnockout" aria-expanded="false" aria-controls="collapseKnockout">
|
||||||
|
Knockout
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseKnockout" class="accordion-collapse collapse" aria-labelledby="headingKnockout" data-bs-parent="#rulesAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
At the end of each hand, any player who took no tricks is knocked out and takes no further part in the game.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingWinningGame">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseWinningGame" aria-expanded="false" aria-controls="collapseWinningGame">
|
||||||
|
Winning the Game
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseWinningGame" class="accordion-collapse collapse" aria-labelledby="headingWinningGame" data-bs-parent="#rulesAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
The game is won when a player takes all the tricks in a round, as all other players are knocked out, leaving only one player remaining.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingDogLife">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseDogLife" aria-expanded="false" aria-controls="collapseDogLife">
|
||||||
|
Dog Life
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseDogLife" class="accordion-collapse collapse" aria-labelledby="headingDogLife" data-bs-parent="#rulesAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
The first player who takes no tricks is awarded a \"dog's life\". In the next hand, that player is dealt one card and can decide which trick to play it to. Each time a trick is played the dog may either play the card or knock on the table and wait to play it later. If the dog wins a trick, the player to the left leads the next and the dog re-enters the game properly in the next hand. If the dog fails, they are knocked out.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</main>
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,10 @@ GET /game/:id controllers.IngameController.game(id: String
|
|||||||
GET /game/:id/join controllers.IngameController.joinGame(id: String)
|
GET /game/:id/join controllers.IngameController.joinGame(id: String)
|
||||||
GET /game/:id/start controllers.IngameController.startGame(id: String)
|
GET /game/:id/start controllers.IngameController.startGame(id: String)
|
||||||
POST /game/:id/kickPlayer controllers.IngameController.kickPlayer(id: String, playerId: String)
|
POST /game/:id/kickPlayer controllers.IngameController.kickPlayer(id: String, playerId: String)
|
||||||
|
|
||||||
|
POST /game/:id/trump controllers.IngameController.playTrump(id: String)
|
||||||
|
POST /game/:id/tie controllers.IngameController.playTie(id: String)
|
||||||
|
|
||||||
GET /game/:id/leaveGame controllers.IngameController.leaveGame(id: String)
|
GET /game/:id/leaveGame controllers.IngameController.leaveGame(id: String)
|
||||||
POST /game/:id/playCard controllers.IngameController.playCard(id: String)
|
POST /game/:id/playCard controllers.IngameController.playCard(id: String)
|
||||||
# Polling
|
# Polling
|
||||||
|
|||||||
Reference in New Issue
Block a user