feat(ui): add Lobby and Main Menu Body #38

Merged
Janis merged 7 commits from feat/31-mainmenu-body into main 2025-11-06 09:03:09 +01:00
7 changed files with 154 additions and 17 deletions
Showing only changes of commit 89d1626bb2 - Show all commits

View File

@@ -8,6 +8,7 @@ import model.sessions.{PlayerSession, UserSession}
import play.api.* import play.api.*
import play.api.mvc.* import play.api.mvc.*
import java.util.UUID
import javax.inject.* import javax.inject.*
import scala.util.Try import scala.util.Try
@@ -28,7 +29,7 @@ class IngameController @Inject()(
game match { game match {
case Some(g) => case Some(g) =>
g.logic.getCurrentState match { g.logic.getCurrentState match {
case Lobby => Ok("Lobby: " + gameId) case Lobby => Ok(views.html.lobby.lobby(Some(request.user), g))
case InGame => case InGame =>
Ok(views.html.ingame.ingame( Ok(views.html.ingame.ingame(
g.getPlayerByUser(request.user), g.getPlayerByUser(request.user),
@@ -63,7 +64,7 @@ class IngameController @Inject()(
} }
} }
if (result.isSuccess) { if (result.isSuccess) {
NoContent Redirect(routes.IngameController.game(gameId))
} else { } else {
val throwable = result.failed.get val throwable = result.failed.get
throwable match { 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] => def joinGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
val game = podManager.getGame(gameId) val game = podManager.getGame(gameId)
val result = Try { val result = Try {

View File

@@ -21,7 +21,7 @@ class MainMenuController @Inject()(
// Pass the request-handling function directly to authAction (no nested Action) // Pass the request-handling function directly to authAction (no nested Action)
def mainMenu(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => 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] => 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] => def createGame(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
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( val gameLobby = podManager.createGame(
host = request.user, host = request.user,
name = s"${request.user.name}'s Game", name = gamename,
maxPlayers = 4 maxPlayers = playeramount.toInt
) )
Redirect(routes.IngameController.game(gameLobby.id)) Redirect(routes.IngameController.game(gameLobby.id))
} else {
BadRequest("Invalid form submission")
}
} }
def joinGame(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => def joinGame(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>

View File

@@ -88,12 +88,12 @@ class GameLobby private(
* Remove the user from the game lobby. * Remove the user from the game lobby.
* @param user the user who wants to leave the game. * @param user the user who wants to leave the game.
*/ */
def leaveGame(user: User): Unit = { def leaveGame(userId: UUID): Unit = {
val sessionOpt = users.get(user.id) val sessionOpt = users.get(userId)
if (sessionOpt.isEmpty) { if (sessionOpt.isEmpty) {
throw new NotInThisGameException("You are not in this game!") throw new NotInThisGameException("You are not in this game!")
} }
users.remove(user.id) users.remove(userId)
} }
/** /**
@@ -176,6 +176,10 @@ class GameLobby private(
getPlayerBySession(getUserSession(user.id)) getPlayerBySession(getUserSession(user.id))
} }
def getPlayers: mutable.Map[UUID, UserSession] = {
users.clone()
}
private def getPlayerBySession(userSession: UserSession): AbstractPlayer = { private def getPlayerBySession(userSession: UserSession): AbstractPlayer = {
val playerOption = getMatch.totalplayers.find(_.id == userSession.id) val playerOption = getMatch.totalplayers.find(_.id == userSession.id)
if (playerOption.isEmpty) { if (playerOption.isEmpty) {

View File

@@ -0,0 +1,84 @@
@(user: Option[model.users.User], gamelobby: logic.game.GameLobby)
@main("Lobby") {
<div class="container">
<div class="row">
<div class="col">
<div class="p-3 fs-1 d-flex align-items-center">
<div class="text-center" style="flex-grow: 1;">
Lobby-Name: @gamelobby.name
</div>
<form action="@(routes.IngameController.leaveGame(gamelobby.id))">
<button type="submit" class="btn btn-danger ms-auto">Exit</button>
</form>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="p-3 text-center fs-4">Playeramount: @gamelobby.getPlayers.size / @gamelobby.maxPlayers</div>
</div>
</div>
<div class="row justify-content-center">
@if((gamelobby.getUserSession(user.get.id).host)) {
@for(playersession <- gamelobby.getPlayers.values) {
<div class="col-auto">
<div class="card" style="width: 18rem;">
<img src="@routes.Assets.versioned("images/profile.png")" alt="Profile" class="card-img-top w-50 mx-auto mt-3" />
<div class="card-body">
@if(playersession.id == user.get.id) {
<h5 class="card-title">@playersession.name (You)</h5>
<p class="card-text">Your text could be here!</p>
<a href="#" class="btn btn-danger disabled" aria-disabled="true" tabindex="-1">Remove</a>
} else {
<h5 class="card-title">@playersession.name</h5>
<p class="card-text">Your text could be here!</p>
<form action="@(routes.IngameController.kickPlayer(gamelobby.id, playersession.id))" method="post">
<button type="submit" class="btn btn-danger">Remove</button>
</form>
}
</div>
</div>
</div>
}
<div class="row">
<div class="col text-center mt-3">
<a href="@(routes.IngameController.startGame(gamelobby.id))" class="btn btn-success">Start Game</a>
</div>
</div>
} else {
@for(playersession <- gamelobby.getPlayers.values) {
<div class="col-auto">
<div class="card" style="width: 18rem;">
<img src="@routes.Assets.versioned("images/profile.png")" alt="Profile" class="card-img-top w-50 mx-auto mt-3" />
<div class="card-body">
@if(playersession.id == user.get.id) {
<h5 class="card-title">@playersession.name (You)</h5>
<p class="card-text">Your text could be here!</p>
} else {
<h5 class="card-title">@playersession.name</h5>
<p class="card-text">Your text could be here!</p>
}
</div>
</div>
</div>
}
<div class="row">
<div class="col mt-3">
<p class="text-center fs-4">Waiting for the host to start the game...</p>
</div>
</div>
<div class="row">
<div class="col mt-1">
<div class="text-center">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</div>
}
</div>
</div>
}

View File

@@ -0,0 +1,31 @@
@(user: Option[model.users.User])
@navbar(user)
@main("Create Game") {
<form action="@routes.MainMenuController.createGame()" method="post">
<div class="w-50 mx-auto">
<div class="mt-3">
<label for="lobbyname" class="form-label">Lobby-Name</label>
<input type="text" class="form-control" id="lobbyname" name="lobbyname" placeholder="Lobby 1" required>
</div>
<div class="form-check form-switch mt-3">
<input class="form-check-input" type="checkbox" id="visibilityswitch" disabled>
<label class="form-check-label" for="visibilityswitch">public/private</label>
</div>
<div class="mt-3">
<label for="playeramount" class="form-label">Playeramount:</label>
<input type="range" class="form-range" min="2" max="7" value="2" id="playeramount" name="playeramount">
<div class="d-flex justify-content-between">
<span>2</span>
<span>3</span>
<span>4</span>
<span>5</span>
<span>6</span>
<span>7</span>
</div>
</div>
<div class="mt-3 text-center">
<button type="submit" class="btn btn-success">Create Game</button>
</div>
</div>
</form>
}

View File

@@ -1,5 +1,4 @@
@(user: Option[model.users.User]) @(user: Option[model.users.User])
@main("Knockout Whist - Main Menu") {
<nav class="navbar navbar-expand-lg bg-body-tertiary"> <nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid"> <div class="container-fluid">
<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">
@@ -53,4 +52,3 @@
</div> </div>
</div> </div>
</nav> </nav>
}

View File

@@ -24,6 +24,7 @@ GET /logout controllers.UserController.logout()
# In-game routes # In-game routes
GET /game/:id controllers.IngameController.game(id: String) 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)
POST /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: java.util.UUID)
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)