Refactor game logic to track player input state; add CardPlayedEvent and update RoundEndEvent with trick count
This commit is contained in:
@@ -6,6 +6,7 @@ import de.knockoutwhist.di.{KnockOutConfigurationModule, KnockOutLogicModule}
|
||||
import de.knockoutwhist.persistence.formats.FileFormatter
|
||||
import de.knockoutwhist.player.AbstractPlayer
|
||||
import de.knockoutwhist.ui.UI
|
||||
import de.knockoutwhist.ui.gui.GUIMain
|
||||
import de.knockoutwhist.ui.tui.TUIMain
|
||||
import de.knockoutwhist.utils
|
||||
import de.knockoutwhist.utils.CustomPlayerQueue
|
||||
@@ -21,7 +22,8 @@ class DefaultConfiguration extends Configuration {
|
||||
override def cardManager: CardManager = injector.getInstance(classOf[CardManager])
|
||||
override def fileFormatter: FileFormatter = injector.getInstance(classOf[FileFormatter])
|
||||
override def uis: Set[UI] = Set[UI](
|
||||
TUIMain()
|
||||
TUIMain(),
|
||||
GUIMain()
|
||||
)
|
||||
override def listener: Set[EventListener] = Set[EventListener](
|
||||
utils.DelayHandler
|
||||
|
||||
@@ -20,6 +20,8 @@ trait GameLogic extends EventHandler with SnapshottingGameLogic {
|
||||
def controlPlayerPlay(): Unit
|
||||
def providePlayersWithCards(): Unit
|
||||
|
||||
def isWaitingForInput: Boolean
|
||||
|
||||
def playerInputLogic: PlayerInputLogic
|
||||
def playerTieLogic: PlayerTieLogic
|
||||
def undoManager: UndoManager
|
||||
|
||||
@@ -175,7 +175,9 @@ final class BaseGameLogic(val config: Configuration) extends EventHandler with G
|
||||
winner = Some(winner)
|
||||
)
|
||||
|
||||
invoke(RoundEndEvent(winner))
|
||||
invoke(RoundEndEvent(winner, roundResult.tricked.filter(
|
||||
rp => rp.player == winner
|
||||
).map(rp => rp.amountOfTricks).sum))
|
||||
invoke(DelayEvent(2000))
|
||||
|
||||
if (roundResult.notTricked.nonEmpty) {
|
||||
@@ -194,7 +196,7 @@ final class BaseGameLogic(val config: Configuration) extends EventHandler with G
|
||||
}
|
||||
}
|
||||
roundResult.tricked.foreach(player => {
|
||||
player.resetDogLife()
|
||||
player.player.resetDogLife()
|
||||
})
|
||||
matchImpl.addRound(resultingRound)
|
||||
}
|
||||
@@ -257,6 +259,15 @@ final class BaseGameLogic(val config: Configuration) extends EventHandler with G
|
||||
playerInputLogic.requestCard(playerImpl)
|
||||
}
|
||||
|
||||
override def isWaitingForInput: Boolean = {
|
||||
if (state == InGame || state == SelectTrump) {
|
||||
playerInputLogic.isWaitingForInput
|
||||
} else if (state == TieBreak) {
|
||||
playerTieLogic.isWaitingForInput
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
override def providePlayersWithCards(): Unit = {
|
||||
@@ -298,6 +309,7 @@ final class BaseGameLogic(val config: Configuration) extends EventHandler with G
|
||||
}
|
||||
|
||||
override def endSession(): Unit = {
|
||||
//TODO Return to main menu
|
||||
System.exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,23 +4,30 @@ import de.knockoutwhist.cards.{Card, Suit}
|
||||
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
|
||||
import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.RoundUtil
|
||||
import de.knockoutwhist.control.sublogic.PlayerInputLogic
|
||||
import de.knockoutwhist.events.player.{PlayCardEvent, RequestTrumpSuitEvent}
|
||||
import de.knockoutwhist.events.global.{CardPlayedEvent, TrumpSelectedEvent}
|
||||
import de.knockoutwhist.events.player.{RequestCardEvent, RequestTrumpSuitEvent}
|
||||
import de.knockoutwhist.player.AbstractPlayer
|
||||
|
||||
final class BasePlayerInputLogic(gameLogic: BaseGameLogic) extends PlayerInputLogic {
|
||||
|
||||
private var _waitingForInput: Boolean = false
|
||||
|
||||
override def requestTrumpSuit(player: AbstractPlayer): Unit = {
|
||||
_waitingForInput = true
|
||||
gameLogic.invoke(RequestTrumpSuitEvent(player))
|
||||
}
|
||||
|
||||
override def receivedTrumpSuit(suit: Suit): Unit = {
|
||||
val newRound = RoundUtil.createRound(suit)
|
||||
_waitingForInput = false
|
||||
gameLogic.currentRound = Some(newRound)
|
||||
gameLogic.invoke(TrumpSelectedEvent(suit))
|
||||
gameLogic.controlRound()
|
||||
}
|
||||
|
||||
override def requestCard(player: AbstractPlayer): Unit = {
|
||||
gameLogic.invoke(PlayCardEvent(player))
|
||||
_waitingForInput = true
|
||||
gameLogic.invoke(RequestCardEvent(player))
|
||||
}
|
||||
|
||||
override def receivedCard(card: Card): Unit = {
|
||||
@@ -29,6 +36,8 @@ final class BasePlayerInputLogic(gameLogic: BaseGameLogic) extends PlayerInputLo
|
||||
if (gameLogic.currentPlayer.isEmpty) throw new IllegalStateException("No current player set")
|
||||
val player = gameLogic.currentPlayer.get
|
||||
|
||||
_waitingForInput = false
|
||||
|
||||
val newTrick = if (trickImpl.firstCard.isEmpty) {
|
||||
trickImpl
|
||||
.setfirstcard(card)
|
||||
@@ -40,6 +49,9 @@ final class BasePlayerInputLogic(gameLogic: BaseGameLogic) extends PlayerInputLo
|
||||
player.removeCard(card)
|
||||
|
||||
gameLogic.currentTrick = Some(newTrick)
|
||||
|
||||
gameLogic.invoke(CardPlayedEvent(player, newTrick))
|
||||
|
||||
gameLogic.controlTrick()
|
||||
}
|
||||
|
||||
@@ -49,6 +61,8 @@ final class BasePlayerInputLogic(gameLogic: BaseGameLogic) extends PlayerInputLo
|
||||
if (gameLogic.currentPlayer.isEmpty) throw new IllegalStateException("No current player set")
|
||||
val player = gameLogic.currentPlayer.get
|
||||
|
||||
_waitingForInput = false
|
||||
|
||||
if (dog.isDefined) {
|
||||
val newTrick = if (trickImpl.firstCard.isEmpty) {
|
||||
trickImpl
|
||||
@@ -63,5 +77,6 @@ final class BasePlayerInputLogic(gameLogic: BaseGameLogic) extends PlayerInputLo
|
||||
}
|
||||
gameLogic.controlTrick()
|
||||
}
|
||||
|
||||
|
||||
override def isWaitingForInput: Boolean = _waitingForInput
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package de.knockoutwhist.control.controllerBaseImpl.sublogic
|
||||
import de.knockoutwhist.cards.Card
|
||||
import de.knockoutwhist.control.LogicSnapshot
|
||||
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
|
||||
import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.RoundResult
|
||||
import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.{ResultPlayer, RoundResult}
|
||||
import de.knockoutwhist.control.sublogic.PlayerTieLogic
|
||||
import de.knockoutwhist.events.global.tie.*
|
||||
import de.knockoutwhist.events.player.RequestTieChoiceEvent
|
||||
@@ -17,6 +17,7 @@ final class BasePlayerTieLogic(gameLogic: BaseGameLogic) extends PlayerTieLogic
|
||||
private[control] var tieBreakerIndex: Int = -1
|
||||
private[control] var lastNumber = -1
|
||||
private[control] var selectedCard: Map[AbstractPlayer, Card] = Map.empty
|
||||
private var _waitingForInput: Boolean = false
|
||||
|
||||
override def handleTie(roundResult: RoundResult): Unit = {
|
||||
this.roundResult = Some(roundResult)
|
||||
@@ -78,6 +79,7 @@ final class BasePlayerTieLogic(gameLogic: BaseGameLogic) extends PlayerTieLogic
|
||||
}
|
||||
|
||||
override def requestTieChoice(player: AbstractPlayer): Unit = {
|
||||
_waitingForInput = true
|
||||
gameLogic.invoke(TieTurnEvent(player))
|
||||
gameLogic.invoke(RequestTieChoiceEvent(player, highestAllowedNumber()))
|
||||
}
|
||||
@@ -90,9 +92,12 @@ final class BasePlayerTieLogic(gameLogic: BaseGameLogic) extends PlayerTieLogic
|
||||
val player = tiedPlayers(tieBreakerIndex)
|
||||
val highestNumber = highestAllowedNumber()
|
||||
if (number < 0 || number > highestNumber)
|
||||
throw new IllegalArgumentException(s"Selected number $number is out of allowed range (1 to $highestNumber)")
|
||||
throw new IllegalArgumentException(s"Selected number $number is out of allowed range (0 to $highestNumber)")
|
||||
|
||||
if (gameLogic.cardManager.isEmpty) throw new IllegalStateException("No card manager set")
|
||||
|
||||
_waitingForInput = false
|
||||
|
||||
val cardManager = gameLogic.cardManager.get
|
||||
val card = cardManager.removeCards(number).last
|
||||
selectedCard += (player -> card)
|
||||
@@ -108,6 +113,10 @@ final class BasePlayerTieLogic(gameLogic: BaseGameLogic) extends PlayerTieLogic
|
||||
remainingCards - (tiedPlayers.size - selectedCard.size - 1)
|
||||
}
|
||||
|
||||
override def isWaitingForInput: Boolean = _waitingForInput
|
||||
|
||||
|
||||
|
||||
override def createSnapshot(): LogicSnapshot[BasePlayerTieLogic.this.type] = BasePlayerTieLogicSnapshot(this).asInstanceOf[LogicSnapshot[BasePlayerTieLogic.this.type]]
|
||||
|
||||
// Getter
|
||||
@@ -122,7 +131,7 @@ final class BasePlayerTieLogic(gameLogic: BaseGameLogic) extends PlayerTieLogic
|
||||
class BasePlayerTieLogicSnapshot(
|
||||
//Round result
|
||||
val winners: List[AbstractPlayer],
|
||||
val tricked: List[AbstractPlayer],
|
||||
val tricked: List[ResultPlayer],
|
||||
val notTricked: List[AbstractPlayer],
|
||||
|
||||
val tiedPlayers: List[AbstractPlayer],
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package de.knockoutwhist.control.controllerBaseImpl.sublogic.util
|
||||
|
||||
object PersistenceUtil {
|
||||
|
||||
}
|
||||
@@ -7,19 +7,9 @@ import de.knockoutwhist.rounds.{Round, Trick}
|
||||
object PlayerUtil {
|
||||
|
||||
def canPlayCard(card: Card, round: Round, trick: Trick, player: AbstractPlayer): Boolean = {
|
||||
if (trick.firstCard.isDefined) {
|
||||
val firstCard = trick.firstCard.get
|
||||
if (firstCard.suit != card.suit) {
|
||||
val alternatives: List[Card] = for cardInHand <- player.currentHand().get.cards
|
||||
if cardInHand.suit == firstCard.suit
|
||||
yield cardInHand
|
||||
if (round.trumpSuit == card.suit && alternatives.isEmpty) {
|
||||
return true
|
||||
}
|
||||
if (alternatives.nonEmpty) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
val alternatives = alternativeCards(card, round, trick, player)
|
||||
if (alternatives.nonEmpty) {
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ object RoundUtil {
|
||||
val winners = tricksMapped
|
||||
.filter((p, i) => i == maxTricks)
|
||||
.keys.toList
|
||||
val trickedPlayers = tricksMapped.keys.toList
|
||||
val trickedPlayers = tricksMapped.map((p, i) => ResultPlayer(p, i)).toList
|
||||
val notTrickedPlayers = matchImpl.playersIn.filterNot(trickedPlayers.contains)
|
||||
RoundResult(winners, trickedPlayers, notTrickedPlayers)
|
||||
}
|
||||
@@ -36,6 +36,8 @@ object RoundUtil {
|
||||
|
||||
}
|
||||
|
||||
case class RoundResult(winners: List[AbstractPlayer], tricked: List[AbstractPlayer], notTricked: List[AbstractPlayer]) {
|
||||
case class RoundResult(winners: List[AbstractPlayer], tricked: List[ResultPlayer], notTricked: List[AbstractPlayer]) {
|
||||
def isTie: Boolean = winners.size > 1
|
||||
}
|
||||
|
||||
case class ResultPlayer(player: AbstractPlayer, amountOfTricks: Int)
|
||||
|
||||
@@ -10,6 +10,8 @@ trait PlayerInputLogic {
|
||||
def requestCard(player: AbstractPlayer): Unit
|
||||
def receivedCard(card: Card): Unit
|
||||
def receivedDog(dog: Option[Card]): Unit
|
||||
|
||||
def isWaitingForInput: Boolean
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ trait PlayerTieLogic extends SnapshottingGameLogic {
|
||||
def receivedTieBreakerCard(number: Int): Unit
|
||||
def highestAllowedNumber(): Int
|
||||
|
||||
def isWaitingForInput: Boolean
|
||||
|
||||
def getRoundResult: Option[RoundResult]
|
||||
def getTiedPlayers: List[AbstractPlayer]
|
||||
def getTieBreakerIndex: Int
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.knockoutwhist.events.global
|
||||
|
||||
import de.knockoutwhist.player.AbstractPlayer
|
||||
import de.knockoutwhist.rounds.Trick
|
||||
import de.knockoutwhist.utils.events.SimpleEvent
|
||||
|
||||
case class CardPlayedEvent(player: AbstractPlayer, trick: Trick) extends SimpleEvent {
|
||||
override def id: String = s"CardPlayedEvent"
|
||||
}
|
||||
@@ -3,6 +3,6 @@ package de.knockoutwhist.events.global
|
||||
import de.knockoutwhist.player.AbstractPlayer
|
||||
import de.knockoutwhist.utils.events.SimpleEvent
|
||||
|
||||
case class RoundEndEvent(winner: AbstractPlayer) extends SimpleEvent {
|
||||
case class RoundEndEvent(winner: AbstractPlayer, amountOfTricks: Int) extends SimpleEvent {
|
||||
override def id: String = s"RoundEndEvent()"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.knockoutwhist.events.global
|
||||
|
||||
import de.knockoutwhist.cards.Suit
|
||||
import de.knockoutwhist.player.AbstractPlayer
|
||||
import de.knockoutwhist.utils.events.SimpleEvent
|
||||
|
||||
case class TrumpSelectedEvent(suit: Suit) extends SimpleEvent{
|
||||
|
||||
override def id: String = "TrumpSelectEvent"
|
||||
|
||||
}
|
||||
34
src/main/scala/de/knockoutwhist/ui/gui/CardUtils.scala
Normal file
34
src/main/scala/de/knockoutwhist/ui/gui/CardUtils.scala
Normal file
@@ -0,0 +1,34 @@
|
||||
package de.knockoutwhist.ui.gui
|
||||
|
||||
import de.knockoutwhist.cards.Card
|
||||
import de.knockoutwhist.cards.CardValue.*
|
||||
import de.knockoutwhist.cards.Suit.{Clubs, Diamonds, Hearts, Spades}
|
||||
import scalafx.scene.image.Image
|
||||
|
||||
object CardUtils {
|
||||
def cardtoImage(card: Card): Image = {
|
||||
val s = card.suit match {
|
||||
case Spades => "S"
|
||||
case Hearts => "H"
|
||||
case Clubs => "C"
|
||||
case Diamonds => "D"
|
||||
}
|
||||
val cv = card.cardValue match {
|
||||
case Ace => "A"
|
||||
case King => "K"
|
||||
case Queen => "Q"
|
||||
case Jack => "J"
|
||||
case Ten => "T"
|
||||
case Nine => "9"
|
||||
case Eight => "8"
|
||||
case Seven => "7"
|
||||
case Six => "6"
|
||||
case Five => "5"
|
||||
case Four => "4"
|
||||
case Three => "3"
|
||||
case Two => "2"
|
||||
}
|
||||
new Image(f"cards/$cv$s.png")
|
||||
|
||||
}
|
||||
}
|
||||
150
src/main/scala/de/knockoutwhist/ui/gui/GUIMain.scala
Normal file
150
src/main/scala/de/knockoutwhist/ui/gui/GUIMain.scala
Normal file
@@ -0,0 +1,150 @@
|
||||
package de.knockoutwhist.ui.gui
|
||||
|
||||
import atlantafx.base.theme.PrimerDark
|
||||
import de.knockoutwhist.control.GameLogic
|
||||
import de.knockoutwhist.control.GameState.{InGame, Lobby, MainMenu, TieBreak}
|
||||
import de.knockoutwhist.events.global.{CardPlayedEvent, GameStateChangeEvent, MatchEndEvent, NewRoundEvent, RoundEndEvent, TrickEndEvent, TrumpSelectEvent, TrumpSelectedEvent, TurnEvent}
|
||||
import de.knockoutwhist.events.global.tie.{TieShowPlayerCardsEvent, TieTieEvent, TieTurnEvent, TieWinningPlayersEvent}
|
||||
import de.knockoutwhist.events.player.{RequestCardEvent, RequestTieChoiceEvent}
|
||||
import de.knockoutwhist.player.AbstractPlayer
|
||||
import de.knockoutwhist.ui.UI
|
||||
import de.knockoutwhist.utils.CustomThread
|
||||
import de.knockoutwhist.utils.events.{EventListener, SimpleEvent}
|
||||
import javafx.application as jfxa
|
||||
import scalafx.application.JFXApp3.PrimaryStage
|
||||
import scalafx.application.{JFXApp3, Platform}
|
||||
import scalafx.beans.property.ObjectProperty
|
||||
import scalafx.scene.{Parent, Scene}
|
||||
|
||||
import scala.compiletime.uninitialized
|
||||
import scala.util.Try
|
||||
|
||||
class GUIMain extends JFXApp3 with EventListener with UI {
|
||||
|
||||
private var platformReady: Boolean = false
|
||||
private var currentRoot: Parent = uninitialized
|
||||
private var _logic: Option[GameLogic] = None
|
||||
|
||||
//UIS
|
||||
private var _mainMenu: MainMenu = uninitialized
|
||||
private var _game: Game = uninitialized
|
||||
private var _tieMenu: TieMenu = uninitialized
|
||||
private var _pickTrumpsuit: PickTrumsuit = uninitialized
|
||||
private var _winnerScreen: WinnerScreen = uninitialized
|
||||
|
||||
def mainMenu: MainMenu = _mainMenu
|
||||
|
||||
def logic: Option[GameLogic] = _logic
|
||||
|
||||
override def listen(event: SimpleEvent): Unit = {
|
||||
while (!platformReady) {
|
||||
Thread.sleep(100)
|
||||
}
|
||||
Platform.runLater {
|
||||
event match {
|
||||
case event: GameStateChangeEvent =>
|
||||
if (event.newState == InGame) {
|
||||
_game.createGame()
|
||||
} else if (event.newState == MainMenu) {
|
||||
_mainMenu.createMainMenu
|
||||
} else if (event.newState == Lobby) {
|
||||
_mainMenu.createPlayeramountmenu()
|
||||
} else if (event.newState == TieBreak) {
|
||||
_tieMenu.spawnTieMain()
|
||||
}
|
||||
case event: TieWinningPlayersEvent =>
|
||||
_tieMenu.updateWinnerLabel(event)
|
||||
|
||||
case event: TieTieEvent =>
|
||||
_tieMenu.showTieAgain(event)
|
||||
case event: MatchEndEvent =>
|
||||
_winnerScreen.spawnWinnerScreen(event.winner)
|
||||
case event: TrickEndEvent =>
|
||||
_game.showFinishedTrick(event)
|
||||
case event: TieTurnEvent =>
|
||||
_tieMenu.updatePlayerLabel(event.player)
|
||||
_tieMenu.changeSlider(logic.get.playerTieLogic.highestAllowedNumber())
|
||||
case event: NewRoundEvent =>
|
||||
_game.updateTrumpSuit(logic.get.getCurrentRound.get.trumpSuit)
|
||||
_game.resetFirstCard()
|
||||
case event: RoundEndEvent =>
|
||||
_game.showWon(event.winner, event.amountOfTricks)
|
||||
case event: TurnEvent =>
|
||||
_game.updateStatus(event.player)
|
||||
case event: TieShowPlayerCardsEvent =>
|
||||
val cards = logic.get.playerTieLogic.getSelectedCard
|
||||
_tieMenu.addCutCards(cards.map((p, c) => (p, c)).toList)
|
||||
case event: CardPlayedEvent =>
|
||||
_game.updatePlayedCards()
|
||||
if (event.trick.firstCard.isDefined)
|
||||
_game.updateFirstCard(event.trick.firstCard.get)
|
||||
case event: TrumpSelectedEvent =>
|
||||
_game.updateTrumpSuit(event.suit)
|
||||
case event: RequestCardEvent =>
|
||||
_game.updateNextPlayer(_logic.get.getPlayerQueue.get, _logic.get.getPlayerQueue.get.currentIndex)
|
||||
_game.updateTrumpSuit(_logic.get.getCurrentRound.get.trumpSuit)
|
||||
_game.updatePlayerCards(event.player)
|
||||
_game.updatePlayedCards()
|
||||
if (_logic.get.getCurrentTrick.get.firstCard.isDefined)
|
||||
_game.updateFirstCard(_logic.get.getCurrentTrick.get.firstCard.get)
|
||||
else
|
||||
_game.resetFirstCard()
|
||||
case RequestTieChoiceEvent =>
|
||||
_tieMenu.showNeccessary()
|
||||
case event: RequestTieChoiceEvent =>
|
||||
_pickTrumpsuit.showPickTrumpsuit(event.player)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def initial(logic: GameLogic): Boolean = {
|
||||
this._logic = Some(logic)
|
||||
new GUIThread(this).start()
|
||||
true
|
||||
}
|
||||
|
||||
override def start(): Unit = {
|
||||
_game = new Game(this)
|
||||
_mainMenu = new MainMenu(this)
|
||||
_tieMenu = new TieMenu(this)
|
||||
_pickTrumpsuit = new PickTrumsuit(this)
|
||||
_winnerScreen = new WinnerScreen(this)
|
||||
|
||||
currentRoot = mainMenu.current_root
|
||||
val cont = ObjectProperty(currentRoot)
|
||||
JFXApp3.userAgentStylesheet_=(new PrimerDark().getUserAgentStylesheet)
|
||||
stage = new PrimaryStage {
|
||||
width = 800
|
||||
height = 600
|
||||
title = "Knockout Whist"
|
||||
scene = new Scene {
|
||||
root = currentRoot
|
||||
cont.onChange(Platform.runLater {
|
||||
root = currentRoot
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_mainMenu.createMainMenu
|
||||
stage.show()
|
||||
platformReady = true
|
||||
}
|
||||
|
||||
override def stopApp(): Unit = {
|
||||
System.exit(0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class GUIThread(gui: GUIMain) extends CustomThread {
|
||||
|
||||
setName("GUIThread")
|
||||
|
||||
override def instance: CustomThread = this
|
||||
|
||||
override def run(): Unit = {
|
||||
gui.main(new Array[String](_length = 0))
|
||||
}
|
||||
|
||||
}
|
||||
407
src/main/scala/de/knockoutwhist/ui/gui/Game.scala
Normal file
407
src/main/scala/de/knockoutwhist/ui/gui/Game.scala
Normal file
@@ -0,0 +1,407 @@
|
||||
package de.knockoutwhist.ui.gui
|
||||
|
||||
import atlantafx.base.theme.Styles
|
||||
import de.knockoutwhist.cards.{Card, Hand, Suit}
|
||||
import de.knockoutwhist.control.ControlThread
|
||||
import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.{MatchUtil, PlayerUtil}
|
||||
import de.knockoutwhist.events.global.TrickEndEvent
|
||||
import de.knockoutwhist.player.AbstractPlayer
|
||||
import de.knockoutwhist.rounds.Trick
|
||||
import de.knockoutwhist.undo.commands.{PlayCardCommand, PlayDogCardCommand}
|
||||
import de.knockoutwhist.utils.CustomPlayerQueue
|
||||
import de.knockoutwhist.utils.Implicits.*
|
||||
import de.knockoutwhist.utils.gui.Animations
|
||||
import javafx.scene.image
|
||||
import javafx.scene.layout.{BackgroundImage, BackgroundPosition, BackgroundRepeat, BackgroundSize}
|
||||
import scalafx.geometry.Insets
|
||||
import scalafx.geometry.Pos.{BottomCenter, Center, CenterRight, TopCenter}
|
||||
import scalafx.scene.control.Alert.AlertType
|
||||
import scalafx.scene.control.{Alert, Button, Label}
|
||||
import scalafx.scene.image.{Image, ImageView}
|
||||
import scalafx.scene.layout.Priority.{Always, Never}
|
||||
import scalafx.scene.layout.{Background, BorderPane, HBox, VBox}
|
||||
import scalafx.scene.text.{Font, TextAlignment}
|
||||
import scalafx.scene.{Node, layout}
|
||||
import scalafx.util.Duration
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
|
||||
class Game(gui: GUIMain) {
|
||||
|
||||
private val statusLabel: Label = new Label {
|
||||
alignment = Center
|
||||
textAlignment = TextAlignment.Center
|
||||
text = "It's {Player?}s turn:"
|
||||
font = Font.font(35)
|
||||
hgrow = Always
|
||||
maxWidth = Double.MaxValue
|
||||
}
|
||||
|
||||
private val suitLabel: Label = new Label {
|
||||
alignment = Center
|
||||
textAlignment = TextAlignment.Left
|
||||
minWidth = 300
|
||||
maxWidth = 300
|
||||
text = "TrumpSuit: "
|
||||
font = Font.font(18)
|
||||
hgrow = Always
|
||||
}
|
||||
|
||||
private val nextPlayers: VBox = new VBox {
|
||||
alignment = TopCenter
|
||||
spacing = 10
|
||||
}
|
||||
private val yourCardslabel: Label = new Label {
|
||||
alignment = Center
|
||||
text = "Your Cards"
|
||||
vgrow = Always
|
||||
font = Font.font(20)
|
||||
margin = Insets(50, 0, 0, 0)
|
||||
}
|
||||
private val playedCardslabel: Label = new Label {
|
||||
alignment = Center
|
||||
text = "Played Cards"
|
||||
vgrow = Always
|
||||
font = Font.font(20)
|
||||
}
|
||||
private val firstCardlabel: Label = new Label {
|
||||
alignment = TopCenter
|
||||
textAlignment = TextAlignment.Center
|
||||
text = "First Card: "
|
||||
vgrow = Always
|
||||
font = Font.font(24)
|
||||
}
|
||||
|
||||
private val firstCard: ImageView = new ImageView {
|
||||
alignmentInParent = BottomCenter
|
||||
image = new Image("cards/1B.png")
|
||||
fitWidth = 170
|
||||
fitHeight = 250
|
||||
onMouseClicked = _ => System.exit(0)
|
||||
}
|
||||
|
||||
private val playedCards: HBox = new HBox {
|
||||
alignment = BottomCenter
|
||||
spacing = 10
|
||||
}
|
||||
|
||||
private val playerCards: HBox = new HBox {
|
||||
alignment = BottomCenter
|
||||
spacing = 10
|
||||
margin = Insets(30, 0, 20, 0)
|
||||
}
|
||||
|
||||
def createGame(): Unit = {
|
||||
gui.stage.maximized = true
|
||||
gui.mainMenu.changeChild(new BorderPane {
|
||||
val myBI = new BackgroundImage(new Image("/background.png", 32, 32, false, true),
|
||||
BackgroundRepeat.REPEAT, BackgroundRepeat.REPEAT, BackgroundPosition.DEFAULT,
|
||||
BackgroundSize.DEFAULT)
|
||||
background = Background(Array(new layout.BackgroundImage(myBI)))
|
||||
padding = Insets(10, 10, 10, 10)
|
||||
top = new HBox {
|
||||
alignment = Center
|
||||
hgrow = Always
|
||||
spacing = 0
|
||||
children = Seq(
|
||||
suitLabel,
|
||||
statusLabel,
|
||||
new HBox {
|
||||
hgrow = Always
|
||||
alignment = CenterRight
|
||||
spacing = 10
|
||||
minWidth = 300
|
||||
maxWidth = 300
|
||||
children = Seq(
|
||||
new Button {
|
||||
hgrow = Never
|
||||
alignment = CenterRight
|
||||
text = "Save"
|
||||
font = Font.font(20)
|
||||
styleClass += Styles.WARNING
|
||||
onMouseClicked = _ => {
|
||||
gui.logic.get.persistenceManager.saveFile("currentSnapshot")
|
||||
new Alert(AlertType.Information) {
|
||||
title = "Game Saved"
|
||||
headerText = "Game Saved"
|
||||
contentText = "This game was stored safely in currentSnapshot :)"
|
||||
}.showAndWait()
|
||||
|
||||
}
|
||||
},
|
||||
new Button {
|
||||
hgrow = Never
|
||||
alignment = CenterRight
|
||||
text = "Undo"
|
||||
font = Font.font(20)
|
||||
styleClass += Styles.WARNING
|
||||
onMouseClicked = _ => {
|
||||
gui.logic.get.undoManager.undoStep()
|
||||
}
|
||||
},
|
||||
new Button {
|
||||
hgrow = Never
|
||||
alignment = CenterRight
|
||||
text = "Exit Game"
|
||||
font = Font.font(20)
|
||||
styleClass += Styles.DANGER
|
||||
onMouseClicked = _ => {
|
||||
gui.logic.get.endSession()
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
center = new VBox {
|
||||
alignment = TopCenter
|
||||
children = Seq(
|
||||
playedCardslabel,
|
||||
playedCards,
|
||||
)
|
||||
}
|
||||
left = new VBox {
|
||||
margin = Insets(30, 0, 0, 30)
|
||||
alignment = TopCenter
|
||||
minWidth = 300
|
||||
maxWidth = 300
|
||||
children = Seq(
|
||||
new Label {
|
||||
alignment = TopCenter
|
||||
textAlignment = TextAlignment.Center
|
||||
text = "Next Players: "
|
||||
vgrow = Always
|
||||
font = Font.font(24)
|
||||
},
|
||||
nextPlayers
|
||||
)
|
||||
}
|
||||
right = new VBox {
|
||||
margin = Insets(30, 0, 0, 30)
|
||||
alignment = TopCenter
|
||||
minWidth = 300
|
||||
maxWidth = 300
|
||||
children = Seq(
|
||||
firstCardlabel,
|
||||
firstCard
|
||||
)
|
||||
}
|
||||
bottom = new VBox {
|
||||
alignment = BottomCenter
|
||||
children = Seq(
|
||||
yourCardslabel,
|
||||
playerCards,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
def updateStatus(player: AbstractPlayer): Unit = {
|
||||
val s = player.name.endsWith("s") ? "" |: "s"
|
||||
statusLabel.text = s"It's ${player.name}$s turn:"
|
||||
nextPlayers.visible = true
|
||||
playerCards.visible = true
|
||||
yourCardslabel.visible = true
|
||||
playedCardslabel.visible = true
|
||||
firstCardlabel.visible = true
|
||||
firstCard.visible = true
|
||||
suitLabel.visible = true
|
||||
nextPlayers.visible = true
|
||||
}
|
||||
|
||||
def updateTrumpSuit(suit: Suit): Unit = {
|
||||
suitLabel.text = s"TrumpSuit: $suit"
|
||||
}
|
||||
|
||||
def visibilityPlayerCards(visible: Boolean): Unit = {
|
||||
playerCards.visible = visible
|
||||
}
|
||||
|
||||
private def firstCardvisible(visible: Boolean): Unit = {
|
||||
firstCard.visible = visible
|
||||
}
|
||||
|
||||
def updateFirstCard(card: Card): Unit = {
|
||||
firstCardvisible(true)
|
||||
firstCard.image = CardUtils.cardtoImage(card)
|
||||
}
|
||||
|
||||
def resetFirstCard(): Unit = {
|
||||
firstCard.image = new Image("cards/1B.png")
|
||||
}
|
||||
|
||||
def updatePlayerCards(player: AbstractPlayer): Unit = {
|
||||
if (gui.logic.isEmpty) throw new IllegalStateException("Logic is not initialized!")
|
||||
val logic = gui.logic.get
|
||||
if (logic.getCurrentMatch.isEmpty) throw new IllegalStateException("No current match available!")
|
||||
val currentMatch = logic.getCurrentMatch.get
|
||||
if (logic.getCurrentRound.isEmpty) throw new IllegalStateException("No current round available!")
|
||||
val currentRound = logic.getCurrentRound.get
|
||||
if (player.currentHand().isEmpty) throw new IllegalStateException("Player has no hand!")
|
||||
val hand: Hand = player.currentHand().get
|
||||
|
||||
val cards = ListBuffer[Node]()
|
||||
playerCards.children.clear()
|
||||
for (card <- hand.cards) {
|
||||
cards += new ImageView {
|
||||
alignmentInParent = BottomCenter
|
||||
image = CardUtils.cardtoImage(card)
|
||||
fitWidth = 170
|
||||
fitHeight = 250
|
||||
onMouseClicked = _ => {
|
||||
if (logic.getCurrentTrick.isEmpty) throw new IllegalStateException("No current trick available!")
|
||||
val currentTrick = logic.getCurrentTrick.get
|
||||
if (logic.getCurrentPlayer.isDefined && logic.isWaitingForInput) {
|
||||
val currentPlayer = logic.getCurrentPlayer.get
|
||||
if (!currentPlayer.isInDogLife) {
|
||||
if (PlayerUtil.canPlayCard(card, currentRound, currentTrick, currentPlayer)) {
|
||||
val pulse = Animations.pulse(this, Duration(400))
|
||||
pulse.play()
|
||||
}
|
||||
hideCards(this)
|
||||
val slideOut = Animations.slideOutUp(this, Duration(400), -350)
|
||||
slideOut.onFinished = _ => {
|
||||
visible = false
|
||||
ControlThread.runLater {
|
||||
logic.undoManager.doStep(
|
||||
PlayCardCommand(
|
||||
logic.createSnapshot(),
|
||||
logic.playerTieLogic.createSnapshot(),
|
||||
card
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
slideOut.play()
|
||||
} else {
|
||||
hideCards(this)
|
||||
val slideOutDog = Animations.slideOutUp(this, Duration(400), -350)
|
||||
slideOutDog.onFinished = _ => {
|
||||
visible = false
|
||||
ControlThread.runLater {
|
||||
logic.undoManager.doStep(
|
||||
PlayDogCardCommand(
|
||||
logic.createSnapshot(),
|
||||
logic.playerTieLogic.createSnapshot(),
|
||||
Some(card)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
slideOutDog.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (player.isInDogLife && !MatchUtil.dogNeedsToPlay(currentMatch, currentRound)) {
|
||||
cards += new Button {
|
||||
alignmentInParent = BottomCenter
|
||||
styleClass += Styles.SUCCESS
|
||||
text = "Skip this turn"
|
||||
minWidth = 170
|
||||
maxWidth = 170
|
||||
minHeight = 250
|
||||
maxHeight = 250
|
||||
onMouseClicked = _ => {
|
||||
hideCards(this)
|
||||
val slideOutDog = Animations.slideOutUp(this, Duration(400), -350)
|
||||
slideOutDog.onFinished = _ => {
|
||||
visible = false
|
||||
ControlThread.runLater {
|
||||
logic.undoManager.doStep(
|
||||
PlayDogCardCommand(
|
||||
logic.createSnapshot(),
|
||||
logic.playerTieLogic.createSnapshot(),
|
||||
None
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
slideOutDog.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
playerCards.children = cards.toList
|
||||
}
|
||||
|
||||
private def visibilityPlayedCards(visible: Boolean): Unit = {
|
||||
playedCards.visible = visible
|
||||
}
|
||||
|
||||
def updatePlayedCards(): Unit = {
|
||||
if (gui.logic.isEmpty) throw new IllegalStateException("Logic is not initialized!")
|
||||
val logic = gui.logic.get
|
||||
if (logic.getCurrentTrick.isEmpty) throw new IllegalStateException("No current trick available!")
|
||||
val trick: Trick = logic.getCurrentTrick.get
|
||||
visibilityPlayedCards(true)
|
||||
val cards = ListBuffer[Node]()
|
||||
for (card <- trick.cards) {
|
||||
cards += new VBox {
|
||||
children = Seq(
|
||||
new Label {
|
||||
text = card._2.toString
|
||||
font = Font.font(10)
|
||||
margin = Insets(0, 0, 0, 0)
|
||||
},
|
||||
new ImageView {
|
||||
alignmentInParent = BottomCenter
|
||||
image = CardUtils.cardtoImage(card._1)
|
||||
fitWidth = 102
|
||||
fitHeight = 150
|
||||
})
|
||||
}
|
||||
}
|
||||
playedCards.children = cards
|
||||
}
|
||||
|
||||
private def hideCards(node: Node): Unit = {
|
||||
playerCards.children.foreach(child => {
|
||||
if(child != node.delegate) {
|
||||
child match
|
||||
case imageView: image.ImageView =>
|
||||
imageView.setImage(new Image("cards/1B.png"))
|
||||
case button: javafx.scene.control.Button =>
|
||||
button.setDisable(true)
|
||||
case _ =>
|
||||
val slideOut = Animations.slideOutDown(child, Duration(400), 350)
|
||||
slideOut.onFinished = _ => {
|
||||
child.setVisible(false)
|
||||
}
|
||||
slideOut.play()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
def updateNextPlayer(queue: CustomPlayerQueue[AbstractPlayer], currendIndx: Int): Unit = {
|
||||
val queueDupli = queue.duplicate()
|
||||
nextPlayers.children = queueDupli.iteratorWithStart(currendIndx).map(player => new Label {
|
||||
text = !player.isInDogLife ? player.name |: s"${player.name} (Doglife)"
|
||||
font = Font.font(20)
|
||||
}).toSeq
|
||||
}
|
||||
|
||||
def showWon(winner: AbstractPlayer, amountOfTricks: Int): Unit = {
|
||||
nextPlayers.visible = false
|
||||
playerCards.visible = false
|
||||
yourCardslabel.visible = false
|
||||
playedCardslabel.visible = false
|
||||
firstCardlabel.visible = false
|
||||
firstCard.visible = false
|
||||
suitLabel.visible = false
|
||||
nextPlayers.visible = false
|
||||
if (amountOfTricks == 1) statusLabel.text = s"${winner.name} won the round with $amountOfTricks trick!"
|
||||
else statusLabel.text = s"${winner.name} won the round with $amountOfTricks tricks!"
|
||||
}
|
||||
|
||||
def showFinishedTrick(event: TrickEndEvent): Unit = {
|
||||
nextPlayers.visible = false
|
||||
playerCards.visible = false
|
||||
yourCardslabel.visible = false
|
||||
playedCardslabel.visible = false
|
||||
statusLabel.text = s"${event.winner.name} won the trick"
|
||||
updatePlayedCards()
|
||||
}
|
||||
}
|
||||
217
src/main/scala/de/knockoutwhist/ui/gui/MainMenu.scala
Normal file
217
src/main/scala/de/knockoutwhist/ui/gui/MainMenu.scala
Normal file
@@ -0,0 +1,217 @@
|
||||
package de.knockoutwhist.ui.gui
|
||||
|
||||
import atlantafx.base.theme.Styles
|
||||
import de.knockoutwhist.KnockOutWhist
|
||||
import de.knockoutwhist.control.controllerBaseImpl.sublogic.BasePersistenceManager
|
||||
import de.knockoutwhist.control.ControlThread
|
||||
import de.knockoutwhist.player.Playertype.HUMAN
|
||||
import de.knockoutwhist.player.{AbstractPlayer, PlayerFactory}
|
||||
import de.knockoutwhist.ui.tui.TUIMain
|
||||
import de.knockoutwhist.utils.gui.Animations
|
||||
import javafx.scene.{Node, control}
|
||||
import scalafx.animation.Timeline
|
||||
import scalafx.geometry.Insets
|
||||
import scalafx.geometry.Pos.{BottomCenter, Center, TopCenter, TopLeft, TopRight}
|
||||
import scalafx.scene.Parent
|
||||
import scalafx.scene.control.Alert.AlertType
|
||||
import scalafx.scene.control.*
|
||||
import scalafx.scene.image.{Image, ImageView}
|
||||
import scalafx.scene.layout.Priority.Always
|
||||
import scalafx.scene.layout.{BorderPane, HBox, StackPane, VBox}
|
||||
import scalafx.scene.text.{Font, TextAlignment}
|
||||
import scalafx.util.Duration
|
||||
|
||||
import java.awt.Taskbar.{Feature, getTaskbar}
|
||||
import scala.collection.mutable
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
class MainMenu(gui: GUIMain) {
|
||||
|
||||
private val mainMenu: StackPane = new StackPane()
|
||||
|
||||
def current_root: Parent = mainMenu
|
||||
|
||||
def createMainMenu: StackPane = {
|
||||
gui.stage.maximized = false
|
||||
gui.stage.resizable = true
|
||||
changeChild(new VBox {
|
||||
alignment = Center
|
||||
spacing = 10
|
||||
margin = Insets(0, 0, 150, 0)
|
||||
children = Seq(
|
||||
new ImageView {
|
||||
image = new Image("/KnockOutLogo.png")
|
||||
fitWidth = 200
|
||||
fitHeight = 200
|
||||
},
|
||||
new Button {
|
||||
alignment = TopCenter
|
||||
hgrow = Always
|
||||
text = "Start Game"
|
||||
font = Font.font(25)
|
||||
styleClass += Styles.SUCCESS
|
||||
onMouseClicked = _ => {
|
||||
ControlThread.runLater {
|
||||
gui.logic.get.createSession()
|
||||
}
|
||||
}
|
||||
},
|
||||
new Button {
|
||||
alignment = TopCenter
|
||||
hgrow = Always
|
||||
text = "Exit Game"
|
||||
font = Font.font(20)
|
||||
styleClass += Styles.DANGER
|
||||
onMouseClicked = _ => System.exit(0)
|
||||
},
|
||||
new Button {
|
||||
alignment = TopCenter
|
||||
hgrow = Always
|
||||
text = "Load Game"
|
||||
font = Font.font(20)
|
||||
styleClass += Styles.ACCENT
|
||||
disable = gui.logic.isEmpty || gui.logic.get.persistenceManager.canLoadfile("currentSnapshot")
|
||||
onMouseClicked = _ => gui.logic.get.persistenceManager.loadFile("currentSnapshot")
|
||||
}
|
||||
)
|
||||
}, Duration(1000))
|
||||
mainMenu
|
||||
}
|
||||
|
||||
def changeChild(child: Parent, duration: Duration = Duration(500)): Unit = {
|
||||
val times = ListBuffer[Timeline]()
|
||||
mainMenu.children.foreach(node => {
|
||||
times += Animations.fadeOutLeft(node, duration)
|
||||
node.setDisable(true)
|
||||
})
|
||||
val fadeIn = Animations.fadeInRight(child, duration)
|
||||
mainMenu.children += child
|
||||
times.foreach(_.play())
|
||||
fadeIn.play()
|
||||
fadeIn.onFinished = _ => {
|
||||
mainMenu.children = Seq(child)
|
||||
}
|
||||
}
|
||||
def createPlayeramountmenu(): Unit = {
|
||||
gui.stage.maximized = true
|
||||
changeChild(new BorderPane {
|
||||
margin = Insets(50, 50, 50, 50)
|
||||
val players: VBox = new VBox {
|
||||
vgrow = Always
|
||||
alignment = TopCenter
|
||||
spacing = 20
|
||||
margin = Insets(0, 0, 50, 0)
|
||||
}
|
||||
top = new HBox() {
|
||||
alignment = TopCenter
|
||||
children = new ImageView {
|
||||
image = new Image("/KnockOutLogo.png")
|
||||
fitWidth = 200
|
||||
fitHeight = 200
|
||||
}
|
||||
}
|
||||
left = new HBox {
|
||||
alignment = TopCenter
|
||||
children = new Button {
|
||||
alignment = TopRight
|
||||
styleClass += Styles.BUTTON_CIRCLE
|
||||
styleClass += Styles.ACCENT
|
||||
graphic = new ImageView {
|
||||
image = new Image("return-icon.png")
|
||||
fitWidth = 20
|
||||
fitHeight = 20
|
||||
}
|
||||
onMouseClicked = _ => {
|
||||
ControlThread.runLater {
|
||||
gui.logic.get.endSession()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
right = new HBox {
|
||||
alignment = TopCenter
|
||||
children = new Button {
|
||||
styleClass += Styles.SUCCESS
|
||||
styleClass += Styles.BUTTON_CIRCLE
|
||||
graphic = new ImageView {
|
||||
image = new Image("checkmark.png")
|
||||
fitWidth = 20
|
||||
fitHeight = 20
|
||||
}
|
||||
onMouseClicked = _ => {
|
||||
val usedNames = ListBuffer[String]()
|
||||
val playerNamesList = ListBuffer[AbstractPlayer]()
|
||||
players.children.foreach {
|
||||
case field: control.TextField =>
|
||||
if (field.getText.nonEmpty && !usedNames.contains(field.getText)) {
|
||||
usedNames += field.getText
|
||||
playerNamesList += PlayerFactory.createPlayer(field.getText, playertype = HUMAN)
|
||||
}
|
||||
case _ =>
|
||||
}
|
||||
if(playerNamesList.size < 2) {
|
||||
new Alert(AlertType.Error) {
|
||||
title = "Enter Names"
|
||||
headerText = "Enter Names " + playerNamesList.size
|
||||
contentText = "You need to enter at least 2 different names in order to play!"
|
||||
}.showAndWait()
|
||||
}else {
|
||||
ControlThread.runLater {
|
||||
gui.logic.get.createMatch(playerNamesList.toList)
|
||||
gui.logic.get.controlMatch()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
center = new VBox{
|
||||
alignment = TopCenter
|
||||
children = Seq(
|
||||
new Label {
|
||||
alignment = TopCenter
|
||||
textAlignment = TextAlignment.Center
|
||||
text = "Select Playeramount below:"
|
||||
font = Font.font(30)
|
||||
},
|
||||
new Slider {
|
||||
min = 2
|
||||
max = 7
|
||||
showTickLabels = true
|
||||
showTickMarks = true
|
||||
majorTickUnit = 1
|
||||
minorTickCount = 0
|
||||
snapToTicks = true
|
||||
maxWidth = 450
|
||||
maxHeight = 30
|
||||
value.onChange((_, oldvalue, newvalue) => {
|
||||
if(oldvalue.intValue() > newvalue.intValue()) {
|
||||
for (i <- oldvalue.intValue()-1 to(newvalue.intValue(), -1)) {
|
||||
players.children.remove(i)
|
||||
}
|
||||
}else if(oldvalue.intValue() < newvalue.intValue()) {
|
||||
for (i <- oldvalue.intValue() + 1 to newvalue.intValue()) {
|
||||
players.children.add(new TextField {
|
||||
promptText = s"Enter Player $i"
|
||||
visible = true
|
||||
maxWidth = 450
|
||||
maxHeight = 30
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
players
|
||||
)
|
||||
}
|
||||
for (i <- 1 to 2) {
|
||||
players.children.add(new TextField {
|
||||
promptText = s"Enter Player $i"
|
||||
visible = true
|
||||
maxWidth = 450
|
||||
maxHeight = 30
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
161
src/main/scala/de/knockoutwhist/ui/gui/PickTrumsuit.scala
Normal file
161
src/main/scala/de/knockoutwhist/ui/gui/PickTrumsuit.scala
Normal file
@@ -0,0 +1,161 @@
|
||||
package de.knockoutwhist.ui.gui
|
||||
|
||||
import de.knockoutwhist.KnockOutWhist
|
||||
import de.knockoutwhist.cards.Suit
|
||||
import de.knockoutwhist.control.ControlThread
|
||||
import de.knockoutwhist.player.AbstractPlayer
|
||||
import de.knockoutwhist.undo.commands.SelectTrumpSuitCommand
|
||||
import de.knockoutwhist.utils.gui.Animations
|
||||
import scalafx.geometry.Insets
|
||||
import scalafx.geometry.Pos.{BottomCenter, TopCenter}
|
||||
import scalafx.scene.control.Label
|
||||
import scalafx.scene.image.{Image, ImageView}
|
||||
import scalafx.scene.layout.Priority.Always
|
||||
import scalafx.scene.layout.{HBox, StackPane, VBox}
|
||||
import scalafx.scene.text.Font
|
||||
import scalafx.util.Duration
|
||||
|
||||
import scala.util.Try
|
||||
|
||||
class PickTrumsuit(gui: GUIMain) {
|
||||
|
||||
def showPickTrumpsuit(player: AbstractPlayer): Unit = {
|
||||
if (gui.logic.isEmpty) throw new IllegalStateException("Game logic is not initialized in GUI")
|
||||
val logicImpl = gui.logic.get
|
||||
|
||||
gui.mainMenu.changeChild(
|
||||
new StackPane {
|
||||
children = Seq(
|
||||
new VBox {
|
||||
alignment = TopCenter
|
||||
spacing = 10
|
||||
margin = Insets(20, 0, 0, 0)
|
||||
hgrow = Always
|
||||
children = Seq(
|
||||
new Label {
|
||||
alignment = TopCenter
|
||||
text = "Pick your trumpsuit"
|
||||
font = Font.font(30)
|
||||
margin = Insets(20, 0, 0, 0)
|
||||
},
|
||||
new HBox {
|
||||
alignment = BottomCenter
|
||||
spacing = 10
|
||||
margin = Insets(40, 0, 20, 0)
|
||||
children = Seq(
|
||||
new ImageView {
|
||||
alignment = BottomCenter
|
||||
image = new Image("cards/AS.png")
|
||||
fitWidth = 102
|
||||
fitHeight = 150
|
||||
onMouseClicked = _ => {
|
||||
val slideOut = Animations.slideOutUp(children.head.asInstanceOf[javafx.scene.image.ImageView], Duration(400), -350)
|
||||
slideOut.onFinished = _ => {
|
||||
visible = false
|
||||
}
|
||||
slideOut.play()
|
||||
ControlThread.runLater {
|
||||
logicImpl.undoManager.doStep(
|
||||
SelectTrumpSuitCommand(
|
||||
logicImpl.createSnapshot(),
|
||||
logicImpl.playerTieLogic.createSnapshot(),
|
||||
Suit.Spades
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
new ImageView {
|
||||
alignment = BottomCenter
|
||||
image = new Image("cards/AC.png")
|
||||
fitWidth = 102
|
||||
fitHeight = 150
|
||||
onMouseClicked = _ => {
|
||||
val slideOut = Animations.slideOutUp(this, Duration(400), -350)
|
||||
slideOut.onFinished = _ => {
|
||||
visible = false
|
||||
}
|
||||
slideOut.play()
|
||||
ControlThread.runLater {
|
||||
logicImpl.undoManager.doStep(
|
||||
SelectTrumpSuitCommand(
|
||||
logicImpl.createSnapshot(),
|
||||
logicImpl.playerTieLogic.createSnapshot(),
|
||||
Suit.Clubs
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
},new ImageView {
|
||||
alignment = BottomCenter
|
||||
image = new Image("cards/AH.png")
|
||||
fitWidth = 102
|
||||
fitHeight = 150
|
||||
onMouseClicked = _ => {
|
||||
val slideOut = Animations.slideOutUp(this, Duration(400), -350)
|
||||
slideOut.onFinished = _ => {
|
||||
visible = false
|
||||
}
|
||||
slideOut.play()
|
||||
ControlThread.runLater {
|
||||
logicImpl.undoManager.doStep(
|
||||
SelectTrumpSuitCommand(
|
||||
logicImpl.createSnapshot(),
|
||||
logicImpl.playerTieLogic.createSnapshot(),
|
||||
Suit.Hearts
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
new ImageView {
|
||||
alignment = BottomCenter
|
||||
image = new Image("cards/AD.png")
|
||||
fitWidth = 102
|
||||
fitHeight = 150
|
||||
onMouseClicked = _ => {
|
||||
val slideOut = Animations.slideOutUp(this, Duration(400), -350)
|
||||
slideOut.onFinished = _ => {
|
||||
visible = false
|
||||
}
|
||||
slideOut.play()
|
||||
ControlThread.runLater {
|
||||
logicImpl.undoManager.doStep(
|
||||
SelectTrumpSuitCommand(
|
||||
logicImpl.createSnapshot(),
|
||||
logicImpl.playerTieLogic.createSnapshot(),
|
||||
Suit.Diamonds
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
)
|
||||
},
|
||||
new Label {
|
||||
alignment = BottomCenter
|
||||
text = "Your Cards"
|
||||
font = Font.font(20)
|
||||
},
|
||||
new HBox {
|
||||
alignment = TopCenter
|
||||
alignment = BottomCenter
|
||||
spacing = 10
|
||||
margin = Insets(100, 0, 20, 0)
|
||||
children = player.currentHand().get.cards.map( i => new ImageView {
|
||||
image = CardUtils.cardtoImage(i)
|
||||
fitWidth = 170
|
||||
fitHeight = 250
|
||||
})
|
||||
}
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
185
src/main/scala/de/knockoutwhist/ui/gui/TieMenu.scala
Normal file
185
src/main/scala/de/knockoutwhist/ui/gui/TieMenu.scala
Normal file
@@ -0,0 +1,185 @@
|
||||
package de.knockoutwhist.ui.gui
|
||||
|
||||
import atlantafx.base.theme.Styles
|
||||
import de.knockoutwhist.KnockOutWhist
|
||||
import de.knockoutwhist.cards.Card
|
||||
import de.knockoutwhist.control.{ControlThread, GameLogic}
|
||||
import de.knockoutwhist.events.global.tie.{TieTieEvent, TieWinningPlayersEvent}
|
||||
import de.knockoutwhist.player.AbstractPlayer
|
||||
import de.knockoutwhist.rounds.{Match, Round}
|
||||
import de.knockoutwhist.undo.commands.SelectTieNumberCommand
|
||||
import de.knockoutwhist.utils.gui.Animations
|
||||
import javafx.scene.layout.{BackgroundImage, BackgroundPosition, BackgroundRepeat, BackgroundSize}
|
||||
import scalafx.animation.Timeline
|
||||
import scalafx.geometry.Insets
|
||||
import scalafx.geometry.Pos.{BottomCenter, Center, TopCenter}
|
||||
import scalafx.scene.control.{Button, Label, Slider}
|
||||
import scalafx.scene.image.{Image, ImageView}
|
||||
import scalafx.scene.layout.Priority.Always
|
||||
import scalafx.scene.layout.*
|
||||
import scalafx.scene.text.Font
|
||||
import scalafx.scene.{Node, Parent, layout}
|
||||
import scalafx.util.Duration
|
||||
|
||||
import scala.collection.immutable
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import scala.compiletime.uninitialized
|
||||
import scala.util.Try
|
||||
|
||||
class TieMenu(gui: GUIMain) {
|
||||
|
||||
private def logic: GameLogic = {
|
||||
gui.logic.get
|
||||
}
|
||||
|
||||
private val tieMenu: StackPane = new StackPane()
|
||||
def current_root: Parent = tieMenu
|
||||
|
||||
private val nextPlayer: Label = new Label {
|
||||
visible = false
|
||||
}
|
||||
private val slider: Slider = new Slider {
|
||||
alignmentInParent = BottomCenter
|
||||
min = 1
|
||||
max = 51
|
||||
visible = true
|
||||
showTickLabels = true
|
||||
showTickMarks = true
|
||||
majorTickUnit = 1
|
||||
minorTickCount = 0
|
||||
snapToTicks = true
|
||||
maxWidth = 1000
|
||||
maxHeight = 60
|
||||
value.onChange((_, oldvalue, newvalue) => {
|
||||
value = newvalue.doubleValue()
|
||||
})
|
||||
}
|
||||
private val toplabel: Label = new Label {
|
||||
alignment = TopCenter
|
||||
visible = true
|
||||
text = "The last round was a tie! Let's cut to determine the winner"
|
||||
font = Font.font(50)
|
||||
}
|
||||
private val selectedCutCards: HBox = new HBox {
|
||||
alignment = BottomCenter
|
||||
spacing = 10
|
||||
margin = Insets(50, 0, 150, 0)
|
||||
visible = false
|
||||
children = Seq()
|
||||
}
|
||||
private val tiewinner: Label = new Label {
|
||||
alignment = TopCenter
|
||||
font = Font.font(30)
|
||||
visible = true
|
||||
}
|
||||
private val selectButton: Button = new Button {
|
||||
text = "Select"
|
||||
styleClass += Styles.ACCENT
|
||||
onMouseClicked = _ => {
|
||||
if (slider.value.toInt >= 1 && slider.value.toInt < logic.playerTieLogic.highestAllowedNumber() && logic.isWaitingForInput) {
|
||||
ControlThread.runLater {
|
||||
logic.undoManager.doStep(
|
||||
SelectTieNumberCommand(
|
||||
logic.createSnapshot(),
|
||||
logic.playerTieLogic.createSnapshot(),
|
||||
slider.value.toInt
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
def updateWinnerLabel(event: TieWinningPlayersEvent) : Unit = {
|
||||
if (event.isSingleWinner) {
|
||||
tiewinner.text = s"${event.winners.head.name} wins the cut!"
|
||||
} else {
|
||||
tiewinner.text = ""
|
||||
}
|
||||
slider.visible = false
|
||||
toplabel.visible = false
|
||||
nextPlayer.visible = false
|
||||
selectButton.visible = false
|
||||
}
|
||||
def showNeccessary(): Unit = {
|
||||
tiewinner.text = ""
|
||||
slider.visible = true
|
||||
toplabel.visible = true
|
||||
nextPlayer.visible = true
|
||||
selectButton.visible = true
|
||||
selectedCutCards.visible = false
|
||||
}
|
||||
def showTieAgain(event: TieTieEvent): Unit = {
|
||||
tiewinner.text = "It's a tie again! Let's cut again."
|
||||
slider.visible = false
|
||||
selectButton.visible = false
|
||||
toplabel.visible = false
|
||||
nextPlayer.visible = false
|
||||
}
|
||||
def addCutCards(list: List[(AbstractPlayer, Card)]): Unit = {
|
||||
selectedCutCards.visible = true
|
||||
val cards = ListBuffer[Node]()
|
||||
for (e <- list) {
|
||||
cards += new VBox {
|
||||
alignment = BottomCenter
|
||||
children = Seq(
|
||||
new Label {
|
||||
text = s"${e._1}"
|
||||
},
|
||||
new ImageView {
|
||||
alignmentInParent = BottomCenter
|
||||
image = CardUtils.cardtoImage(e._2)
|
||||
fitWidth = 170
|
||||
fitHeight = 250
|
||||
})
|
||||
}
|
||||
selectedCutCards.children = cards
|
||||
}
|
||||
|
||||
}
|
||||
def hideCutCards(): Unit = {
|
||||
selectedCutCards.visible = false
|
||||
}
|
||||
def changeSlider(maxNumber: Int): Unit = {
|
||||
slider.max = maxNumber
|
||||
}
|
||||
def updatePlayerLabel(player: AbstractPlayer): Unit = {
|
||||
nextPlayer.visible = true
|
||||
nextPlayer.font = Font.font(20)
|
||||
nextPlayer.text = s"It's $player's turn to select the tie card."
|
||||
}
|
||||
def spawnTieMain(): Unit = {
|
||||
gui.mainMenu.changeChild(
|
||||
new VBox {
|
||||
alignment = TopCenter
|
||||
spacing = 10
|
||||
margin = Insets(50, 0, 150, 0)
|
||||
children = Seq(
|
||||
toplabel,
|
||||
nextPlayer,
|
||||
slider,
|
||||
selectButton,
|
||||
tiewinner,
|
||||
selectedCutCards
|
||||
)
|
||||
//}
|
||||
},Duration(1000))
|
||||
}
|
||||
|
||||
def changeChild(child: Parent, duration: Duration = Duration(500)): Unit = {
|
||||
val times = ListBuffer[Timeline]()
|
||||
tieMenu.children.foreach(node => times += Animations.fadeOutLeft(node, duration))
|
||||
val fadeIn = Animations.fadeInRight(child, duration)
|
||||
tieMenu.children += child
|
||||
times.foreach(_.play())
|
||||
fadeIn.play()
|
||||
fadeIn.onFinished = _ => {
|
||||
tieMenu.children = Seq(child)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object TieData {
|
||||
var winners: List[AbstractPlayer] = uninitialized
|
||||
var remainingCards: Int = uninitialized
|
||||
}
|
||||
75
src/main/scala/de/knockoutwhist/ui/gui/WinnerScreen.scala
Normal file
75
src/main/scala/de/knockoutwhist/ui/gui/WinnerScreen.scala
Normal file
@@ -0,0 +1,75 @@
|
||||
package de.knockoutwhist.ui.gui
|
||||
|
||||
import atlantafx.base.theme.Styles
|
||||
import de.knockoutwhist.KnockOutWhist
|
||||
import de.knockoutwhist.control.ControlThread
|
||||
import de.knockoutwhist.player.AbstractPlayer
|
||||
import scalafx.geometry.Insets
|
||||
import scalafx.geometry.Pos.{BottomCenter, Center, TopCenter}
|
||||
import scalafx.scene.control.{Button, Label}
|
||||
import scalafx.scene.layout.Priority.Always
|
||||
import scalafx.scene.layout.{HBox, VBox}
|
||||
import scalafx.scene.text.Font
|
||||
import scalafx.util.Duration
|
||||
|
||||
class WinnerScreen(gui: GUIMain) {
|
||||
private val winnerLabel: Label = new Label {
|
||||
text = ""
|
||||
alignment = Center
|
||||
font = Font.font(25)
|
||||
}
|
||||
private val actionButtons: HBox = new HBox {
|
||||
alignment = BottomCenter
|
||||
spacing = 10
|
||||
margin = Insets(0, 0, 150, 0)
|
||||
children = Seq(
|
||||
new Button {
|
||||
alignment = TopCenter
|
||||
text = "Yes"
|
||||
font = Font.font(20)
|
||||
styleClass += Styles.SUCCESS
|
||||
onMouseClicked = _ => {
|
||||
ControlThread.runLater {
|
||||
gui.logic.foreach(_.createSession())
|
||||
}
|
||||
}
|
||||
},
|
||||
new Button {
|
||||
alignment = TopCenter
|
||||
hgrow = Always
|
||||
text = "No"
|
||||
font = Font.font(20)
|
||||
styleClass += Styles.DANGER
|
||||
onMouseClicked = _ =>
|
||||
System.exit(0)
|
||||
}
|
||||
)
|
||||
}
|
||||
private val nextAction: Label = new Label {
|
||||
text = "Do you want to play another match?"
|
||||
alignment = BottomCenter
|
||||
font = Font.font(20)
|
||||
margin = Insets(50,0,0,0)
|
||||
}
|
||||
def spawnWinnerScreen(player: AbstractPlayer): Unit = {
|
||||
gui.mainMenu.changeChild(
|
||||
new VBox {
|
||||
alignment = TopCenter
|
||||
spacing = 10
|
||||
margin = Insets(50, 0, 150, 0)
|
||||
children = Seq(
|
||||
new Label {
|
||||
text = s"Congratulations! $player won this match of Knock-Out-Whist."
|
||||
alignment = TopCenter
|
||||
font = Font.font(35)
|
||||
},
|
||||
nextAction,
|
||||
actionButtons
|
||||
)
|
||||
//}
|
||||
},Duration(1000))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import de.knockoutwhist.control.{ControlThread, GameLogic}
|
||||
import de.knockoutwhist.events.*
|
||||
import de.knockoutwhist.events.global.*
|
||||
import de.knockoutwhist.events.global.tie.*
|
||||
import de.knockoutwhist.events.player.{PlayCardEvent, RequestTieChoiceEvent, RequestTrumpSuitEvent}
|
||||
import de.knockoutwhist.events.player.{RequestCardEvent, RequestTieChoiceEvent, RequestTrumpSuitEvent}
|
||||
import de.knockoutwhist.player.Playertype.HUMAN
|
||||
import de.knockoutwhist.player.{AbstractPlayer, PlayerFactory}
|
||||
import de.knockoutwhist.ui.UI
|
||||
@@ -68,12 +68,12 @@ class TUIMain extends CustomThread with EventListener with UI {
|
||||
case event: TurnEvent =>
|
||||
if (logic.get.getCurrentTrick.isEmpty) {
|
||||
println("No trick found!")
|
||||
return Some(true)
|
||||
return
|
||||
}
|
||||
val trickImpl = logic.get.getCurrentTrick.get
|
||||
if (logic.get.getCurrentRound.isEmpty) {
|
||||
println("No round found!")
|
||||
return Some(true)
|
||||
return
|
||||
}
|
||||
val roundImpl = logic.get.getCurrentRound.get
|
||||
TUIUtil.clearConsole()
|
||||
@@ -110,7 +110,7 @@ class TUIMain extends CustomThread with EventListener with UI {
|
||||
println(s"The winner(s) of the tie-breaker: ${event.winners.map(_.name).mkString(", ")}")
|
||||
case event: RequestTieChoiceEvent =>
|
||||
reqnumbereventmet(event)
|
||||
case event: PlayCardEvent =>
|
||||
case event: RequestCardEvent =>
|
||||
if (event.player.isInDogLife) reqdogeventmet(event)
|
||||
else reqcardeventmet(event)
|
||||
case event: RequestTrumpSuitEvent =>
|
||||
@@ -128,7 +128,7 @@ class TUIMain extends CustomThread with EventListener with UI {
|
||||
}
|
||||
|
||||
|
||||
object TUICards {
|
||||
private object TUICards {
|
||||
def renderCardAsString(card: Card): Vector[String] = {
|
||||
val lines = "│ │"
|
||||
if (card.cardValue == CardValue.Ten) {
|
||||
@@ -272,7 +272,7 @@ class TUIMain extends CustomThread with EventListener with UI {
|
||||
Some(true)
|
||||
}
|
||||
|
||||
private def reqcardeventmet(event: PlayCardEvent): Option[Boolean] = {
|
||||
private def reqcardeventmet(event: RequestCardEvent): Option[Boolean] = {
|
||||
println("Which card do you want to play?")
|
||||
if (logic.isEmpty) throw new IllegalStateException("Logic is not initialized")
|
||||
val logicImpl = logic.get
|
||||
@@ -322,7 +322,7 @@ class TUIMain extends CustomThread with EventListener with UI {
|
||||
Some(true)
|
||||
}
|
||||
|
||||
private def reqdogeventmet(event: PlayCardEvent): Option[Boolean] = {
|
||||
private def reqdogeventmet(event: RequestCardEvent): Option[Boolean] = {
|
||||
println("You are using your dog life. Do you want to play your final card now?")
|
||||
if (event.player.currentHand().isEmpty) {
|
||||
println("You have no cards to play! This should not happen.")
|
||||
|
||||
Reference in New Issue
Block a user