feat!: implemented multigame support #34
Submodule knockoutwhist updated: fbc0ea2277...e0e45c4b43
@@ -6,7 +6,7 @@ import de.knockoutwhist.components.Configuration
|
|||||||
import de.knockoutwhist.control.GameState.{InGame, Lobby, SelectTrump, TieBreak}
|
import de.knockoutwhist.control.GameState.{InGame, Lobby, SelectTrump, TieBreak}
|
||||||
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
|
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
|
||||||
import di.KnockOutWebConfigurationModule
|
import di.KnockOutWebConfigurationModule
|
||||||
import logic.PodGameManager
|
import logic.PodManager
|
||||||
import model.sessions.SimpleSession
|
import model.sessions.SimpleSession
|
||||||
import play.api.mvc.*
|
import play.api.mvc.*
|
||||||
import play.api.*
|
import play.api.*
|
||||||
@@ -44,12 +44,12 @@ class HomeController @Inject()(val controllerComponents: ControllerComponents) e
|
|||||||
}
|
}
|
||||||
def rules(): Action[AnyContent] = {
|
def rules(): Action[AnyContent] = {
|
||||||
Action { implicit request =>
|
Action { implicit request =>
|
||||||
Ok(views.html.rules.apply())
|
Ok(views.html.rules())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def sessions(): Action[AnyContent] = {
|
def sessions(): Action[AnyContent] = {
|
||||||
Action { implicit request =>
|
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.GameState.{InGame, Lobby, SelectTrump, TieBreak}
|
||||||
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
|
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
|
||||||
import di.KnockOutWebConfigurationModule
|
import di.KnockOutWebConfigurationModule
|
||||||
import logic.PodGameManager
|
import logic.PodManager
|
||||||
import logic.user.{SessionManager, UserManager}
|
import logic.user.{SessionManager, UserManager}
|
||||||
import model.sessions.SimpleSession
|
import model.sessions.SimpleSession
|
||||||
import play.api.*
|
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.cards.{Hand, Suit}
|
||||||
import de.knockoutwhist.control.GameLogic
|
import de.knockoutwhist.control.GameLogic
|
||||||
|
import de.knockoutwhist.control.GameState.Lobby
|
||||||
import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.{MatchUtil, PlayerUtil, RoundUtil}
|
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.events.player.PlayerEvent
|
||||||
import de.knockoutwhist.player.Playertype.HUMAN
|
import de.knockoutwhist.player.Playertype.HUMAN
|
||||||
import de.knockoutwhist.player.{AbstractPlayer, PlayerFactory}
|
import de.knockoutwhist.player.{AbstractPlayer, PlayerFactory}
|
||||||
import de.knockoutwhist.rounds.{Match, Round, Trick}
|
import de.knockoutwhist.rounds.{Match, Round, Trick}
|
||||||
import de.knockoutwhist.utils.events.{EventListener, SimpleEvent}
|
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.sessions.{InteractionType, UserSession}
|
||||||
import model.users.User
|
import model.users.User
|
||||||
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
import scala.collection.mutable
|
||||||
import scala.collection.mutable.ListBuffer
|
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.addListener(this)
|
||||||
logic.createSession()
|
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 = {
|
override def listen(event: SimpleEvent): Unit = {
|
||||||
event match {
|
event match {
|
||||||
case event: PlayerEvent =>
|
case event: PlayerEvent =>
|
||||||
users.get(event.playerId).foreach(session => session.updatePlayer(event))
|
users.get(event.playerId).foreach(session => session.updatePlayer(event))
|
||||||
|
case event: SessionClosed =>
|
||||||
|
users.values.foreach(session => session.updatePlayer(event))
|
||||||
|
|
||||||
case event: SimpleEvent =>
|
case event: SimpleEvent =>
|
||||||
users.values.foreach(session => session.updatePlayer(event))
|
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 = {
|
def startGame(user: User): Unit = {
|
||||||
val sessionOpt = users.get(user.id)
|
val sessionOpt = users.get(user.id)
|
||||||
if (sessionOpt.isEmpty) {
|
if (sessionOpt.isEmpty) {
|
||||||
@@ -46,13 +74,25 @@ class GameLobby(val logic: GameLogic, val id: String, internalId: UUID) extends
|
|||||||
logic.controlMatch()
|
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.
|
* Play a card from the player's hand.
|
||||||
* @param userSession the user session of the player.
|
* @param userSession the user session of the player.
|
||||||
* @param cardIndex the index of the card in the player's hand.
|
* @param cardIndex the index of the card in the player's hand.
|
||||||
*/
|
*/
|
||||||
def playCard(userSession: UserSession, cardIndex: Int): Unit = {
|
def playCard(userSession: UserSession, cardIndex: Int): Unit = {
|
||||||
val player = getPlayer(userSession, InteractionType.Card)
|
val player = getPlayerInteractable(userSession, InteractionType.Card)
|
||||||
if (player.isInDogLife) {
|
if (player.isInDogLife) {
|
||||||
throw new CantPlayCardException("You are in dog life!")
|
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.
|
* @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 = {
|
def playDogCard(userSession: UserSession, cardIndex: Int): Unit = {
|
||||||
val player = getPlayer(userSession, InteractionType.DogCard)
|
val player = getPlayerInteractable(userSession, InteractionType.DogCard)
|
||||||
if (!player.isInDogLife) {
|
if (!player.isInDogLife) {
|
||||||
throw new CantPlayCardException("You are not in dog life!")
|
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.
|
* @param trumpIndex the index of the trump suit.
|
||||||
*/
|
*/
|
||||||
def selectTrump(userSession: UserSession, trumpIndex: Int): Unit = {
|
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 trumpSuits = Suit.values.toList
|
||||||
val selectedTrump = trumpSuits(trumpIndex)
|
val selectedTrump = trumpSuits(trumpIndex)
|
||||||
logic.playerInputLogic.receivedTrumpSuit(selectedTrump)
|
logic.playerInputLogic.receivedTrumpSuit(selectedTrump)
|
||||||
@@ -103,14 +143,22 @@ class GameLobby(val logic: GameLogic, val id: String, internalId: UUID) extends
|
|||||||
* @param tieNumber
|
* @param tieNumber
|
||||||
*/
|
*/
|
||||||
def selectTie(userSession: UserSession, tieNumber: Int): Unit = {
|
def selectTie(userSession: UserSession, tieNumber: Int): Unit = {
|
||||||
val player = getPlayer(userSession, InteractionType.TieChoice)
|
val player = getPlayerInteractable(userSession, InteractionType.TieChoice)
|
||||||
logic.playerTieLogic.receivedTieBreakerCard(tieNumber)
|
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)) {
|
if (!Thread.holdsLock(userSession.lock)) {
|
||||||
throw new IllegalStateException("The user session is not locked!")
|
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