feat(user-sessions): enhance game lobby management with user session handling and game full exception
This commit is contained in:
@@ -6,7 +6,7 @@ import de.knockoutwhist.components.Configuration
|
||||
import de.knockoutwhist.control.GameState.{InGame, Lobby, SelectTrump, TieBreak}
|
||||
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
|
||||
import di.KnockOutWebConfigurationModule
|
||||
import logic.PodGameManager
|
||||
import logic.PodManager
|
||||
import model.sessions.SimpleSession
|
||||
import play.api.mvc.*
|
||||
import play.api.*
|
||||
@@ -44,12 +44,12 @@ class HomeController @Inject()(val controllerComponents: ControllerComponents) e
|
||||
}
|
||||
def rules(): Action[AnyContent] = {
|
||||
Action { implicit request =>
|
||||
Ok(views.html.rules.apply())
|
||||
Ok(views.html.rules())
|
||||
}
|
||||
}
|
||||
def sessions(): Action[AnyContent] = {
|
||||
Action { implicit request =>
|
||||
Ok(views.html.sessions.apply(PodGameManager.listSessions()))
|
||||
Ok(views.html.rules())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import de.knockoutwhist.components.Configuration
|
||||
import de.knockoutwhist.control.GameState.{InGame, Lobby, SelectTrump, TieBreak}
|
||||
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
|
||||
import di.KnockOutWebConfigurationModule
|
||||
import logic.PodGameManager
|
||||
import logic.PodManager
|
||||
import logic.user.{SessionManager, UserManager}
|
||||
import model.sessions.SimpleSession
|
||||
import play.api.*
|
||||
|
||||
7
knockoutwhistweb/app/exceptions/GameFullException.java
Normal file
7
knockoutwhistweb/app/exceptions/GameFullException.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package exceptions;
|
||||
|
||||
public class GameFullException extends GameException {
|
||||
public GameFullException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package logic
|
||||
|
||||
import de.knockoutwhist.utils.events.SimpleEvent
|
||||
import model.sessions.PlayerSession
|
||||
|
||||
import java.util.UUID
|
||||
import scala.collection.mutable
|
||||
|
||||
object PodGameManager {
|
||||
|
||||
private val sessions: mutable.Map[UUID, PlayerSession] = mutable.Map()
|
||||
|
||||
def addSession(session: PlayerSession): Unit = {
|
||||
sessions.put(session.id, session)
|
||||
}
|
||||
|
||||
def clearSessions(): Unit = {
|
||||
sessions.clear()
|
||||
}
|
||||
|
||||
def identify(id: UUID): Option[PlayerSession] = {
|
||||
sessions.get(id)
|
||||
}
|
||||
|
||||
def transmit(id: UUID, event: SimpleEvent): Unit = {
|
||||
identify(id).foreach(_.updatePlayer(event))
|
||||
}
|
||||
|
||||
def transmitAll(event: SimpleEvent): Unit = {
|
||||
sessions.foreach(session => session._2.updatePlayer(event))
|
||||
}
|
||||
|
||||
def listSessions(): List[PlayerSession] = {
|
||||
sessions.values.toList
|
||||
}
|
||||
|
||||
}
|
||||
48
knockoutwhistweb/app/logic/PodManager.scala
Normal file
48
knockoutwhistweb/app/logic/PodManager.scala
Normal file
@@ -0,0 +1,48 @@
|
||||
package logic
|
||||
|
||||
import com.google.inject.{Guice, Injector}
|
||||
import de.knockoutwhist.components.Configuration
|
||||
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
|
||||
import di.KnockOutWebConfigurationModule
|
||||
import logic.game.GameLobby
|
||||
import model.users.User
|
||||
|
||||
import javax.inject.Singleton
|
||||
import scala.collection.mutable
|
||||
|
||||
@Singleton
|
||||
class PodManager {
|
||||
|
||||
val TTL: Long = System.currentTimeMillis() + 86400000L // 24 hours in milliseconds
|
||||
val podIp: String = System.getenv("POD_IP")
|
||||
val podName: String = System.getenv("POD_NAME")
|
||||
|
||||
private val sessions: mutable.Map[String, GameLobby] = mutable.Map()
|
||||
private val injector: Injector = Guice.createInjector(KnockOutWebConfigurationModule())
|
||||
|
||||
def createGame(
|
||||
host: User,
|
||||
name: String,
|
||||
maxPlayers: Int
|
||||
): GameLobby = {
|
||||
val gameLobby = GameLobby(
|
||||
logic = BaseGameLogic(injector.getInstance(classOf[Configuration])),
|
||||
id = java.util.UUID.randomUUID().toString,
|
||||
internalId = java.util.UUID.randomUUID(),
|
||||
name = name,
|
||||
maxPlayers = maxPlayers,
|
||||
host = host
|
||||
)
|
||||
sessions += (gameLobby.id -> gameLobby)
|
||||
gameLobby
|
||||
}
|
||||
|
||||
def getGame(gameId: String): Option[GameLobby] = {
|
||||
sessions.get(gameId)
|
||||
}
|
||||
|
||||
private[logic] def removeGame(gameId: String): Unit = {
|
||||
sessions.remove(gameId)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,34 +2,62 @@ package logic.game
|
||||
|
||||
import de.knockoutwhist.cards.{Hand, Suit}
|
||||
import de.knockoutwhist.control.GameLogic
|
||||
import de.knockoutwhist.control.GameState.Lobby
|
||||
import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.{MatchUtil, PlayerUtil, RoundUtil}
|
||||
import de.knockoutwhist.events.global.SessionClosed
|
||||
import de.knockoutwhist.events.player.PlayerEvent
|
||||
import de.knockoutwhist.player.Playertype.HUMAN
|
||||
import de.knockoutwhist.player.{AbstractPlayer, PlayerFactory}
|
||||
import de.knockoutwhist.rounds.{Match, Round, Trick}
|
||||
import de.knockoutwhist.utils.events.{EventListener, SimpleEvent}
|
||||
import exceptions.{CantPlayCardException, NotHostException, NotInThisGameException, NotInteractableException}
|
||||
import exceptions.{CantPlayCardException, GameFullException, NotHostException, NotInThisGameException, NotInteractableException}
|
||||
import model.sessions.{InteractionType, UserSession}
|
||||
import model.users.User
|
||||
|
||||
import java.util.UUID
|
||||
import scala.collection.mutable
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
class GameLobby(val logic: GameLogic, val id: String, internalId: UUID) extends EventListener{
|
||||
class GameLobby private(
|
||||
val logic: GameLogic,
|
||||
val id: String,
|
||||
val internalId: UUID,
|
||||
val name: String,
|
||||
val maxPlayers: Int
|
||||
) extends EventListener {
|
||||
logic.addListener(this)
|
||||
logic.createSession()
|
||||
|
||||
val users: Map[UUID, UserSession] = Map()
|
||||
private val users: mutable.Map[UUID, UserSession] = mutable.Map()
|
||||
|
||||
def addUser(user: User): UserSession = {
|
||||
if (users.size >= maxPlayers) throw new GameFullException("The game is full!")
|
||||
if (users.contains(user.id)) throw new IllegalArgumentException("User is already in the game!")
|
||||
if (logic.getCurrentState != Lobby) throw new IllegalStateException("The game has already started!")
|
||||
val userSession = new UserSession(
|
||||
user = user,
|
||||
host = false
|
||||
)
|
||||
users += (user.id -> userSession)
|
||||
userSession
|
||||
}
|
||||
|
||||
override def listen(event: SimpleEvent): Unit = {
|
||||
event match {
|
||||
case event: PlayerEvent =>
|
||||
users.get(event.playerId).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))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the game if the user is the host.
|
||||
* @param user the user who wants to start the game.
|
||||
*/
|
||||
def startGame(user: User): Unit = {
|
||||
val sessionOpt = users.get(user.id)
|
||||
if (sessionOpt.isEmpty) {
|
||||
@@ -46,13 +74,25 @@ class GameLobby(val logic: GameLogic, val id: String, internalId: UUID) extends
|
||||
logic.controlMatch()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
if (sessionOpt.isEmpty) {
|
||||
throw new NotInThisGameException("You are not in this game!")
|
||||
}
|
||||
users.remove(user.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a card from the player's hand.
|
||||
* @param userSession the user session of the player.
|
||||
* @param cardIndex the index of the card in the player's hand.
|
||||
*/
|
||||
def playCard(userSession: UserSession, cardIndex: Int): Unit = {
|
||||
val player = getPlayer(userSession, InteractionType.Card)
|
||||
val player = getPlayerInteractable(userSession, InteractionType.Card)
|
||||
if (player.isInDogLife) {
|
||||
throw new CantPlayCardException("You are in dog life!")
|
||||
}
|
||||
@@ -70,7 +110,7 @@ class GameLobby(val logic: GameLogic, val id: String, internalId: UUID) extends
|
||||
* @param cardIndex the index of the card in the player's hand or -1 if the player wants to skip the round.
|
||||
*/
|
||||
def playDogCard(userSession: UserSession, cardIndex: Int): Unit = {
|
||||
val player = getPlayer(userSession, InteractionType.DogCard)
|
||||
val player = getPlayerInteractable(userSession, InteractionType.DogCard)
|
||||
if (!player.isInDogLife) {
|
||||
throw new CantPlayCardException("You are not in dog life!")
|
||||
}
|
||||
@@ -91,7 +131,7 @@ class GameLobby(val logic: GameLogic, val id: String, internalId: UUID) extends
|
||||
* @param trumpIndex the index of the trump suit.
|
||||
*/
|
||||
def selectTrump(userSession: UserSession, trumpIndex: Int): Unit = {
|
||||
val player = getPlayer(userSession, InteractionType.TrumpSuit)
|
||||
val player = getPlayerInteractable(userSession, InteractionType.TrumpSuit)
|
||||
val trumpSuits = Suit.values.toList
|
||||
val selectedTrump = trumpSuits(trumpIndex)
|
||||
logic.playerInputLogic.receivedTrumpSuit(selectedTrump)
|
||||
@@ -103,14 +143,22 @@ class GameLobby(val logic: GameLogic, val id: String, internalId: UUID) extends
|
||||
* @param tieNumber
|
||||
*/
|
||||
def selectTie(userSession: UserSession, tieNumber: Int): Unit = {
|
||||
val player = getPlayer(userSession, InteractionType.TieChoice)
|
||||
val player = getPlayerInteractable(userSession, InteractionType.TieChoice)
|
||||
logic.playerTieLogic.receivedTieBreakerCard(tieNumber)
|
||||
}
|
||||
|
||||
|
||||
//-------------------
|
||||
|
||||
private def getPlayer(userSession: UserSession, iType: InteractionType): AbstractPlayer = {
|
||||
private def getUserSession(userId: UUID): UserSession = {
|
||||
val sessionOpt = users.get(userId)
|
||||
if (sessionOpt.isEmpty) {
|
||||
throw new NotInThisGameException("You are not in this game!")
|
||||
}
|
||||
sessionOpt.get
|
||||
}
|
||||
|
||||
private def getPlayerInteractable(userSession: UserSession, iType: InteractionType): AbstractPlayer = {
|
||||
if (!Thread.holdsLock(userSession.lock)) {
|
||||
throw new IllegalStateException("The user session is not locked!")
|
||||
}
|
||||
@@ -157,3 +205,27 @@ class GameLobby(val logic: GameLogic, val id: String, internalId: UUID) extends
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object GameLobby {
|
||||
def apply(
|
||||
logic: GameLogic,
|
||||
id: String,
|
||||
internalId: UUID,
|
||||
name: String,
|
||||
maxPlayers: Int,
|
||||
host: User
|
||||
): GameLobby = {
|
||||
val lobby = new GameLobby(
|
||||
logic = logic,
|
||||
id = id,
|
||||
internalId = internalId,
|
||||
name = name,
|
||||
maxPlayers = maxPlayers
|
||||
)
|
||||
lobby.users += (host.id -> new UserSession(
|
||||
user = host,
|
||||
host = true
|
||||
))
|
||||
lobby
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user