diff --git a/knockoutwhistweb/app/assets/stylesheets/dark-mode.less b/knockoutwhistweb/app/assets/stylesheets/dark-mode.less index 6249ca9..17b7f1a 100644 --- a/knockoutwhistweb/app/assets/stylesheets/dark-mode.less +++ b/knockoutwhistweb/app/assets/stylesheets/dark-mode.less @@ -1,6 +1,13 @@ @media (prefers-color-scheme: dark) { :root { --background-image: url('/assets/images/background.png'); - --color: white; + --color: #f8f9fa; /* Light text on dark bg */ + + /* Bootstrap variable overrides for dark mode */ + --bs-body-color: var(--color); + --bs-link-color: #66b2ff; + --bs-link-hover-color: #99ccff; + --bs-border-color: rgba(255, 255, 255, 0.2); + --bs-heading-color: var(--color); } } diff --git a/knockoutwhistweb/app/assets/stylesheets/main.less b/knockoutwhistweb/app/assets/stylesheets/main.less index 8bc3b69..b723d4c 100644 --- a/knockoutwhistweb/app/assets/stylesheets/main.less +++ b/knockoutwhistweb/app/assets/stylesheets/main.less @@ -2,6 +2,19 @@ @import "dark-mode.less"; @import "login.less"; +/* Provide default (light) variables so the site works even if light-mode.less fails */ +:root { + --background-image: url('/assets/images/img.png'); + --color: #212529; /* Bootstrap body text default */ + + /* Bootstrap variable overrides for light mode */ + --bs-body-color: var(--color) !important; + --bs-link-color: #0d6efd !important; + --bs-link-hover-color: #0a58ca !important; + --bs-border-color: rgba(0, 0, 0, 0.125) !important; + --bs-heading-color: var(--color) !important; +} + @background-image: var(--background-image); @color: var(--color); @keyframes slideIn { @@ -10,8 +23,33 @@ } .game-field-background { background-image: @background-image; - background-size: 100vw 100vh; + background-size: cover; + background-position: center center; background-repeat: no-repeat; + background-attachment: fixed; +} + +.navbar-header{ + text-align:center; +} + +.navbar-toggle { + float: none; + margin-right:0; +} + +/* Ensure body text color follows theme variable and works with Bootstrap */ +body { + color: @color; +} + +.footer { + width: 100%; + text-align: center; + font-size: 12px; + color: @color; + padding: 0.5rem 0; + flex-grow: 1; /* fill remaining vertical space as visual footer background */ } .game-field { @@ -19,6 +57,7 @@ inset: 0; overflow: auto; } + #sessions { display: flex; flex-direction: column; @@ -70,8 +109,22 @@ &:nth-child(7) { animation-delay: 3.5s; } } } +#card-slide { + div { + animation: slideIn 0.5s ease-out forwards; + animation-fill-mode: backwards; + &:nth-child(1) { animation-delay: 0.5s; } + &:nth-child(2) { animation-delay: 1s; } + &:nth-child(3) { animation-delay: 1.5s; } + &:nth-child(4) { animation-delay: 2s; } + &:nth-child(5) { animation-delay: 2.5s; } + &:nth-child(6) { animation-delay: 3s; } + &:nth-child(7) { animation-delay: 3.5s; } + } +} + #cardsplayed { display: flex; flex-direction: row; diff --git a/knockoutwhistweb/app/controllers/IngameController.scala b/knockoutwhistweb/app/controllers/IngameController.scala index 59749c3..373701d 100644 --- a/knockoutwhistweb/app/controllers/IngameController.scala +++ b/knockoutwhistweb/app/controllers/IngameController.scala @@ -8,6 +8,7 @@ import model.sessions.{PlayerSession, UserSession} import play.api.* import play.api.mvc.* +import java.util.UUID import javax.inject.* import scala.util.Try @@ -28,11 +29,11 @@ class IngameController @Inject()( game match { case Some(g) => g.logic.getCurrentState match { - case Lobby => Ok("Lobby: " + gameId) + case Lobby => Ok(views.html.lobby.lobby(Some(request.user), g)) case InGame => Ok(views.html.ingame.ingame( g.getPlayerByUser(request.user), - g.logic + g )) case SelectTrump => Ok(views.html.ingame.selecttrump( @@ -63,7 +64,7 @@ class IngameController @Inject()( } } if (result.isSuccess) { - NoContent + Redirect(routes.IngameController.game(gameId)) } else { val throwable = result.failed.get throwable match { @@ -78,6 +79,16 @@ class IngameController @Inject()( } } } + def kickPlayer(gameId: String, playerToKick: UUID): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => + val game = podManager.getGame(gameId) + game.get.leaveGame(playerToKick) + Redirect(routes.IngameController.game(gameId)) + } + def leaveGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => + val game = podManager.getGame(gameId) + game.get.leaveGame(request.user.id) + Redirect(routes.MainMenuController.mainMenu()) + } def joinGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => val game = podManager.getGame(gameId) val result = Try { diff --git a/knockoutwhistweb/app/controllers/MainMenuController.scala b/knockoutwhistweb/app/controllers/MainMenuController.scala index 7032b52..55c4a1b 100644 --- a/knockoutwhistweb/app/controllers/MainMenuController.scala +++ b/knockoutwhistweb/app/controllers/MainMenuController.scala @@ -21,7 +21,7 @@ class MainMenuController @Inject()( // Pass the request-handling function directly to authAction (no nested Action) def mainMenu(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => - Ok(views.html.mainmenu.navbar(Some(request.user))) + Ok(views.html.mainmenu.creategame(Some(request.user))) } def index(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => @@ -29,12 +29,20 @@ class MainMenuController @Inject()( } def createGame(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => - val gameLobby = podManager.createGame( - host = request.user, - name = s"${request.user.name}'s Game", - maxPlayers = 4 - ) - Redirect(routes.IngameController.game(gameLobby.id)) + val postData = request.body.asFormUrlEncoded + if (postData.isDefined) { + val gamename = postData.get.get("lobbyname").flatMap(_.headOption).getOrElse(s"${request.user.name}'s Game") + val playeramount = postData.get.get("playeramount").flatMap(_.headOption).getOrElse("") + val gameLobby = podManager.createGame( + host = request.user, + name = gamename, + maxPlayers = playeramount.toInt + ) + Redirect(routes.IngameController.game(gameLobby.id)) + } else { + BadRequest("Invalid form submission") + } + } def joinGame(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => @@ -53,9 +61,7 @@ class MainMenuController @Inject()( } } - def rules(): Action[AnyContent] = { - Action { implicit request => - Ok(views.html.mainmenu.rules()) - } + def rules(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => + Ok(views.html.mainmenu.rules(Some(request.user))) } } \ No newline at end of file diff --git a/knockoutwhistweb/app/logic/game/GameLobby.scala b/knockoutwhistweb/app/logic/game/GameLobby.scala index 6743bdb..486bf4f 100644 --- a/knockoutwhistweb/app/logic/game/GameLobby.scala +++ b/knockoutwhistweb/app/logic/game/GameLobby.scala @@ -88,12 +88,12 @@ class GameLobby private( * Remove the user from the game lobby. * @param user the user who wants to leave the game. */ - def leaveGame(user: User): Unit = { - val sessionOpt = users.get(user.id) + def leaveGame(userId: UUID): Unit = { + val sessionOpt = users.get(userId) if (sessionOpt.isEmpty) { throw new NotInThisGameException("You are not in this game!") } - users.remove(user.id) + users.remove(userId) } /** @@ -176,6 +176,14 @@ class GameLobby private( getPlayerBySession(getUserSession(user.id)) } + def getPlayers: mutable.Map[UUID, UserSession] = { + users.clone() + } + + def getLogic: GameLogic = { + logic + } + private def getPlayerBySession(userSession: UserSession): AbstractPlayer = { val playerOption = getMatch.totalplayers.find(_.id == userSession.id) if (playerOption.isEmpty) { diff --git a/knockoutwhistweb/app/logic/user/impl/StubUserManager.scala b/knockoutwhistweb/app/logic/user/impl/StubUserManager.scala index 44f71ee..398a908 100644 --- a/knockoutwhistweb/app/logic/user/impl/StubUserManager.scala +++ b/knockoutwhistweb/app/logic/user/impl/StubUserManager.scala @@ -22,6 +22,12 @@ class StubUserManager @Inject()(val config: Config) extends UserManager { id = java.util.UUID.fromString("223e4567-e89b-12d3-a456-426614174000"), name = "Leon", passwordHash = UserHash.hashPW("password123") + ), + "Jakob" -> User( + internalId = 2L, + id = java.util.UUID.fromString("323e4567-e89b-12d3-a456-426614174000"), + name = "Jakob", + passwordHash = UserHash.hashPW("password123") ) ) diff --git a/knockoutwhistweb/app/views/ingame/ingame.scala.html b/knockoutwhistweb/app/views/ingame/ingame.scala.html index f587dbc..4dc9f97 100644 --- a/knockoutwhistweb/app/views/ingame/ingame.scala.html +++ b/knockoutwhistweb/app/views/ingame/ingame.scala.html @@ -1,50 +1,71 @@ -@(player: de.knockoutwhist.player.AbstractPlayer, logic: de.knockoutwhist.control.GameLogic) +@import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.TrickUtil + +@(player: de.knockoutwhist.player.AbstractPlayer, gamelobby: logic.game.GameLobby) @main("Ingame") { -
-

Knockout Whist

-
-

Next Player:

-

@logic.getPlayerQueue.get.duplicate().nextPlayer()

-
-
-
-

Trumpsuit:

-

@logic.getCurrentRound.get.trumpSuit

+
+ + +
+
+

Current Player

+

@gamelobby.getLogic.getCurrentPlayer.get.name

+ @if(!TrickUtil.isOver(gamelobby.getLogic.getCurrentMatch.get, gamelobby.getLogic.getPlayerQueue.get)) { +

Next Player

+ @for(nextplayer <- gamelobby.getLogic.getPlayerQueue.get.duplicate()) { +

@nextplayer

+ } + } +
+ + +
+ +
+ +
+

Trumpsuit

+

@gamelobby.getLogic.getCurrentRound.get.trumpSuit

+ +
First Card
+
+ @if(gamelobby.getLogic.getCurrentTrick.get.firstCard.isDefined) { + @util.WebUIUtils.cardtoImage(gamelobby.getLogic.getCurrentTrick.get.firstCard.get) width="80px"/> + } else { + @views.html.render.card.apply("images/cards/1B.png")("Blank Card") width="80px"/> + } +
+
-
-

First Card

- @if(logic.getCurrentTrick.get.firstCard.isDefined) { - @util.WebUIUtils.cardtoImage(logic.getCurrentTrick.get.firstCard.get) - } else { - @views.html.render.card.apply("images/cards/1B.png")("Blank Card") + + +
+ @for((cardplayed, player) <- gamelobby.getLogic.getCurrentTrick.get.cards) { +
+
+
+ @util.WebUIUtils.cardtoImage(cardplayed) width="100%"/> +
+
+ @player +
+
+
+ } +
+ + +
+ @for(i <- player.currentHand().get.cards.indices) { +
+
+ + +
+
}
- -

@logic.getCurrentPlayer.get has to play a card!

- @if(logic.getCurrentTrick.get.cards.nonEmpty) { -

Cards played

- } else { -

Cards played

- } - -
- @for((cardplayed, player) <- logic.getCurrentTrick.get.cards) { -
-

@player

- @util.WebUIUtils.cardtoImage(cardplayed) -
- } -
- -

Your cards

-
- @for(card <- player.currentHand().get.cards) { - @util.WebUIUtils.cardtoImage(card) - } -
- - -
-} \ No newline at end of file +} diff --git a/knockoutwhistweb/app/views/lobby/lobby.scala.html b/knockoutwhistweb/app/views/lobby/lobby.scala.html new file mode 100644 index 0000000..090b1a7 --- /dev/null +++ b/knockoutwhistweb/app/views/lobby/lobby.scala.html @@ -0,0 +1,84 @@ +@(user: Option[model.users.User], gamelobby: logic.game.GameLobby) + +@main("Lobby") { +
+
+
+
+
+ Lobby-Name: @gamelobby.name +
+
+ +
+
+
+
+
+
+
Playeramount: @gamelobby.getPlayers.size / @gamelobby.maxPlayers
+
+
+
+ + @if((gamelobby.getUserSession(user.get.id).host)) { + @for(playersession <- gamelobby.getPlayers.values) { +
+
+ Profile +
+ @if(playersession.id == user.get.id) { +
@playersession.name (You)
+

Your text could be here!

+ Remove + } else { +
@playersession.name
+

Your text could be here!

+
+ +
+ } +
+
+
+ } +
+ +
+ } else { + @for(playersession <- gamelobby.getPlayers.values) { +
+
+ Profile +
+ @if(playersession.id == user.get.id) { +
@playersession.name (You)
+

Your text could be here!

+ } else { +
@playersession.name
+

Your text could be here!

+ } +
+
+
+ } +
+
+

Waiting for the host to start the game...

+
+
+
+
+
+
+ Loading... +
+
+
+
+ } +
+
+} \ No newline at end of file diff --git a/knockoutwhistweb/app/views/login/login.scala.html b/knockoutwhistweb/app/views/login/login.scala.html index 219707e..60aa0e4 100644 --- a/knockoutwhistweb/app/views/login/login.scala.html +++ b/knockoutwhistweb/app/views/login/login.scala.html @@ -1,20 +1,21 @@ @() @main("Login") { +