feat(user-sessions): add custom exceptions for game logic and enhance user session interaction

This commit is contained in:
2025-10-31 17:37:58 +01:00
committed by Janis
parent 992dd8f11c
commit aeeb8c13e9
8 changed files with 134 additions and 17 deletions

View File

@@ -0,0 +1,7 @@
package exceptions;
public class CantPlayCardException extends GameException {
public CantPlayCardException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,7 @@
package exceptions;
public abstract class GameException extends RuntimeException {
public GameException(String message) {
super(message);
}
}

View File

@@ -1,6 +1,6 @@
package exceptions; package exceptions;
public class NotHostException extends RuntimeException { public class NotHostException extends GameException {
public NotHostException(String message) { public NotHostException(String message) {
super(message); super(message);
} }

View File

@@ -1,6 +1,6 @@
package exceptions; package exceptions;
public class NotInThisGameException extends RuntimeException { public class NotInThisGameException extends GameException {
public NotInThisGameException(String message) { public NotInThisGameException(String message) {
super(message); super(message);
} }

View File

@@ -1,6 +1,6 @@
package exceptions; package exceptions;
public class NotInteractableException extends RuntimeException { public class NotInteractableException extends GameException {
public NotInteractableException(String message) { public NotInteractableException(String message) {
super(message); super(message);
} }

View File

@@ -1,13 +1,15 @@
package logic.game package logic.game
import de.knockoutwhist.cards.{Hand, Suit}
import de.knockoutwhist.control.GameLogic import de.knockoutwhist.control.GameLogic
import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.{MatchUtil, PlayerUtil, RoundUtil}
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 import de.knockoutwhist.rounds.{Match, Round, Trick}
import de.knockoutwhist.utils.events.{EventListener, SimpleEvent} import de.knockoutwhist.utils.events.{EventListener, SimpleEvent}
import exceptions.{NotHostException, NotInThisGameException, NotInteractableException} import exceptions.{CantPlayCardException, NotHostException, NotInThisGameException, NotInteractableException}
import model.sessions.UserSession import model.sessions.{InteractionType, UserSession}
import model.users.User import model.users.User
import java.util.UUID import java.util.UUID
@@ -44,20 +46,92 @@ class GameLobby(val logic: GameLogic, val id: String, internalId: UUID) extends
logic.controlMatch() logic.controlMatch()
} }
def playCard(user: User, card: Int): Unit = { /**
val sessionOpt = users.get(user.id) * Play a card from the player's hand.
if (sessionOpt.isEmpty) { * @param userSession the user session of the player.
throw new NotInThisGameException("You are not in this game!") * @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)
if (player.isInDogLife) {
throw new CantPlayCardException("You are in dog life!")
} }
if (!sessionOpt.get.canInteract) { val hand = getHand(player)
throw new NotInteractableException("You can't play a card!") val card = hand.cards(cardIndex)
if (!PlayerUtil.canPlayCard(card, getRound, getTrick, player)) {
throw new CantPlayCardException("You can't play this card!")
} }
logic.playerInputLogic.receivedCard(card)
}
/**
* Play a card from the player's hand while in dog life or skip the round.
* @param userSession the user session of the player.
* @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)
if (!player.isInDogLife) {
throw new CantPlayCardException("You are not in dog life!")
}
if (cardIndex == -1) {
if (!MatchUtil.dogNeedsToPlay(getMatch, getRound)) {
throw new CantPlayCardException("You can't skip this round!")
}
logic.playerInputLogic.receivedDog(None)
}
val hand = getHand(player)
val card = hand.cards(cardIndex)
logic.playerInputLogic.receivedDog(Some(card))
}
/**
* Select the trump suit for the round.
* @param userSession the user session of the player.
* @param trumpIndex the index of the trump suit.
*/
def selectTrump(userSession: UserSession, trumpIndex: Int): Unit = {
val player = getPlayer(userSession, InteractionType.TrumpSuit)
val trumpSuits = Suit.values.toList
val selectedTrump = trumpSuits(trumpIndex)
logic.playerInputLogic.receivedTrumpSuit(selectedTrump)
}
/**
*
* @param userSession
* @param tieNumber
*/
def selectTie(userSession: UserSession, tieNumber: Int): Unit = {
val player = getPlayer(userSession, InteractionType.TieChoice)
logic.playerTieLogic.receivedTieBreakerCard(tieNumber)
} }
//------------------- //-------------------
private def getPlayer(userSession: UserSession, iType: InteractionType): AbstractPlayer = {
if (!Thread.holdsLock(userSession.lock)) {
throw new IllegalStateException("The user session is not locked!")
}
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
}
private def getHand(player: AbstractPlayer): Hand = {
val handOption = player.currentHand()
if (handOption.isEmpty) {
throw new IllegalStateException("You have no cards!")
}
handOption.get
}
private def getMatch: Match = { private def getMatch: Match = {
val matchOpt = logic.getCurrentMatch val matchOpt = logic.getCurrentMatch
if (matchOpt.isEmpty) { if (matchOpt.isEmpty) {
@@ -66,4 +140,20 @@ class GameLobby(val logic: GameLogic, val id: String, internalId: UUID) extends
matchOpt.get matchOpt.get
} }
private def getRound: Round = {
val roundOpt = logic.getCurrentRound
if (roundOpt.isEmpty) {
throw new IllegalStateException("No round is currently running!")
}
roundOpt.get
}
private def getTrick: Trick = {
val trickOpt = logic.getCurrentTrick
if (trickOpt.isEmpty) {
throw new IllegalStateException("No trick is currently running!")
}
trickOpt.get
}
} }

View File

@@ -0,0 +1,10 @@
package model.sessions
enum InteractionType {
case TrumpSuit
case Card
case DogCard
case TieChoice
}

View File

@@ -5,18 +5,21 @@ import de.knockoutwhist.utils.events.SimpleEvent
import model.users.User import model.users.User
import java.util.UUID import java.util.UUID
import java.util.concurrent.locks.{Lock, ReentrantLock}
class UserSession(user: User, val host: Boolean) extends PlayerSession { class UserSession(user: User, val host: Boolean) extends PlayerSession {
var canInteract: Boolean = false var canInteract: Option[InteractionType] = None
val lock: Lock = ReentrantLock()
override def updatePlayer(event: SimpleEvent): Unit = { override def updatePlayer(event: SimpleEvent): Unit = {
event match { event match {
case event: RequestTrumpSuitEvent => case event: RequestTrumpSuitEvent =>
canInteract = true canInteract = Some(InteractionType.TrumpSuit)
case event: RequestTieChoiceEvent => case event: RequestTieChoiceEvent =>
canInteract = true canInteract = Some(InteractionType.TieChoice)
case event: RequestCardEvent => case event: RequestCardEvent =>
canInteract = true if (event.player.isInDogLife) canInteract = Some(InteractionType.DogCard)
else canInteract = Some(InteractionType.Card)
case _ => case _ =>
} }
} }