9 Commits

Author SHA1 Message Date
10fa4badf0 fix: BAC-29 Implement Mappers for Common Classes (#60)
Reviewed-on: #60
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-12-05 19:24:07 +01:00
f765dd64dd fix: CORE-5 GameStateChange Event can be fired without an actual change (#59)
Reviewed-on: #59
Reviewed-by: lq64 <lq@blackhole.local>
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-12-03 12:14:36 +01:00
ef7397f7f1 fix(api): fixes (#58)
Reviewed-on: #58
2025-11-27 09:53:14 +01:00
d833932f16 feat(ui): Websocket (#57)
Started implementing functionality to the Websocket

Co-authored-by: LQ63 <lkhermann@web.de>
Reviewed-on: #57
2025-11-27 09:17:17 +01:00
c5dd02a5e8 fix(base)!: Fixed gamelogic, but it breaks current UI - required more attention (CORE-2). (#56)
Reviewed-on: #56
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-11-26 11:27:58 +01:00
3048552f4c fix(base): COR-1 No Dog after vorletzte Runde (#55)
Reviewed-on: #55
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-11-24 12:17:56 +01:00
ec94ecd46c fix(polling): Improve polling mechanism and delay handling (#54)
Reviewed-on: #54
2025-11-20 10:51:14 +01:00
20e8bae883 feat(game)!: Fixed polling, SPA, Gameplayloop etc. (#53)
Reviewed-on: #53
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-11-19 22:53:48 +01:00
a5dcf3ee90 feat(game): implement tie resolution and enhance player interaction (#52)
43 [Subtask] UI looks better and improved

Reviewed-on: #52
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-11-12 11:47:05 +01:00
13 changed files with 71 additions and 40 deletions

View File

@@ -37,13 +37,11 @@ class CardBaseManager extends CardManager {
}
override def nextCard(): Card = {
val card = cc(currentIdx)
if (currentIdx + 1 > 51) {
if (currentIdx > 51)
throw new IndexOutOfBoundsException("Trying to access card 53(out of bounds)")
} else {
currentIdx += 1
card
}
val card = cc(currentIdx)
currentIdx += 1
card
}
override def removeCards(amount: Int): List[Card] = {

View File

@@ -16,6 +16,7 @@ trait GameLogic extends EventHandler with SnapshottingGameLogic {
def controlPreRound(): Unit
def controlRound(): Unit
def endRound(winner: AbstractPlayer, roundResult: RoundResult): Match
def returnFromTie(winner: AbstractPlayer): Unit
def controlTrick(): Unit
def endTrick(): Round
def controlPlayerPlay(): Unit
@@ -27,12 +28,14 @@ trait GameLogic extends EventHandler with SnapshottingGameLogic {
def playerTieLogic: PlayerTieLogic
def undoManager: UndoManager
def persistenceManager: PersistenceManager
def changeState(gameState: GameState): Unit
def getCurrentState: GameState
def getCurrentMatch: Option[Match]
def getCurrentRound: Option[Round]
def getCurrentTrick: Option[Trick]
def getCurrentPlayer: Option[AbstractPlayer]
def getWinner: Option[AbstractPlayer]
def getTrumpPlayer: Option[AbstractPlayer]
def getPlayerQueue: Option[CustomPlayerQueue[AbstractPlayer]]

View File

@@ -53,8 +53,7 @@ final class BaseGameLogic(val config: Configuration) extends EventHandler with G
currentTrick = None
currentPlayer = None
playerQueue = None
invoke(GameStateChangeEvent(state, Lobby))
state = Lobby
changeState(Lobby)
}
override def createMatch(players: List[AbstractPlayer]): Match = {
@@ -72,20 +71,23 @@ final class BaseGameLogic(val config: Configuration) extends EventHandler with G
if (matchImpl.isOver) {
//Winner is the last person in the playersIn list
val winner = matchImpl.playersIn.head
invoke(GameStateChangeEvent(state, FinishedMatch))
state = FinishedMatch
changeState(FinishedMatch)
invoke(MatchEndEvent(winner))
} else {
changeState(InGame)
if (matchImpl.roundlist.isEmpty) {
if (cardManager.isEmpty) throw new IllegalStateException("No card manager set")
val cardManagerImpl = cardManager.get
cardManagerImpl.shuffleAndReset()
val firstCard = cardManagerImpl.nextCard()
val newRound = RoundUtil.createRound(firstCard.suit, true)
providePlayersWithCards()
val randomPlayer: Int = 1//Random.nextInt(matchImpl.playersIn.size)
val randomPlayer: Int = Random.nextInt(matchImpl.playersIn.size)
playerQueue = Some(config.createRightQueue(matchImpl.playersIn.toArray, randomPlayer))
matchImpl.playersIn.foreach(player => {invoke(ReceivedHandEvent(player))})
@@ -106,6 +108,18 @@ final class BaseGameLogic(val config: Configuration) extends EventHandler with G
controlPreRound()
}
}
override def returnFromTie(winner: AbstractPlayer): Unit = {
if (currentMatch.isEmpty) throw new IllegalStateException("No current match set")
val matchImpl = currentMatch.get
if (currentRound.isEmpty) throw new IllegalStateException("No current round set")
val roundImpl = currentRound.get
val roundResult: RoundResult = RoundUtil.finishRound(roundImpl, matchImpl)
val newMatch = endRound(winner, roundResult)
currentMatch = Some(newMatch)
controlMatch()
}
//
override def controlPreRound(): Unit = {
@@ -122,8 +136,7 @@ final class BaseGameLogic(val config: Configuration) extends EventHandler with G
matchImpl.playersIn.indexOf(lastWinner.get)
))
invoke(GameStateChangeEvent(state, SelectTrump))
state = SelectTrump
changeState(SelectTrump)
invoke(TrumpSelectEvent(lastWinner.get))
@@ -131,9 +144,7 @@ final class BaseGameLogic(val config: Configuration) extends EventHandler with G
}
override def controlRound(): Unit = {
if (state != InGame)
invoke(GameStateChangeEvent(state, InGame))
state = InGame
changeState(InGame)
if (currentMatch.isEmpty) throw new IllegalStateException("No current match set")
val matchImpl = currentMatch.get
if (currentRound.isEmpty) throw new IllegalStateException("No current round set")
@@ -144,8 +155,7 @@ final class BaseGameLogic(val config: Configuration) extends EventHandler with G
if (MatchUtil.isRoundOver(matchImpl, roundImpl)) {
val roundResult: RoundResult = RoundUtil.finishRound(roundImpl, matchImpl)
if (roundResult.isTie) {
invoke(GameStateChangeEvent(state, TieBreak))
state = TieBreak
changeState(TieBreak)
invoke(TieEvent(roundResult.winners))
invoke(DelayEvent(2000))
@@ -185,8 +195,11 @@ final class BaseGameLogic(val config: Configuration) extends EventHandler with G
).map(rp => rp.amountOfTricks).sum))
invoke(DelayEvent(2000))
if (roundResult.notTricked.nonEmpty && !roundImpl.firstRound) {
if (matchImpl.dogLife) {
if (roundResult.notTricked.nonEmpty && !resultingRound.firstRound) {
// When the number of cards is less than 2, dog life ends automatically
val cantDogLife = (matchImpl.numberofcards - 1) < 2
if (matchImpl.dogLife && !cantDogLife) {
invoke(ShowPlayersOutEvent(roundResult.notTricked))
invoke(DelayEvent(2000))
matchImpl = matchImpl.updatePlayersIn(matchImpl.playersIn.filterNot(roundResult.notTricked.contains(_)))
@@ -228,6 +241,7 @@ final class BaseGameLogic(val config: Configuration) extends EventHandler with G
queueImpl.resetAndSetStart(winner)
controlRound()
} else {
invoke(DelayEvent(2000))
val playerImpl = queueImpl.nextPlayer()
currentPlayer = Some(playerImpl)
controlPlayerPlay()
@@ -295,7 +309,11 @@ final class BaseGameLogic(val config: Configuration) extends EventHandler with G
})
}
override def changeState(gameState: GameState): Unit = {
if(state == gameState) return
invoke(GameStateChangeEvent(state, gameState))
state = gameState
}
// Getters
@@ -306,6 +324,14 @@ final class BaseGameLogic(val config: Configuration) extends EventHandler with G
override def getCurrentPlayer: Option[AbstractPlayer] = currentPlayer
override def getPlayerQueue: Option[CustomPlayerQueue[AbstractPlayer]] = playerQueue
override def getWinner: Option[AbstractPlayer] = {
if (currentMatch.isEmpty) throw new IllegalStateException("No current match set")
val matchImpl = currentMatch.get
if (!matchImpl.isOver) return None
Some(matchImpl.playersIn.head)
}
override def getTrumpPlayer: Option[AbstractPlayer] = {
if (currentMatch.isEmpty) throw new IllegalStateException("No current match set")
val matchImpl = currentMatch.get
@@ -328,8 +354,7 @@ final class BaseGameLogic(val config: Configuration) extends EventHandler with G
currentPlayer = None
playerQueue = None
invoke(SessionClosed())
invoke(GameStateChangeEvent(state, MainMenu))
state = MainMenu
changeState(MainMenu)
}
}

View File

@@ -33,7 +33,7 @@ final class BasePlayerTieLogic(gameLogic: BaseGameLogic) extends PlayerTieLogic
override def handleNextTieBreakerPlayer(): Unit = {
tieBreakerIndex += 1
if(tieBreakerIndex >= 0 && tieBreakerIndex < tiedPlayers.size) {
requestTieChoice(currentTiePlayer())
requestTieChoice(currentTiePlayer().get)
} else {
// All players have selected their tie-breaker cards
// Find the highest card among selected cards
@@ -70,12 +70,14 @@ final class BasePlayerTieLogic(gameLogic: BaseGameLogic) extends PlayerTieLogic
val winner = winners.head
// Inform game logic about the winner
gameLogic.returnFromTie(winner)
}
}
override def currentTiePlayer(): AbstractPlayer = {
tiedPlayers(tieBreakerIndex)
override def currentTiePlayer(): Option[AbstractPlayer] = {
if (tieBreakerIndex < 0 || tieBreakerIndex >= tiedPlayers.size)
return None
Some(tiedPlayers(tieBreakerIndex))
}
override def requestTieChoice(player: AbstractPlayer): Unit = {
@@ -111,7 +113,7 @@ final class BasePlayerTieLogic(gameLogic: BaseGameLogic) extends PlayerTieLogic
// The highest allowed number is total cards minus the number of tied players already selected
// This ensures that each tied player can select a unique card
remainingCards - (tiedPlayers.size - selectedCard.size - 1)
remainingCards - (tiedPlayers.size - (selectedCard.size + 1)) - 1
}
override def isWaitingForInput: Boolean = _waitingForInput

View File

@@ -17,7 +17,7 @@ object MatchUtil {
}
def dogNeedsToPlay(matchImpl: Match, roundImpl: Round): Boolean = {
remainingRounds(matchImpl, roundImpl) == 1
remainingRounds(matchImpl, roundImpl) <= 1
}
}

View File

@@ -9,7 +9,7 @@ trait PlayerTieLogic extends SnapshottingGameLogic {
def handleTie(roundResult: RoundResult): Unit
def handleNextTieBreakerPlayer(): Unit
def currentTiePlayer(): AbstractPlayer
def currentTiePlayer(): Option[AbstractPlayer]
def requestTieChoice(player: AbstractPlayer): Unit
def receivedTieBreakerCard(number: Int): Unit
def highestAllowedNumber(): Int

View File

@@ -4,5 +4,5 @@ import de.knockoutwhist.control.GameState
import de.knockoutwhist.utils.events.SimpleEvent
case class GameStateChangeEvent(oldState: GameState, newState: GameState) extends SimpleEvent {
override def id: String = s"GameStateChangeEvent(from=$oldState,to=$newState)"
override def id: String = s"GameStateChangeEvent"
}

View File

@@ -4,5 +4,5 @@ import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
case class RoundEndEvent(winner: AbstractPlayer, amountOfTricks: Int) extends SimpleEvent {
override def id: String = s"RoundEndEvent()"
override def id: String = s"RoundEndEvent"
}

View File

@@ -3,5 +3,5 @@ package de.knockoutwhist.events.player
import de.knockoutwhist.player.AbstractPlayer
case class RequestCardEvent(player: AbstractPlayer) extends PlayerEvent(player) {
override def id: String = "PlayCardEvent"
override def id: String = "RequestCardEvent"
}

View File

@@ -27,7 +27,7 @@ class TieMenu(gui: GUIMain) {
if (gui.logic.isEmpty) throw new IllegalStateException("Logic is not initialized!")
val logic = gui.logic.get
val player = logic.playerTieLogic.currentTiePlayer()
val player = logic.playerTieLogic.currentTiePlayer().get
updatePlayerLabel(player)
changeSlider(logic.playerTieLogic.highestAllowedNumber())

View File

@@ -26,7 +26,7 @@ case class SelectTieNumberCommand[
glSnapshot.restore(gameLogic.asInstanceOf[GL])
ptlSnapshot.restore(gameLogic.playerTieLogic.asInstanceOf[PT])
ControlThread.runLater {
gameLogic.playerTieLogic.requestTieChoice(gameLogic.playerTieLogic.currentTiePlayer())
gameLogic.playerTieLogic.requestTieChoice(gameLogic.playerTieLogic.currentTiePlayer().get)
}
}
}

View File

@@ -5,12 +5,15 @@ import de.knockoutwhist.utils.events.{EventListener, SimpleEvent}
object DelayHandler extends EventListener {
private[knockoutwhist] var activateDelay: Boolean = false
private[knockoutwhist] var activateDelay: Boolean = true
override def listen(event: SimpleEvent): Unit = {
event match {
case event: DelayEvent =>
if(activateDelay) Thread.sleep(event.delay)
if(activateDelay) {
println(s"Delaying for ${event.delay}ms")
Thread.sleep(event.delay)
}
case _ =>
}
}

View File

@@ -52,7 +52,7 @@ class BasePlayerTieLogicSpec extends AnyWordSpec with Matchers {
tieLogic.getTiedPlayers should contain inOrder (p1, p2)
tieLogic.getTieBreakerIndex shouldBe 0
tieLogic.isWaitingForInput shouldBe true
tieLogic.currentTiePlayer() shouldBe p1
tieLogic.currentTiePlayer().get shouldBe p1
tieLogic.highestAllowedNumber() shouldBe (logic.cardManager.get.remainingCards - (2 - 0 - 1))
}