feat!: implemented multigame support #34
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: Login
|
||||
type: http
|
||||
seq: 1
|
||||
seq: 2
|
||||
}
|
||||
|
||||
post {
|
||||
@@ -22,4 +22,5 @@ body:multipart-form {
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ class AuthAction @Inject()(val sessionManager: SessionManager, val parser: BodyP
|
||||
|
||||
override def executionContext: ExecutionContext = ec
|
||||
|
||||
// This simulates checking if a user is logged in (e.g. via session)
|
||||
private def getUserFromSession(request: RequestHeader): Option[User] = {
|
||||
val session = request.cookies.get("sessionId")
|
||||
if (session.isDefined)
|
||||
@@ -23,7 +22,6 @@ class AuthAction @Inject()(val sessionManager: SessionManager, val parser: BodyP
|
||||
None
|
||||
}
|
||||
|
||||
// Transform a normal request into an AuthenticatedRequest
|
||||
override def invokeBlock[A](
|
||||
request: Request[A],
|
||||
block: AuthenticatedRequest[A] => Future[Result]
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package controllers
|
||||
|
||||
import auth.{AuthAction, AuthenticatedRequest}
|
||||
import logic.user.{SessionManager, UserManager}
|
||||
import de.knockoutwhist.control.GameState.{InGame, Lobby, SelectTrump, TieBreak}
|
||||
import exceptions.{CantPlayCardException, GameFullException, NotEnoughPlayersException, NotHostException, NotInThisGameException}
|
||||
import logic.PodManager
|
||||
import play.api.*
|
||||
import play.api.mvc.*
|
||||
|
||||
import javax.inject.*
|
||||
import scala.util.Try
|
||||
|
||||
|
||||
/**
|
||||
@@ -14,12 +17,228 @@ import javax.inject.*
|
||||
*/
|
||||
@Singleton
|
||||
class IngameController @Inject()(
|
||||
val controllerComponents: ControllerComponents,
|
||||
val authAction: AuthAction
|
||||
) extends BaseController {
|
||||
val controllerComponents: ControllerComponents,
|
||||
val authAction: AuthAction,
|
||||
val podManager: PodManager
|
||||
) extends BaseController {
|
||||
|
||||
def mainMenu(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||
Ok("Main Menu for user: " + request.user.name)
|
||||
def game(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||
val game = podManager.getGame(gameId)
|
||||
game match {
|
||||
case Some(g) =>
|
||||
g.logic.getCurrentState match {
|
||||
case Lobby => Ok("Lobby: " + gameId)
|
||||
case InGame =>
|
||||
Ok(views.html.ingame.ingame(
|
||||
g.getPlayerByUser(request.user),
|
||||
g.logic
|
||||
))
|
||||
case SelectTrump =>
|
||||
Ok(views.html.ingame.selecttrump(
|
||||
g.getPlayerByUser(request.user),
|
||||
g.logic
|
||||
))
|
||||
case TieBreak =>
|
||||
Ok(views.html.ingame.tie(
|
||||
g.getPlayerByUser(request.user),
|
||||
g.logic
|
||||
))
|
||||
case _ =>
|
||||
InternalServerError(s"Invalid game state for in-game view. GameId: $gameId" + s" State: ${g.logic.getCurrentState}")
|
||||
}
|
||||
case None =>
|
||||
NotFound("Game not found")
|
||||
}
|
||||
//NotFound(s"Reached end of game method unexpectedly. GameId: $gameId")
|
||||
}
|
||||
def startGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||
val game = podManager.getGame(gameId)
|
||||
val result = Try {
|
||||
game match {
|
||||
case Some(g) =>
|
||||
g.startGame(request.user)
|
||||
case None =>
|
||||
NotFound("Game not found")
|
||||
}
|
||||
}
|
||||
if (result.isSuccess) {
|
||||
NoContent
|
||||
} else {
|
||||
val throwable = result.failed.get
|
||||
throwable match {
|
||||
case _: NotInThisGameException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _: NotHostException =>
|
||||
Forbidden(throwable.getMessage)
|
||||
case _: NotEnoughPlayersException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _ =>
|
||||
InternalServerError(throwable.getMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
def joinGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||
val game = podManager.getGame(gameId)
|
||||
val result = Try {
|
||||
game match {
|
||||
case Some(g) =>
|
||||
g.addUser(request.user)
|
||||
case None =>
|
||||
NotFound("Game not found")
|
||||
}
|
||||
}
|
||||
if (result.isSuccess) {
|
||||
Redirect(routes.IngameController.game(gameId))
|
||||
} else {
|
||||
val throwable = result.failed.get
|
||||
throwable match {
|
||||
case _: GameFullException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _: IllegalArgumentException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _: IllegalStateException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _ =>
|
||||
InternalServerError(throwable.getMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
def playCard(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => {
|
||||
val game = podManager.getGame(gameId)
|
||||
game match {
|
||||
case Some(g) =>
|
||||
val cardIdOpt = request.body.asFormUrlEncoded.flatMap(_.get("cardId").flatMap(_.headOption))
|
||||
cardIdOpt match {
|
||||
case Some(cardId) =>
|
||||
val result = Try {
|
||||
g.playCard(g.getUserSession(request.user.id), cardId.toInt)
|
||||
}
|
||||
if (result.isSuccess) {
|
||||
NoContent
|
||||
} else {
|
||||
val throwable = result.failed.get
|
||||
throwable match {
|
||||
case _: CantPlayCardException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _: NotInThisGameException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _: IllegalArgumentException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _: IllegalStateException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _ =>
|
||||
InternalServerError(throwable.getMessage)
|
||||
}
|
||||
}
|
||||
case None =>
|
||||
BadRequest("cardId parameter is missing")
|
||||
}
|
||||
case None =>
|
||||
NotFound("Game not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
def playDogCard(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => {
|
||||
val game = podManager.getGame(gameId)
|
||||
game match {
|
||||
case Some(g) => {
|
||||
val cardIdOpt = request.body.asFormUrlEncoded.flatMap(_.get("cardId").flatMap(_.headOption))
|
||||
val result = Try {
|
||||
cardIdOpt match {
|
||||
case Some(cardId) if cardId == "skip" =>
|
||||
g.playDogCard(g.getUserSession(request.user.id), -1)
|
||||
case Some(cardId) =>
|
||||
g.playDogCard(g.getUserSession(request.user.id), cardId.toInt)
|
||||
case None =>
|
||||
throw new IllegalArgumentException("cardId parameter is missing")
|
||||
}
|
||||
}
|
||||
if (result.isSuccess) {
|
||||
NoContent
|
||||
} else {
|
||||
val throwable = result.failed.get
|
||||
throwable match {
|
||||
case _: CantPlayCardException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _: NotInThisGameException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _: IllegalArgumentException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _: IllegalStateException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _ =>
|
||||
InternalServerError(throwable.getMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
case None =>
|
||||
NotFound("Game not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
def playTrump(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||
val game = podManager.getGame(gameId)
|
||||
game match {
|
||||
case Some(g) =>
|
||||
val trumpOpt = request.body.asFormUrlEncoded.flatMap(_.get("trump").flatMap(_.headOption))
|
||||
trumpOpt match {
|
||||
case Some(trump) =>
|
||||
val result = Try {
|
||||
g.selectTrump(g.getUserSession(request.user.id), trump.toInt)
|
||||
}
|
||||
if (result.isSuccess) {
|
||||
NoContent
|
||||
} else {
|
||||
val throwable = result.failed.get
|
||||
throwable match {
|
||||
case _: IllegalArgumentException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _: NotInThisGameException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _: IllegalStateException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _ =>
|
||||
InternalServerError(throwable.getMessage)
|
||||
}
|
||||
}
|
||||
case None =>
|
||||
BadRequest("trump parameter is missing")
|
||||
}
|
||||
case None =>
|
||||
NotFound("Game not found")
|
||||
}
|
||||
}
|
||||
def playTie(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||
val game = podManager.getGame(gameId)
|
||||
game match {
|
||||
case Some(g) =>
|
||||
val tieOpt = request.body.asFormUrlEncoded.flatMap(_.get("tie").flatMap(_.headOption))
|
||||
tieOpt match {
|
||||
case Some(tie) =>
|
||||
val result = Try {
|
||||
g.selectTie(g.getUserSession(request.user.id), tie.toInt)
|
||||
}
|
||||
if (result.isSuccess) {
|
||||
NoContent
|
||||
} else {
|
||||
val throwable = result.failed.get
|
||||
throwable match {
|
||||
case _: IllegalArgumentException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _: NotInThisGameException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _: IllegalStateException =>
|
||||
BadRequest(throwable.getMessage)
|
||||
case _ =>
|
||||
InternalServerError(throwable.getMessage)
|
||||
}
|
||||
}
|
||||
case None =>
|
||||
BadRequest("tie parameter is missing")
|
||||
}
|
||||
case None =>
|
||||
NotFound("Game not found")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package controllers
|
||||
|
||||
import auth.{AuthAction, AuthenticatedRequest}
|
||||
import logic.PodManager
|
||||
import play.api.*
|
||||
import play.api.mvc.*
|
||||
|
||||
@@ -14,7 +15,8 @@ import javax.inject.*
|
||||
@Singleton
|
||||
class MainMenuController @Inject()(
|
||||
val controllerComponents: ControllerComponents,
|
||||
val authAction: AuthAction
|
||||
val authAction: AuthAction,
|
||||
val podManager: PodManager
|
||||
) extends BaseController {
|
||||
|
||||
// Pass the request-handling function directly to authAction (no nested Action)
|
||||
@@ -23,13 +25,21 @@ class MainMenuController @Inject()(
|
||||
}
|
||||
|
||||
def index(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||
Redirect("/mainmenu")
|
||||
Redirect(routes.MainMenuController.mainMenu())
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
def rules(): Action[AnyContent] = {
|
||||
Action { implicit request =>
|
||||
Ok(views.html.rules())
|
||||
Ok(views.html.mainmenu.rules())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,10 +28,10 @@ class UserController @Inject()(
|
||||
if (possibleUser.isDefined) {
|
||||
Redirect(routes.MainMenuController.mainMenu())
|
||||
} else {
|
||||
Ok(views.html.login())
|
||||
Ok(views.html.login.login())
|
||||
}
|
||||
} else {
|
||||
Ok(views.html.login())
|
||||
Ok(views.html.login.login())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package exceptions;
|
||||
|
||||
public class NotEnoughPlayersException extends GameException {
|
||||
public NotEnoughPlayersException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
|
||||
import di.KnockOutWebConfigurationModule
|
||||
import logic.game.GameLobby
|
||||
import model.users.User
|
||||
import util.GameUtil
|
||||
|
||||
import javax.inject.Singleton
|
||||
import scala.collection.mutable
|
||||
@@ -27,7 +28,7 @@ class PodManager {
|
||||
): GameLobby = {
|
||||
val gameLobby = GameLobby(
|
||||
logic = BaseGameLogic(injector.getInstance(classOf[Configuration])),
|
||||
id = java.util.UUID.randomUUID().toString,
|
||||
id = GameUtil.generateCode(),
|
||||
internalId = java.util.UUID.randomUUID(),
|
||||
name = name,
|
||||
maxPlayers = maxPlayers,
|
||||
|
||||
@@ -2,9 +2,9 @@ package logic.game
|
||||
|
||||
import de.knockoutwhist.cards.{Hand, Suit}
|
||||
import de.knockoutwhist.control.GameLogic
|
||||
import de.knockoutwhist.control.GameState.Lobby
|
||||
import de.knockoutwhist.control.GameState.{Lobby, MainMenu}
|
||||
import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.{MatchUtil, PlayerUtil}
|
||||
import de.knockoutwhist.events.global.SessionClosed
|
||||
import de.knockoutwhist.events.global.{GameStateChangeEvent, SessionClosed}
|
||||
import de.knockoutwhist.events.player.PlayerEvent
|
||||
import de.knockoutwhist.player.Playertype.HUMAN
|
||||
import de.knockoutwhist.player.{AbstractPlayer, PlayerFactory}
|
||||
@@ -46,9 +46,13 @@ class GameLobby private(
|
||||
event match {
|
||||
case event: PlayerEvent =>
|
||||
users.get(event.playerId).foreach(session => session.updatePlayer(event))
|
||||
case event: GameStateChangeEvent =>
|
||||
if (event.oldState == MainMenu && event.newState == Lobby) {
|
||||
return
|
||||
}
|
||||
users.values.foreach(session => session.updatePlayer(event))
|
||||
case event: SessionClosed =>
|
||||
users.values.foreach(session => session.updatePlayer(event))
|
||||
|
||||
case event: SimpleEvent =>
|
||||
users.values.foreach(session => session.updatePlayer(event))
|
||||
}
|
||||
@@ -70,6 +74,9 @@ class GameLobby private(
|
||||
users.values.foreach { player =>
|
||||
playerNamesList += PlayerFactory.createPlayer(player.name, player.id, HUMAN)
|
||||
}
|
||||
if (playerNamesList.size < 2) {
|
||||
throw new NotEnoughPlayersException("Not enough players to start the game!")
|
||||
}
|
||||
logic.createMatch(playerNamesList.toList)
|
||||
logic.controlMatch()
|
||||
}
|
||||
@@ -150,7 +157,7 @@ class GameLobby private(
|
||||
|
||||
//-------------------
|
||||
|
||||
private def getUserSession(userId: UUID): UserSession = {
|
||||
def getUserSession(userId: UUID): UserSession = {
|
||||
val sessionOpt = users.get(userId)
|
||||
if (sessionOpt.isEmpty) {
|
||||
throw new NotInThisGameException("You are not in this game!")
|
||||
@@ -158,6 +165,18 @@ class GameLobby private(
|
||||
sessionOpt.get
|
||||
}
|
||||
|
||||
def getPlayerByUser(user: User): AbstractPlayer = {
|
||||
getPlayerBySession(getUserSession(user.id))
|
||||
}
|
||||
|
||||
private def getPlayerBySession(userSession: UserSession): AbstractPlayer = {
|
||||
val playerOption = getMatch.totalplayers.find(_.id == userSession.id)
|
||||
if (playerOption.isEmpty) {
|
||||
throw new NotInThisGameException("You are not in this game!")
|
||||
}
|
||||
playerOption.get
|
||||
}
|
||||
|
||||
private def getPlayerInteractable(userSession: UserSession, iType: InteractionType): AbstractPlayer = {
|
||||
if (!Thread.holdsLock(userSession.lock)) {
|
||||
throw new IllegalStateException("The user session is not locked!")
|
||||
@@ -165,11 +184,7 @@ class GameLobby private(
|
||||
if (userSession.canInteract.isEmpty || userSession.canInteract.get != iType) {
|
||||
throw new NotInteractableException("You can't play a card!")
|
||||
}
|
||||
val playerOption = getMatch.totalplayers.find(_.id == userSession.id)
|
||||
if (playerOption.isEmpty) {
|
||||
throw new NotInThisGameException("You are not in this game!")
|
||||
}
|
||||
playerOption.get
|
||||
getPlayerBySession(userSession)
|
||||
}
|
||||
|
||||
private def getHand(player: AbstractPlayer): Hand = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@(player: de.knockoutwhist.player.AbstractPlayer, logic: de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic)
|
||||
@(player: de.knockoutwhist.player.AbstractPlayer, logic: de.knockoutwhist.control.GameLogic)
|
||||
|
||||
@main("Ingame") {
|
||||
<div id="ingame" class="game-field game-field-background">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@(player: de.knockoutwhist.player.AbstractPlayer, logic: de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic)
|
||||
@(player: de.knockoutwhist.player.AbstractPlayer, logic: de.knockoutwhist.control.GameLogic)
|
||||
|
||||
@main("Selecting Trumpsuit...") {
|
||||
<div id="selecttrumpsuit" class="game-field game-field-background">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@(player: de.knockoutwhist.player.AbstractPlayer, logic: de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic)
|
||||
@(player: de.knockoutwhist.player.AbstractPlayer, logic: de.knockoutwhist.control.GameLogic)
|
||||
|
||||
@main("Tie") {
|
||||
<div id="tie" class="game-field game-field-background">
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="@routes.Assets.versioned("javascripts/particles.js")"></script>
|
||||
<script src="@routes.Assets.versioned("/javascripts/particles.js")"></script>
|
||||
<div id="particles-js" style="background-color: rgb(182, 25, 36);
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
@@ -12,10 +12,19 @@ GET /assets/*file controllers.Assets.versioned(path="/public",
|
||||
GET /mainmenu controllers.MainMenuController.mainMenu()
|
||||
GET /rules controllers.MainMenuController.rules()
|
||||
|
||||
POST /createGame controllers.MainMenuController.createGame()
|
||||
|
||||
# User authentication routes
|
||||
GET /login controllers.UserController.login()
|
||||
POST /login controllers.UserController.login_Post()
|
||||
|
||||
GET /logout controllers.UserController.logout()
|
||||
|
||||
# In-game routes
|
||||
# GET /ingame/:id controllers.MainMenuController.ingame(id: String)
|
||||
GET /game/:id controllers.IngameController.game(id: String)
|
||||
POST /game/:id/join controllers.IngameController.joinGame(id: String)
|
||||
|
||||
POST /game/:id/start controllers.IngameController.startGame(id: String)
|
||||
|
||||
|
||||
POST /game/:id/playCard controllers.IngameController.playCard(id: String)
|
||||
Reference in New Issue
Block a user