74 Commits

Author SHA1 Message Date
3dac384f50 feat(ui): FRO-26 Tie selection (#63)
Changed Delay in Logic for better experience

Co-authored-by: LQ63 <lkhermann@web.de>
Reviewed-on: #63
2026-01-22 01:09:15 +01:00
0b35e1a649 feat: Update ID mapping in OpenIDUserInfo to use hashed value and remove name field 2026-01-21 12:25:38 +01:00
77a44fa17b feat: CORE-4 Rework the delay handler (#62)
Reviewed-on: #62
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2026-01-14 10:12:02 +01:00
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
b9a7b0a2af feat(user-sessions): enhance game lobby management with user session handling and game full exception (#51)
Reviewed-on: #51
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-11-01 20:53:41 +01:00
fbc0ea2277 fix(ui): add light mode styles, update font families and fixed ui (#50)
Reviewed-on: #50
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-10-28 19:06:52 +01:00
f998d5f6f0 fix(imports): reorganized import statements for clarity and consistency (#49)
Reviewed-on: #49
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-10-26 18:09:45 +01:00
c5895f094b fix(base): fixed persistence logic (#48)
Fixed persistence logic for Trump Selection and Save File Loading

Reviewed-on: #48
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-10-26 18:03:20 +01:00
8645d4a219 test(base): added some tests to improve the coverage (#47)
Reviewed-on: #47
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-10-22 20:12:03 +02:00
c9e44bc604 fix(base): fixed logic regarding dog player detection (#46)
Reviewed-on: #46
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-10-22 20:11:21 +02:00
7dad9052f7 fix(base): no dogs after the first round (#45)
Stopped people from becoming dogs in the first round

Reviewed-on: #45
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-10-22 15:08:09 +02:00
65921c289a Merge pull request 'finished: webapplication-logic-refactor' (#44) from webapplication-logic-refactor into webapplication
Reviewed-on: #44
2025-10-22 13:08:02 +02:00
cc4bc9d93e Fix tie number selection logic to include highest allowed number 2025-10-22 10:34:49 +02:00
c43cc7cac0 Fixes 2025-10-22 08:20:47 +02:00
e17ab6a552 Various fixes 2025-10-22 08:16:22 +02:00
786204dfaf Rename PlayCardEvent to RequestCardEvent for clarity in event handling 2025-10-19 20:49:25 +02:00
026c666f03 Refactor game logic to track player input state; add CardPlayedEvent and update RoundEndEvent with trick count 2025-10-19 20:48:45 +02:00
6335cbee23 Fixed TUI refactor and logic errors 2025-10-19 13:30:25 +02:00
16857fe42c TUI finished 2025-10-18 01:28:42 +02:00
1a189ef0fe Add core game logic components and event handling for player actions 2025-10-17 20:39:57 +02:00
f9a6e6af05 Refactor player classes and enhance game logic; add tie handling and player input management 2025-10-17 16:25:46 +02:00
11834a7d44 Refactor match handling and session management; add player session functionality and update event handling in WebUI 2025-10-13 15:16:41 +02:00
51ef1f1607 Provided the match with an ID 2025-10-10 20:17:25 +02:00
7676dd8ed1 Add WebUI and ingame route, update Configuration and DefaultConfiguration for UI integration 2025-10-10 13:31:50 +02:00
51604124fd Add initial application structure with HomeController and routing 2025-10-09 13:25:05 +02:00
48cd4d3956 Add migration state XML files and update build configuration 2025-10-09 10:52:04 +02:00
d54f814192 Merge pull request 'Implemented Docker' (#43) from dockertest into master
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
Reviewed-on: #43
2025-01-23 18:09:24 +01:00
05b6ebb2a8 DOCKER WORKS
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2025-01-23 17:46:18 +01:00
079e66a5cf Docker File update
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2025-01-23 17:00:18 +01:00
220c3d04ea Hotfix for Presentation
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2025-01-23 16:04:39 +01:00
a6e514880c Docker File implementation
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2025-01-23 13:05:10 +01:00
ec329df768 Hotfix for Presentation
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2025-01-23 09:38:18 +01:00
a856843fc3 Fixed Tests v2
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2025-01-20 14:08:19 +01:00
LQ63
f57537b3eb updated WinnerScreen
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2025-01-20 13:34:07 +01:00
6c825845ea Merge branch 'master' into FileIO
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2025-01-20 13:32:43 +01:00
5b37ebb01d Fixed Tests 2025-01-20 13:31:54 +01:00
2b63fbe88d Merge pull request 'FileIO' (#42) from FileIO into master
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
Reviewed-on: #42
2025-01-20 13:04:14 +01:00
LQ63
0e31134dfb better winning screen
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2025-01-18 19:54:26 +01:00
LQ63
d0ab531e16 Finished Basic functionality for Gui. Includes working cut, (very) basic winning screen 2025-01-18 17:16:18 +01:00
b6812d1223 Fixed Dog Play + Savegames 2025-01-18 15:17:06 +01:00
LQ63
169537c479 Added new Stuff for the gui, added support for gamesaving and loading. 2025-01-17 09:18:10 +01:00
aad61fd10a Load File improvements 2025-01-17 09:09:53 +01:00
d9b1bb2186 Tie fixes 2025-01-17 09:00:10 +01:00
d48e3b1daa File ending support 2025-01-17 08:55:45 +01:00
fa8cab1d6f FileIO 2025-01-16 21:53:31 +01:00
a53f4dda46 Tried fixing CT 2025-01-16 21:09:21 +01:00
483991bdbb Reduced Critical Section 2025-01-16 21:06:58 +01:00
bd73997959 XML FileIO Done 2025-01-16 20:24:40 +01:00
3a41a4bb4d Current FileIO State 2025-01-16 18:59:04 +01:00
474de82cde Fixed GUI 2025-01-16 18:47:59 +01:00
d39e92cfa8 Fixed PersistenceManager 2025-01-16 18:04:13 +01:00
e1d7405f51 FileIO Impl 2025-01-16 17:49:41 +01:00
a33e404378 Start of fileio 2025-01-15 20:24:25 +01:00
LQ63
2e0d24adbd changed tests
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2025-01-14 21:13:57 +01:00
13199ddd74 Dependency Injection 2025-01-09 14:35:45 +01:00
3396355193 Config Version yay
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-12-20 10:54:29 +01:00
LQ63
111242a6aa removed comments
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-12-20 10:52:37 +01:00
LQ63
681fdd4253 Merge remote-tracking branch 'origin/Components' into Components
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-12-20 10:51:26 +01:00
LQ63
3fe3abe7fc removed comments 2024-12-20 10:51:09 +01:00
e0b7a68207 Components yayyy
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-12-20 10:48:46 +01:00
LQ63
67a590e544 Configuration
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-12-20 10:38:19 +01:00
LQ63
8742c5dc7a Mock PlayerQueue + startet config
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-12-20 10:33:20 +01:00
LQ63
7a46bed011 Mock PlayerQueue
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-12-20 09:30:34 +01:00
LQ63
9ecf91282e Merge remote-tracking branch 'origin/Components' into Components 2024-12-20 09:29:55 +01:00
LQ63
0526f99464 Mock PlayerQueue 2024-12-20 09:29:32 +01:00
9e9941cd70 Merge pull request 'Hotfix Code Coverage' (#41) from development into master
All checks were successful
Build and Test (KnockOutWhist) TeamCity build finished
Reviewed-on: #41
2024-11-29 12:31:41 +01:00
182 changed files with 5248 additions and 3793 deletions

6
.idea/copilot.data.migration.agent.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

6
.idea/copilot.data.migration.ask.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AskMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Ask2AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

6
.idea/copilot.data.migration.edit.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EditMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

1
DOCKER-COMMANDS Normal file
View File

@@ -0,0 +1 @@
docker run -it --rm -e DISPLAY=host.docker.internal:0 -v /tmp/.X11-unix:/tmp/.X11-unix knockout:v1

35
Dockerfile Normal file
View File

@@ -0,0 +1,35 @@
FROM sbtscala/scala-sbt:eclipse-temurin-jammy-22_36_1.10.0_3.4.2
WORKDIR /knockout
ENV DEBIAN_FRONTEND=noninterface
RUN apt-get update && apt-get install -y curl gnupg2 x11-apps
RUN apt-get update && apt-get install -y \
libxext6 \
libxrender1 \
libxtst6 \
libxi6 \
libxrandr2 \
libgtk-3-0 \
xorg
RUN curl -sL https://dlcdn.apache.org/sbt/debian/sbt-1.9.4.deb -o sbt.deb
RUN dpkg -i sbt.deb || apt-get install -f -y
ENV DEBIAN_FRONTEND=dialog
RUN sbt update
ENV DISPLAY=:99
ENV SBT_OPTS="-Xms512M -Xmx1536M -Xss2M -XX:MaxMetaspaceSize=512M"
ENV _JAVA_OPTIONS="-Djava.security.policy=applet.policy -Dprism.order=sw"
VOLUME /tmp/.X11-unix
COPY . /knockout
RUN sbt compile
CMD ["sh", "-c", "sbt run"]

View File

@@ -1,51 +0,0 @@
Compile/mainClass := Some("de.knockoutwhist.KnockOutWhist")
name := "KnockOutWhist"
version := {
val major = sys.env.getOrElse("MAJOR_VERSION", "0")
val minor = sys.env.getOrElse("MINOR_VERSION", "0")
val buildNR = sys.env.getOrElse("BUI_COUNTER", "1")
s"$major.$minor.$buildNR"
}
ThisBuild / organization := "de.knockoutwhist"
ThisBuild / version := version.value
ThisBuild / scalaVersion := "3.5.1"
lazy val root = (project in file("."))
.settings(
name := "Projekt-zu-SE",
assembly / mainClass := Some("de.knockoutwhist.KnockOutWhist"),
assembly / assemblyJarName := s"KnockOutWhist-${version.value}.jar",
)
libraryDependencies += "org.scalactic" %% "scalactic" % "3.2.18"
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.18" % "test"
libraryDependencies +="io.github.mkpaz" % "atlantafx-base" % "2.0.1"
libraryDependencies += "org.scalafx" %% "scalafx" % "22.0.0-R33"
libraryDependencies ++= {
// Determine OS version of JavaFX binaries
lazy val osName = System.getProperty("os.name") match {
case n if n.startsWith("Linux") => "linux"
case n if n.startsWith("Mac") => "mac"
case n if n.startsWith("Windows") => "win"
case _ => throw new Exception("Unknown platform!")
}
Seq("base", "controls", "fxml", "graphics", "media", "swing", "web")
.map(m => "org.openjfx" % s"javafx-$m" % "21" classifier osName)
}
Test / testOptions += Tests.Filter(_.equals("de.knockoutwhist.TestSequence"))
coverageEnabled := true
coverageFailOnMinimum := true
coverageMinimumStmtTotal := 85
coverageMinimumBranchTotal := 100

View File

@@ -1 +0,0 @@
sbt.version = 1.10.2

View File

@@ -1,3 +0,0 @@
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.2.1")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.3.0")
addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.3.1")

View File

@@ -1,30 +1,44 @@
package de.knockoutwhist
import de.knockoutwhist.control.{ControlHandler, ControlThread}
import de.knockoutwhist.events.ui.GameState.MAIN_MENU
import de.knockoutwhist.events.ui.GameStateUpdateEvent
import de.knockoutwhist.ui.gui.GUIMain
import de.knockoutwhist.ui.tui.TUIMain
import com.google.inject.{Guice, Injector}
import de.knockoutwhist.components.Configuration
import de.knockoutwhist.control.ControlThread
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
import de.knockoutwhist.di.KnockOutConfigurationModule
import de.knockoutwhist.utils.events.EventListener
object KnockOutWhist {
/*
Debug mode:
Debug mode
- Disables the random shuffle of the cards
*/
private[knockoutwhist] var DEBUG_MODE_VAR: Boolean = false
private var _config: Option[Configuration] = None
def config: Configuration = _config.get
def debugmode: Boolean = DEBUG_MODE_VAR
def main(args: Array[String]): Unit = {
ControlThread.start()
if(!TUIMain.initial) throw new IllegalStateException("TUI could not be started.")
if(!GUIMain.initial) throw new IllegalStateException("GUI could not be started.")
ControlThread.runLater {
ControlHandler.invoke(GameStateUpdateEvent(MAIN_MENU))
val injector: Injector = Guice.createInjector(KnockOutConfigurationModule())
val config: Configuration = injector.getInstance(classOf[Configuration])
entry(config)
}
def entry(configuration: Configuration): Unit = {
_config = Some(configuration)
val baseLogic = BaseGameLogic(configuration)
for (handler <- configuration.listener) baseLogic.addListener(handler)
for (ui <- configuration.uis) {
if (!ui.initial(baseLogic)) throw new IllegalStateException(s"${ui.getClass.getName} could not be started.")
ui match {
case eventListener: EventListener => baseLogic.addListener(eventListener)
case _ =>
}
}
}

View File

@@ -0,0 +1,9 @@
//start from 0, stop at 9 inclusive
for (i <- 0 until 10){
println("Hi " + i)
}
//or start from 0, stop at 9 inclusive
for (i <- 0 to 9){
println("Hi " + i)
}

View File

@@ -33,4 +33,13 @@ end CardValue
case class Card(cardValue: CardValue, suit: Suit) {
override def toString: String = s"$cardValue of $suit"
override def canEqual(that: Any): Boolean = that.isInstanceOf[Card]
override def equals(obj: Any): Boolean = obj match {
case that: Card =>
this.cardValue == that.cardValue &&
this.suit == that.suit
case _ => false
}
}

View File

@@ -1,29 +1,25 @@
package de.knockoutwhist.cards
trait CardManager {
/**
* The container of cards, where all cards are stored
* @return List[Card]
*/
def cardContainer: List[Card]
/**
* Shuffles the card container and resets the current index
*/
def shuffleAndReset(): Unit
/**
* Resets the order of the card container and resets the current index
*/
def resetOrder(): Unit
/**
* Returns the next card in the card container
* @return Card
*/
def nextCard(): Card
/**
* Creates a hand of cards
* @param amount The amount of cards in the hand
* @return Hand
*/
def remainingCards: Int = cardContainer.size - currentIndx
def removeCards(amount: Int): List[Card]
def createHand(amount: Int = 7): Hand
def grabSpecificCard(card: Card): Card
def currentIndx: Int
def setState(cc: List[Card], currentIndex: Int): Unit
}

View File

@@ -5,8 +5,12 @@ import de.knockoutwhist.cards.*
import scala.collection.mutable.ListBuffer
import scala.util.Random
object CardBaseManager extends CardManager {
class CardBaseManager extends CardManager {
override def setState(cc: List[Card], currentIndex: Int): Unit = {
this.cc = cc
this.currentIdx = currentIndex
}
override def cardContainer: List[Card] = cc
private var cc: List[Card] = {
@@ -20,6 +24,8 @@ object CardBaseManager extends CardManager {
}
private var currentIdx = 0
override def currentIndx: Int = currentIdx
override def shuffleAndReset(): Unit = {
cc = Random.shuffle(cc)
currentIdx = 0
@@ -31,13 +37,19 @@ object 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] = {
val removedCards = ListBuffer[Card]()
for (_ <- 0 to amount) {
removedCards += nextCard()
}
removedCards.toList
}
override def createHand(amount: Int = 7): Hand = {
@@ -48,4 +60,7 @@ object CardBaseManager extends CardManager {
Hand(hand.toList)
}
override def grabSpecificCard(card: Card): Card = {
cc.filter(c => c.suit == card.suit && c.cardValue == card.cardValue).head
}
}

View File

@@ -1,16 +0,0 @@
package de.knockoutwhist.cards.mock
import de.knockoutwhist.cards.{Card, CardManager, CardValue, Hand, Suit}
object MockCardManager extends CardManager {
override def cardContainer: List[Card] = List()
override def shuffleAndReset(): Unit = {}
override def resetOrder(): Unit = {}
override def nextCard(): Card = Card(CardValue.Ace, Suit.Clubs)
override def createHand(amount: Int): Hand = Hand(List(Card(CardValue.Ace, Suit.Clubs)))
}

View File

@@ -0,0 +1,23 @@
package de.knockoutwhist.cards.stub
import de.knockoutwhist.cards.*
object StubCardManager extends CardManager {
override def cardContainer: List[Card] = List()
override def shuffleAndReset(): Unit = {}
override def resetOrder(): Unit = {}
override def nextCard(): Card = Card(CardValue.Ace, Suit.Clubs)
override def removeCards(amount: Int): List[Card] = List(Card(CardValue.Ace, Suit.Clubs))
override def createHand(amount: Int): Hand = Hand(List(Card(CardValue.Ace, Suit.Clubs)))
override def grabSpecificCard(card: Card): Card = card
override def currentIndx: Int = -1
override def setState(cc: List[Card], currentIndex: Int): Unit = {}
}

View File

@@ -0,0 +1,18 @@
package de.knockoutwhist.components
import de.knockoutwhist.cards.CardManager
import de.knockoutwhist.persistence.formats.FileFormatter
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.ui.UI
import de.knockoutwhist.utils.CustomPlayerQueue
import de.knockoutwhist.utils.events.EventListener
trait Configuration {
def cardManager: CardManager
def fileFormatter: FileFormatter
def uis: Set[UI]
def listener: Set[EventListener]
def createRightQueue(players: Array[AbstractPlayer], start: Int = 0): CustomPlayerQueue[AbstractPlayer]
}

View File

@@ -0,0 +1,38 @@
package de.knockoutwhist.components
import com.google.inject.Guice
import de.knockoutwhist.cards.CardManager
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
import de.knockoutwhist.utils.baseQueue.QueueBuilder
import de.knockoutwhist.utils.events.EventListener
import scala.language.postfixOps
class DefaultConfiguration extends Configuration {
private val injector = Guice.createInjector(KnockOutLogicModule(), KnockOutConfigurationModule())
override def cardManager: CardManager = injector.getInstance(classOf[CardManager])
override def fileFormatter: FileFormatter = injector.getInstance(classOf[FileFormatter])
override def uis: Set[UI] = Set[UI](
TUIMain(),
GUIMain()
)
override def listener: Set[EventListener] = Set[EventListener](
utils.DelayHandler
)
override def createRightQueue(players: Array[AbstractPlayer], start: Int): CustomPlayerQueue[AbstractPlayer] = {
val builder = injector.getInstance(classOf[QueueBuilder])
builder.setStart(start)
builder.setPlayer(players)
builder.build()
}
}

View File

@@ -1,34 +0,0 @@
package de.knockoutwhist.control
import de.knockoutwhist.cards.CardManager
import de.knockoutwhist.cards.base.CardBaseManager
import de.knockoutwhist.control.controllerBaseImpl.{MainLogic, MatchLogic, PlayerControl, PlayerLogic, RoundLogic, TrickLogic}
import de.knockoutwhist.ui.gui.GUIMain
import de.knockoutwhist.ui.tui.TUIMain
import de.knockoutwhist.utils.{CustomThread, DelayHandler}
import de.knockoutwhist.utils.events.EventHandler
object ControlHandler extends EventHandler {
addListener(GUIMain)
addListener(TUIMain)
addListener(DelayHandler)
val maincomponent: Maincomponent = MainLogic
val matchcomponent: Matchcomponent = MatchLogic
val playeractrcomponent: Playeractrcomponent = PlayerControl
val playerlogcomponent: Playerlogcomponent = PlayerLogic
val roundlogcomponent: Roundlogcomponent = RoundLogic
val trickcomponent: Tricklogcomponent = TrickLogic
val cardManager: CardManager = CardBaseManager
}
object ControlThread extends CustomThread {
setName("ControlThread")
def isControlThread: Boolean = Thread.currentThread().equals(ControlThread)
override def instance: CustomThread = ControlThread
}

View File

@@ -0,0 +1,9 @@
package de.knockoutwhist.control
object ControlThread {
def runLater[R](op: => R): Unit = {
op
}
}

View File

@@ -0,0 +1,42 @@
package de.knockoutwhist.control
import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.RoundResult
import de.knockoutwhist.control.sublogic.{PersistenceManager, PlayerInputLogic, PlayerTieLogic, UndoManager}
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round, Trick}
import de.knockoutwhist.utils.CustomPlayerQueue
import de.knockoutwhist.utils.events.EventHandler
trait GameLogic extends EventHandler with SnapshottingGameLogic {
def createSession(): Unit
def endSession(): Unit
def createMatch(players: List[AbstractPlayer]): Match
def controlMatch(): Unit
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
def providePlayersWithCards(): Unit
def isWaitingForInput: Boolean
def playerInputLogic: PlayerInputLogic
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

@@ -0,0 +1,12 @@
package de.knockoutwhist.control
enum GameState {
case MainMenu
case Lobby
case InGame
case SelectTrump
case TieBreak
case FinishedMatch
}

View File

@@ -1,75 +0,0 @@
package de.knockoutwhist.control
import de.knockoutwhist.cards.Card
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round, Trick}
trait Maincomponent {
/**
* Starts the game
*
*/
def startMatch(): Unit
/**
* Creates a match with the entered players by calling the UndoManager.
*
* @param players The players selected by the user.
*/
def enteredPlayers(players: List[AbstractPlayer]): Unit
/**
* Checks if a match is finished.
* If it is not finished, this method deals cards to the players that are currently in the game.
* @param matchImpl The current match.
*/
def controlMatch(matchImpl: Match): Unit
/**
* Checks if a round is finished.
* If this is not the case, it calls the control trick method.
* If this is the case, it finalizes the round and checks for multiple winners.
* @param matchImpl The current match
* @param round The current round
*/
def controlRound(matchImpl: Match, round: Round): Unit
/**
* This method adds the finished round to the match and calls controlMatch.
* @param matchImpl The current Match.
* @param round The finished round.
* @param winner The winner(s) of that round.
* @param playersOut The players that lost in this round.
*/
def endRound(matchImpl: Match, round: Round, winner: AbstractPlayer, playersOut: List[AbstractPlayer]): Unit
/**
* This method checks, if a trick is finished.
* If so, it finalizes the trick and adds it to the current round.
* If not, it calls controlPlayer to let the player play a card.
* @param matchImpl The current match.
* @param round The current round.
* @param trick The current trick.
* @param currentIndex The index of the player queue that decides which players turn it is.
*/
def controlTrick(matchImpl: Match, round: Round, trick: Trick, currentIndex: Int = 0): Unit
/**
* Checks in which state the player is in (normal/ dog life) and lets the player play his card accordingly.
* @param matchImpl The current match.
* @param round The current round.
* @param trick The current trick.
* @param player The currently playing player
* @param currentIndex The index of the player queue.
*/
def controlPlayer(matchImpl: Match, round: Round, trick: Trick, player: AbstractPlayer, currentIndex: Int): Unit
/**
* Adds the specified card by the player to the trick.
* @param trick The current trick.
* @param card The card a player wants to play.
* @param player The player that is allowed to play a card.
*/
def playCard(trick: Trick, card: Card, player: AbstractPlayer): Trick
}

View File

@@ -1,12 +0,0 @@
package de.knockoutwhist.control
import de.knockoutwhist.rounds.Match
trait Matchcomponent {
/**
* Checks if the match is finished
* @param matchImpl The current match
* @return Returns true if it is finished, false if it is not
*/
def isOver(matchImpl: Match): Boolean
}

View File

@@ -1,39 +0,0 @@
package de.knockoutwhist.control
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round, Trick}
trait Playeractrcomponent {
/**
* This method invokes some events to signal the player to play a valid card.
* Then it calls handlePlayCard to get the card that the player eventually plays.
* @param matchImpl The current match.
* @param player The player whose turn it is to play a card.
* @param round The current round.
* @param trick The current trick.
* @param currentIndex The current index of the player queue.
*/
def playCard(matchImpl: Match, player: AbstractPlayer, round: Round, trick: Trick, currentIndex: Int): Unit
/**
* This method invokes some events to signal the player who is in dog mode to play a valid card.
* Then it calls handlePlayCard to get the card that the player eventually plays or doesn't.
* @param matchImpl The current match.
* @param player The player whose turn it is to play a card.
* @param round The current round.
* @param trick The current trick
* @param currentIndex The current index of the player queue.
*/
def dogplayCard(matchImpl: Match, player: AbstractPlayer, round: Round, trick: Trick, currentIndex: Int): Unit
/**
*This method invokes some events to signal the player who won the last round to call a trumpsuit.
*
*
* @param matchImpl The current match.
* @param remaining_players The players that are still in the game.
* @param firstRound
* @param player The player whose turn it is to play a card.
*/
def pickNextTrumpsuit(matchImpl: Match, remaining_players: List[AbstractPlayer], firstRound: Boolean, player: AbstractPlayer): Unit
}

View File

@@ -1,77 +0,0 @@
package de.knockoutwhist.control
import de.knockoutwhist.cards.{Card, Suit}
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round}
import scala.collection.immutable
import scala.util.Try
trait Playerlogcomponent {
/**
* Checks, if the round played is the first round or not.
* If it is, it selects a random trump suit.
* If not it lets a player decide.
* @param matchImpl The current match.
* @param remaining_players The players that are still in the game.
*/
def trumpsuitStep(matchImpl: Match, remaining_players: List[AbstractPlayer]): Unit
/**
* Checks if the selection worked. It then calls the UndoManager to set the trumpsuit.
* @param matchImpl The current match.
* @param suit The selected suit.
* @param remaining_players The players that are still in the game.
* @param firstRound If it is the first round.
* @param decided The player that selected the trumpsuit.
*/
def trumpSuitSelected(matchImpl: Match, suit: Try[Suit], remaining_players: List[AbstractPlayer], firstRound: Boolean, decided: AbstractPlayer): Unit
/**
* Shuffles the cards for the cut and calls selecttie to manage the cut.
* @param winners The players that won the previous round and are now cutting to determine the trumpsuit caller.
* @param matchImpl The current match.
* @param round The current round.
* @param playersout The players that are out of this game.
*/
def preSelect(winners: List[AbstractPlayer], matchImpl: Match, round: Round, playersout: List[AbstractPlayer]): Unit
/**
* Checks if the cut is finished.
* If it is, the method calls evaluatewinnertie to determine the winner of the cut.
* If not, the method lets the next player pick a card.
* @param winners The players that won the previous round and are now cutting to determine the trumpsuit caller.
* @param matchImpl The current match.
* @param round The current round.
* @param playersout The players that are out of the game.
* @param cut A HashMap that stores the selected cut cards.
* @param currentStep The cards that are unavailable due to the card picks from other players.
* @param remaining The remaining cards in the deck for the cut.
* @param currentIndex The index of the player that is allowed to pick a card.
*/
def selectTie(winners: List[AbstractPlayer], matchImpl: Match, round: Round, playersout: List[AbstractPlayer], cut: immutable.HashMap[AbstractPlayer, Card], currentStep: Int, remaining: Int, currentIndex: Int = 0): Unit
/**
* Checks if the selected number is in the range of valid numbers and calls the UndoManager to add the card to the cut etc.
* @param winner The players that won the previous round and are now cutting to determine the trumpsuit caller.
* @param matchImpl The current match.
* @param round The current round.
* @param playersout The players that are out of the game.
* @param cut A HashMap that stores the selected cut cards.
* @param value Selected Number and whether it is valid.
* @param currentStep The cards that are unavailable due to the card picks from other players.
* @param remaining The remaining cards in the deck for the cut.
* @param currentIndex The index of the player that is allowed to pick a card.
*/
def selectedTie(winner: List[AbstractPlayer],matchImpl: Match, round: Round, playersout: List[AbstractPlayer], cut: immutable.HashMap[AbstractPlayer, Card], value: Try[Int], currentStep: Int, remaining: Int, currentIndex: Int = 0): Unit
/**
* This method looks for the winner of the finished cut. If it finds two or more winners, it calls preselect to restart the
* cut.
* @param matchImpl The current match.
* @param round The current round.
* @param playersout The players that are out of the game.
* @param cut A HashMap that stores the selected cut cards.
*/
def evaluateTieWinner(matchImpl: Match, round: Round, playersout: List[AbstractPlayer], cut: immutable.HashMap[AbstractPlayer, Card]): Unit
}

View File

@@ -1,52 +0,0 @@
package de.knockoutwhist.control
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round}
trait Roundlogcomponent {
/**
* Checks if the current round is finished.
* @param round The current round.
* @return True if the round is over, false if it isn't.
*/
def isOver(round: Round): Boolean
/**
* Checks if the dogplayer(s) have to play their card.
* @param round The current round.
* @return True if they have to play their card, false if they don't.
*/
def dogNeedsToPlay(round: Round): Boolean
/**
* Checks if the round is finished and then finalizes it by determining the winner of the round.
* It then determines the players that lost and sets the right players to dog-mode.
* @param round The current round.
* @param matchImpl The current match.
* @param force A debug-parameter with which rounds can get finalized even if they aren't finished.
* @return
*/
def finalizeRound(round: Round, matchImpl: Match, force: Boolean = false): (Match, Round, List[AbstractPlayer], List[AbstractPlayer])
/**
* Gets the remaining players in a round
* @param round The current round
* @return The remaining players as List.
*/
def remainingPlayers(round: Round): List[AbstractPlayer]
/**
* Provides the exact number of cards a player needs.
* @param matchImpl The current match.
* @param players The players that are still in the game
* @return The current Match and players.
*/
def provideCards(matchImpl: Match, players: List[AbstractPlayer]): (Match,List[AbstractPlayer])
/**
* Immutable broke us. If you don't know why, increase this counter: 1
* @param round The current round.
* @return Returns the current round.
*/
def smashResults(round: Round): Round
}

View File

@@ -0,0 +1,13 @@
package de.knockoutwhist.control
trait SnapshottingGameLogic {
def createSnapshot(): LogicSnapshot[this.type]
}
trait LogicSnapshot[T <: SnapshottingGameLogic] {
def restore(logic: T): Unit
}

View File

@@ -1,50 +0,0 @@
package de.knockoutwhist.control
import de.knockoutwhist.cards.Card
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round, Trick}
import scala.util.Try
trait Tricklogcomponent {
/**
* Checks if the card number is valid and if it is valid, if the player is allowed to play it.
* @param card The card that a player wants to play.
* @param matchImpl The current match.
* @param round The current round.
* @param trick The current trick.
* @param currentIndex The index of the player queue.
* @param player The player that wants to play a card.
*/
def controlSuitplayed(card: Try[Card], matchImpl: Match, round: Round, trick: Trick, currentIndex: Int, player: AbstractPlayer): Unit
/**
* Checks if the card number is valid for the doglife player.
* The method then calls the UndoManager to play the card.
* @param card
* @param matchImpl
* @param round
* @param trick
* @param currentIndex
* @param player
*/
def controlDogPlayed(card: Try[Option[Card]], matchImpl: Match, round: Round, trick: Trick, currentIndex: Int, player: AbstractPlayer): Unit
/**
* Checks the options that a player has to play a card and doesn't let the player play a wrong card.
* @param card The current card.
* @param round The current round.
* @param trick The current trick.
* @param player The player that wants to play the card.
* @return A List of cards that are possible to be played.
*/
def alternativeCards(card: Card, round: Round, trick: Trick, player: AbstractPlayer): List[Card]
/**
* Checks which player won the trick.
* @param trick The current finished trick.
* @param round The current round.
* @return Returns the player that won the trick and the trick this player won.
*/
def wonTrick(trick: Trick, round: Round): (AbstractPlayer, Trick)
}

View File

@@ -0,0 +1,443 @@
package de.knockoutwhist.control.controllerBaseImpl
import de.knockoutwhist.cards.{Card, CardManager}
import de.knockoutwhist.components.Configuration
import de.knockoutwhist.control.GameState.*
import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.{MatchUtil, RoundResult, RoundUtil, TrickUtil}
import de.knockoutwhist.control.controllerBaseImpl.sublogic.{BasePersistenceManager, BasePlayerInputLogic, BasePlayerTieLogic, BaseUndoManager}
import de.knockoutwhist.control.sublogic.{PersistenceManager, PlayerInputLogic, PlayerTieLogic}
import de.knockoutwhist.control.{GameLogic, GameState, LogicSnapshot}
import de.knockoutwhist.events.global.*
import de.knockoutwhist.events.global.tie.TieEvent
import de.knockoutwhist.events.player.ReceivedHandEvent
import de.knockoutwhist.events.util.DelayEvent
import de.knockoutwhist.persistence.MethodEntryPoint.{ControlMatch, ControlRound}
import de.knockoutwhist.player.{AbstractPlayer, PlayerData}
import de.knockoutwhist.rounds.{Match, Round, Trick}
import de.knockoutwhist.utils.CustomPlayerQueue
import de.knockoutwhist.utils.events.EventHandler
import java.util.UUID
import scala.util.Random
/*
Main game logic controller
*/
final class BaseGameLogic(val config: Configuration) extends EventHandler with GameLogic {
//Constants
val ID: UUID = UUID.randomUUID()
//Logics
val playerTieLogic: PlayerTieLogic = BasePlayerTieLogic(this)
val playerInputLogic: PlayerInputLogic = BasePlayerInputLogic(this)
val undoManager: BaseUndoManager = BaseUndoManager(this)
val persistenceManager: PersistenceManager = BasePersistenceManager(this)
//Game State
private[control] var state: GameState = GameState.MainMenu
//Variables
private[control] var cardManager: Option[CardManager] = Some(config.cardManager)
private[control] var currentMatch: Option[Match] = None
private[control] var currentRound: Option[Round] = None
private[control] var currentTrick: Option[Trick] = None
private[control] var currentPlayer: Option[AbstractPlayer] = None
private[control] var playerQueue: Option[CustomPlayerQueue[AbstractPlayer]] = None
override def createSession(): Unit = {
cardManager = Some(config.cardManager)
currentMatch = None
currentRound = None
currentTrick = None
currentPlayer = None
playerQueue = None
changeState(Lobby)
}
override def createMatch(players: List[AbstractPlayer]): Match = {
val matchImpl = Match(totalplayers = players, playersIn = players)
currentMatch = Some(matchImpl)
matchImpl
}
override def controlMatch(): Unit = {
if (currentMatch.isEmpty) throw new IllegalStateException("No current match set")
val matchImpl = currentMatch.get
persistenceManager.update(ControlMatch)
if (matchImpl.isOver) {
//Winner is the last person in the playersIn list
val winner = matchImpl.playersIn.head
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 = Random.nextInt(matchImpl.playersIn.size)
playerQueue = Some(config.createRightQueue(matchImpl.playersIn.toArray, randomPlayer))
matchImpl.playersIn.foreach(player => {invoke(ReceivedHandEvent(player))})
currentRound = Some(newRound)
invoke(NewRoundEvent())
controlRound()
return
}
currentMatch = Some(matchImpl.setNumberOfCards(matchImpl.numberofcards - 1))
providePlayersWithCards()
matchImpl.playersIn.foreach(player => {invoke(ReceivedHandEvent(player))})
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 = {
if (currentMatch.isEmpty) throw new IllegalStateException("No current match set")
val matchImpl = currentMatch.get
//Check if the last round had a winner
val lastWinner = getTrumpPlayer
if (lastWinner.isEmpty) throw new IllegalStateException("No last round winner found")
//Create new player queue starting with last round winner
playerQueue = Some(config.createRightQueue(
matchImpl.playersIn.toArray,
matchImpl.playersIn.indexOf(lastWinner.get)
))
changeState(SelectTrump)
invoke(TrumpSelectEvent(lastWinner.get))
playerInputLogic.requestTrumpSuit(lastWinner.get)
}
override def controlRound(): Unit = {
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")
val roundImpl = currentRound.get
persistenceManager.update(ControlRound)
if (MatchUtil.isRoundOver(matchImpl, roundImpl)) {
val roundResult: RoundResult = RoundUtil.finishRound(roundImpl, matchImpl)
if (roundResult.isTie) {
changeState(TieBreak)
invoke(TieEvent(roundResult.winners))
playerTieLogic.handleTie(roundResult)
return
}
val newMatch = endRound(roundResult.winners.head, roundResult)
currentMatch = Some(newMatch)
controlMatch()
} else {
invoke(NewTrickEvent())
val trick = Trick()
currentTrick = Some(trick)
controlTrick()
}
}
override def endRound(winner: AbstractPlayer, roundResult: RoundResult): Match = {
if (currentMatch.isEmpty) throw new IllegalStateException("No current match set")
var matchImpl = currentMatch.get
if (currentRound.isEmpty) throw new IllegalStateException("No current round set")
val roundImpl = currentRound.get
//Create final round snapshot
val resultingRound = Round(
trumpSuit = roundImpl.trumpSuit,
firstRound = roundImpl.firstRound,
tricklist = roundImpl.tricklist,
winner = Some(winner)
)
invoke(RoundEndEvent(winner, roundResult.tricked.filter(
rp => rp.player == winner
).map(rp => rp.amountOfTricks).sum))
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))
matchImpl = matchImpl.updatePlayersIn(matchImpl.playersIn.filterNot(roundResult.notTricked.contains(_)))
} else {
invoke(ShowDogsEvent(roundResult.notTricked))
matchImpl = matchImpl.setDogLife()
// Make players dogs
roundResult.notTricked.foreach(player => {
player.setDogLife()
})
}
}
roundResult.tricked.foreach(player => {
player.player.resetDogLife()
})
matchImpl.addRound(resultingRound)
}
override def controlTrick(): Unit = {
if (currentMatch.isEmpty) throw new IllegalStateException("No current match set")
val matchImpl = currentMatch.get
if (playerQueue.isEmpty) throw new IllegalStateException("No player queue set")
val queueImpl = playerQueue.get
if (currentTrick.isEmpty) throw new IllegalStateException("No current trick set")
val trickImpl = currentTrick.get
persistenceManager.update(ControlRound)
if (TrickUtil.isOver(matchImpl, queueImpl)) {
val newRound = endTrick()
if (newRound.tricklist.isEmpty || newRound.tricklist.last.winner.isEmpty) throw new IllegalStateException("Trick has no winner after ending trick")
val winner = newRound.tricklist.last.winner.get
currentRound = Some(newRound)
invoke(TrickEndEvent(winner))
invoke(DelayEvent(2000))
queueImpl.resetAndSetStart(winner)
controlRound()
} else {
val playerImpl = queueImpl.nextPlayer()
currentPlayer = Some(playerImpl)
controlPlayerPlay()
}
}
override def endTrick(): Round = {
if (currentTrick.isEmpty) throw new IllegalStateException("No current trick set")
val trickImpl = currentTrick.get
if (currentRound.isEmpty) throw new IllegalStateException("No current round set")
val roundImpl = currentRound.get
val resultTrick = TrickUtil.finishTrick(trickImpl, roundImpl)
val resultingTrick = Trick(
cards = trickImpl.cards,
winner = Some(resultTrick.winner),
firstCard = trickImpl.firstCard
)
roundImpl.addTrick(resultingTrick)
}
override def controlPlayerPlay(): Unit = {
if (currentPlayer.isEmpty) throw new IllegalStateException("No current player set")
val playerImpl = currentPlayer.get
if (playerImpl.currentHand().isEmpty) {
controlTrick()
return
}
val handImpl = playerImpl.currentHand().get
if (handImpl.cards.isEmpty) {
controlTrick()
return
}
invoke(TurnEvent(playerImpl))
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 = {
if (currentMatch.isEmpty) throw new IllegalStateException("No current match set")
val matchImpl = currentMatch.get
if (cardManager.isEmpty) throw new IllegalStateException("No card manager set")
val cardManagerImpl = cardManager.get
cardManagerImpl.shuffleAndReset()
val handSize = matchImpl.numberofcards
matchImpl.playersIn.foreach(player => {
val hand = if (!player.isInDogLife) {
cardManagerImpl.createHand(handSize)
} else {
cardManagerImpl.createHand(1)
}
player.provideHand(hand)
})
}
override def changeState(gameState: GameState): Unit = {
if(state == gameState) return
invoke(GameStateChangeEvent(state, gameState))
state = gameState
}
// Getters
override def getCurrentState: GameState = state
override def getCurrentMatch: Option[Match] = currentMatch
override def getCurrentRound: Option[Round] = currentRound
override def getCurrentTrick: Option[Trick] = currentTrick
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
if (matchImpl.roundlist.isEmpty) return None
val roundImpl = matchImpl.roundlist.last
if (roundImpl.winner.isEmpty) return None
Some(roundImpl.winner.get)
}
//Snapshotting
override def createSnapshot(): LogicSnapshot[BaseGameLogic.this.type] = {
BaseGameLogicSnapshot(this).asInstanceOf[LogicSnapshot[BaseGameLogic.this.type]]
}
override def endSession(): Unit = {
cardManager = None
currentMatch = None
currentRound = None
currentTrick = None
currentPlayer = None
playerQueue = None
invoke(SessionClosed())
changeState(MainMenu)
}
}
class BaseGameLogicSnapshot(
val savedState: GameState,
//Card Manager
val cardContainer: Option[List[Card]],
val cardIndex: Option[Int],
val currentMatch: Option[Match],
val currentRound: Option[Round],
val currentTrick: Option[Trick],
val currentPlayer: Option[AbstractPlayer],
//Custom Player Queue
val playerIndex: Option[Int],
val players: Option[List[AbstractPlayer]],
val playerStates: Map[UUID, PlayerData]
) extends LogicSnapshot[BaseGameLogic] {
def this(gameLogic: BaseGameLogic) = {
this(
gameLogic.state,
gameLogic.cardManager.map(cm => cm.cardContainer),
gameLogic.cardManager.map(cm => cm.currentIndx),
gameLogic.currentMatch,
gameLogic.currentRound,
gameLogic.currentTrick,
gameLogic.currentPlayer,
gameLogic.playerQueue.map(pq => pq.currentIndex),
gameLogic.playerQueue.map(pq => pq.duplicate().toList),
gameLogic.currentMatch match {
case Some(m) => m.totalplayers.map(p => (p.id, p.generatePlayerData())).toMap
case None => Map.empty[UUID, PlayerData]
}
)
}
override def restore(logic: BaseGameLogic): Unit = {
logic.state = savedState
//Card Manager
if (logic.cardManager.isDefined) {
val cardManagerImpl = logic.cardManager.get
if (cardContainer.isDefined && cardIndex.isDefined)
cardManagerImpl.setState(cardContainer.get, cardIndex.get)
} else {
if (cardContainer.isDefined && cardIndex.isDefined) {
val newCardManager = logic.config.cardManager
newCardManager.setState(cardContainer.get, cardIndex.get)
logic.cardManager = Some(newCardManager)
}
}
logic.currentMatch = currentMatch
logic.currentRound = currentRound
logic.currentTrick = currentTrick
logic.currentPlayer = currentPlayer
//Custom Player Queue
if (logic.playerQueue.isDefined) {
if (players.isDefined && playerIndex.isDefined)
logic.playerQueue = Some(logic.config.createRightQueue(
players.get.toArray,
playerIndex.get
))
} else {
if (players.isDefined && playerIndex.isDefined)
logic.playerQueue = Some(logic.config.createRightQueue(
players.get.toArray,
playerIndex.get
))
}
//Player States
logic.currentMatch match {
case Some(m) =>
m.totalplayers.foreach(player => {
val dataOpt = playerStates.get(player.id)
if (dataOpt.isDefined)
player.receivePlayerData(dataOpt.get)
})
case None => //Do nothing
}
}
}

View File

@@ -1,100 +0,0 @@
package de.knockoutwhist.control.controllerBaseImpl
import de.knockoutwhist.cards.Card
import de.knockoutwhist.control.*
import de.knockoutwhist.events.GLOBAL_STATUS.SHOW_FINISHED_MATCH
import de.knockoutwhist.events.PLAYER_STATUS.SHOW_WON_PLAYER_TRICK
import de.knockoutwhist.events.ROUND_STATUS.{PLAYERS_OUT, WON_ROUND}
import de.knockoutwhist.events.directional.RequestPlayersEvent
import de.knockoutwhist.events.round.ShowCurrentTrickEvent
import de.knockoutwhist.events.ui.GameState.MAIN_MENU
import de.knockoutwhist.events.ui.GameStateUpdateEvent
import de.knockoutwhist.events.util.DelayEvent
import de.knockoutwhist.events.{ShowGlobalStatus, ShowPlayerStatus, ShowRoundStatus}
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round, Trick}
import de.knockoutwhist.undo.UndoManager
import de.knockoutwhist.undo.commands.EnterPlayersCommand
import de.knockoutwhist.utils.Implicits.*
object MainLogic extends Maincomponent {
def startMatch(): Unit = {
ControlHandler.invoke(RequestPlayersEvent())
}
def enteredPlayers(players: List[AbstractPlayer]): Unit = {
UndoManager.doStep(EnterPlayersCommand(players))
}
def controlMatch(matchImpl: Match): Unit = {
if(ControlHandler.matchcomponent.isOver(matchImpl)) {
ControlHandler.invoke(ShowGlobalStatus(SHOW_FINISHED_MATCH, ControlHandler.roundlogcomponent.remainingPlayers(matchImpl.roundlist.last).head))
ControlHandler.invoke(GameStateUpdateEvent(MAIN_MENU))
} else {
val remainingPlayer = matchImpl.roundlist.isEmpty ? matchImpl.totalplayers |: ControlHandler.roundlogcomponent.remainingPlayers(matchImpl.roundlist.last)
val newMatch = ControlHandler.roundlogcomponent.provideCards(matchImpl, remainingPlayer)
ControlHandler.playerlogcomponent.trumpsuitStep(newMatch._1, newMatch._2)
}
}
def controlRound(matchImpl: Match, round: Round): Unit = {
if(!ControlHandler.roundlogcomponent.isOver(round)) {
val trick = Trick()
controlTrick(matchImpl, round, trick)
return
}
val result = ControlHandler.roundlogcomponent.finalizeRound(ControlHandler.roundlogcomponent.smashResults(round), matchImpl)
if(result._3.size == 1) {
endRound(result._1, result._2, result._3.head, result._4)
} else {
ControlHandler.playerlogcomponent.preSelect(result._3, result._1, result._2, result._4)
}
}
def endRound(matchImpl: Match, round: Round, winner: AbstractPlayer, playersOut: List[AbstractPlayer]): Unit = {
val finalRound = Round(round.trumpSuit, round.tricklist, round.playersin, playersOut, round.startingPlayer, winner, firstRound = round.firstRound)
val newMatch = matchImpl.addRound(finalRound)
ControlHandler.invoke(ShowRoundStatus(WON_ROUND, finalRound, winner))
ControlHandler.invoke(DelayEvent(2000L))
if (finalRound.playersout.nonEmpty) {
ControlHandler.invoke(ShowRoundStatus(PLAYERS_OUT, finalRound))
}
controlMatch(newMatch)
}
def controlTrick(matchImpl: Match, round: Round, trick: Trick, currentIndex: Int = 0): Unit = {
if(currentIndex < round.playersin.size) {
val player = round.playerQueue.nextPlayer()
//ControlHandler.invoke(ShowCurrentTrickEvent(round, trick))
controlPlayer(matchImpl, round, trick, player, currentIndex)
}else {
val result = ControlHandler.trickcomponent.wonTrick(trick, round)
val newRound = round.addTrick(result._2)
ControlHandler.invoke(ShowPlayerStatus(SHOW_WON_PLAYER_TRICK, result._1, result._2))
newRound.playerQueue.resetAndSetStart(result._1)
ControlHandler.invoke(DelayEvent(1000L))
controlRound(matchImpl, newRound)
}
}
def controlPlayer(matchImpl: Match, round: Round, trick: Trick, player: AbstractPlayer, currentIndex: Int): Unit = {
ControlHandler.invoke(ShowCurrentTrickEvent(round, trick))
if (!player.doglife) {
ControlHandler.playeractrcomponent.playCard(matchImpl, player, round, trick, currentIndex)
} else if (player.currentHand().exists(_.cards.nonEmpty)) {
ControlHandler.playeractrcomponent.dogplayCard(matchImpl, player, round, trick, currentIndex)
}else {
controlTrick(matchImpl, round, trick, currentIndex+1)
}
}
def playCard(trick: Trick, card: Card, player: AbstractPlayer): Trick = {
if (trick.firstCard.isEmpty) {
trick.setfirstcard(card).addCard(card, player)
} else {
trick.addCard(card, player)
}
}
}

View File

@@ -1,14 +0,0 @@
package de.knockoutwhist.control.controllerBaseImpl
import de.knockoutwhist.control.{ControlHandler, Matchcomponent}
import de.knockoutwhist.rounds.Match
object MatchLogic extends Matchcomponent {
def isOver(matchImpl: Match): Boolean = {
if (matchImpl.roundlist.isEmpty) {
false
} else {
ControlHandler.roundlogcomponent.remainingPlayers(matchImpl.roundlist.last).size == 1
}
}
}

View File

@@ -1,35 +0,0 @@
package de.knockoutwhist.control.controllerBaseImpl
import de.knockoutwhist.control.{ControlHandler, Playeractrcomponent}
import de.knockoutwhist.events.PLAYER_STATUS.*
import de.knockoutwhist.events.ShowPlayerStatus
import de.knockoutwhist.events.cards.RenderHandEvent
import de.knockoutwhist.events.util.DelayEvent
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round, Trick}
object PlayerControl extends Playeractrcomponent {
def playCard(matchImpl: Match, player: AbstractPlayer, round: Round, trick: Trick, currentIndex: Int): Unit = {
ControlHandler.invoke(ShowPlayerStatus(SHOW_TURN, player))
ControlHandler.invoke(DelayEvent(500))
ControlHandler.invoke(ShowPlayerStatus(SHOW_PLAY_CARD, player))
ControlHandler.invoke(RenderHandEvent(player.currentHand().get, true))
player.handlePlayCard(player.currentHand().get, matchImpl, round, trick, currentIndex)
}
def dogplayCard(matchImpl: Match, player: AbstractPlayer, round: Round, trick: Trick, currentIndex: Int): Unit = {
ControlHandler.invoke(ShowPlayerStatus(SHOW_TURN, player))
ControlHandler.invoke(DelayEvent(2000))
ControlHandler.invoke(ShowPlayerStatus(SHOW_DOG_PLAY_CARD, player, ControlHandler.roundlogcomponent.dogNeedsToPlay(round)))
ControlHandler.invoke(RenderHandEvent(player.currentHand().get, false))
player.handleDogPlayCard(player.currentHand().get, matchImpl, round, trick, currentIndex, ControlHandler.roundlogcomponent.dogNeedsToPlay(round))
}
def pickNextTrumpsuit(matchImpl: Match, remaining_players: List[AbstractPlayer], firstRound: Boolean, player: AbstractPlayer): Unit = {
ControlHandler.invoke(ShowPlayerStatus(SHOW_TRUMPSUIT_OPTIONS, player))
ControlHandler.invoke(RenderHandEvent(player.currentHand().get, false))
player.handlePickTrumpsuit(matchImpl, remaining_players, firstRound)
}
}

View File

@@ -1,102 +0,0 @@
package de.knockoutwhist.control.controllerBaseImpl
import de.knockoutwhist.KnockOutWhist
import de.knockoutwhist.cards.base.CardBaseManager
import de.knockoutwhist.cards.{Card, Suit}
import de.knockoutwhist.control.{ControlHandler, Playerlogcomponent}
import de.knockoutwhist.events.ERROR_STATUS.{INVALID_NUMBER, NOT_A_NUMBER}
import de.knockoutwhist.events.GLOBAL_STATUS.{SHOW_TIE, SHOW_TIE_TIE, SHOW_TIE_WINNER}
import de.knockoutwhist.events.PLAYER_STATUS.SHOW_TIE_NUMBERS
import de.knockoutwhist.events.cards.ShowTieCardsEvent
import de.knockoutwhist.events.ui.GameState.INGAME
import de.knockoutwhist.events.ui.GameStateUpdateEvent
import de.knockoutwhist.events.{ShowErrorStatus, ShowGlobalStatus, ShowPlayerStatus}
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round}
import de.knockoutwhist.undo.UndoManager
import de.knockoutwhist.undo.commands.{SelectTieCommand, TrumpSuitSelectedCommand}
import scala.collection.immutable
import scala.collection.mutable.ListBuffer
import scala.util.Try
object PlayerLogic extends Playerlogcomponent {
def trumpsuitStep(matchImpl: Match, remaining_players: List[AbstractPlayer]): Unit = {
if (matchImpl.roundlist.isEmpty) {
val randomTrumpsuit = ControlHandler.cardManager.nextCard().suit
val newMatchImpl = matchImpl.setNumberOfCards(matchImpl.numberofcards - 1)
val round = new Round(randomTrumpsuit, remaining_players, true)
ControlHandler.maincomponent.controlRound(newMatchImpl, round)
} else {
val winner = matchImpl.totalplayers.filter(matchImpl.roundlist.last.winner.name == _.name).head
ControlHandler.playeractrcomponent.pickNextTrumpsuit(matchImpl, remaining_players, false, winner)
}
}
def trumpSuitSelected(matchImpl: Match, suit: Try[Suit], remaining_players: List[AbstractPlayer], firstRound: Boolean, decided: AbstractPlayer): Unit = {
if (suit.isFailure) {
ControlHandler.invoke(ShowErrorStatus(INVALID_NUMBER))
ControlHandler.playeractrcomponent.pickNextTrumpsuit(matchImpl, remaining_players, firstRound, decided)
return
}
ControlHandler.invoke(GameStateUpdateEvent(INGAME))
UndoManager.doStep(TrumpSuitSelectedCommand(matchImpl, suit.get, remaining_players, false, decided))
}
def preSelect(winners: List[AbstractPlayer], matchImpl: Match, round: Round, playersout: List[AbstractPlayer]): Unit = {
if (!KnockOutWhist.debugmode) ControlHandler.cardManager.shuffleAndReset()
ControlHandler.invoke(ShowGlobalStatus(SHOW_TIE))
selectTie(winners, matchImpl, round, playersout, immutable.HashMap(), 0, ControlHandler.cardManager.cardContainer.size - (winners.length - 1))
}
def selectTie(winners: List[AbstractPlayer], matchImpl: Match, round: Round, playersout: List[AbstractPlayer], cut: immutable.HashMap[AbstractPlayer, Card], currentStep: Int, remaining: Int, currentIndex: Int = 0): Unit = {
if(currentIndex == winners.size) {
evaluateTieWinner(matchImpl, round, playersout, cut)
} else {
val player = winners(currentIndex)
ControlHandler.invoke(ShowPlayerStatus(SHOW_TIE_NUMBERS, player, remaining))
player.handlePickTieCard(winners, matchImpl, round, playersout, cut, currentStep, remaining, currentIndex)
}
}
def selectedTie(winner: List[AbstractPlayer],matchImpl: Match, round: Round, playersout: List[AbstractPlayer], cut: immutable.HashMap[AbstractPlayer, Card], value: Try[Int], currentStep: Int, remaining: Int, currentIndex: Int = 0): Unit = {
if (value.isFailure) {
ControlHandler.invoke(ShowErrorStatus(NOT_A_NUMBER))
selectTie(winner, matchImpl, round, playersout, cut, currentStep, remaining, currentIndex)
return
}
val selCard = ControlHandler.cardManager.cardContainer(currentStep + (value.get - 1))
UndoManager.doStep(SelectTieCommand(winner, matchImpl, round, playersout, cut, value.get, selCard, currentStep, remaining, currentIndex))
}
def evaluateTieWinner(matchImpl: Match, round: Round, playersout: List[AbstractPlayer], cut: immutable.HashMap[AbstractPlayer, Card]): Unit = {
ControlHandler.invoke(ShowTieCardsEvent(cut.toList))
val winner: ListBuffer[AbstractPlayer] = ListBuffer()
var currentHighest: Card = null
for ((player, card) <- cut) {
if (currentHighest == null) {
currentHighest = card
winner += player
} else {
val compared = card.cardValue.ordinal.compareTo(currentHighest.cardValue.ordinal)
if (compared > 0) {
currentHighest = card
winner.clear()
winner += player
} else if (compared == 0) {
winner += player
}
}
}
if (winner.size == 1) {
ControlHandler.invoke(ShowGlobalStatus(SHOW_TIE_WINNER, winner.head))
ControlHandler.maincomponent.endRound(matchImpl, round, winner.head, playersout)
return
}
ControlHandler.invoke(ShowGlobalStatus(SHOW_TIE_TIE))
preSelect(winner.toList, matchImpl, round, playersout)
}
}

View File

@@ -1,82 +0,0 @@
package de.knockoutwhist.control.controllerBaseImpl
import de.knockoutwhist.KnockOutWhist
import de.knockoutwhist.cards.base.CardBaseManager
import de.knockoutwhist.control.{ControlHandler, Roundlogcomponent}
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round, Trick}
import de.knockoutwhist.utils.Implicits.*
import scala.collection.mutable.ListBuffer
object RoundLogic extends Roundlogcomponent{
def isOver(round: Round): Boolean = {
round.playersin.map(_.currentHand()).count(_.get.cards.isEmpty) == round.playersin.size
}
def dogNeedsToPlay(round: Round): Boolean = {
round.playersin.filter(!_.doglife).map(_.currentHand()).exists(_.get.cards.isEmpty)
}
def finalizeRound(round: Round, matchImpl: Match, force: Boolean = false): (Match, Round, List[AbstractPlayer], List[AbstractPlayer]) = {
if (!force && round.tricklist.isEmpty)
throw new IllegalStateException("No tricks played in this round")
if (!force && !isOver(round))
throw new IllegalStateException("Not all tricks were played in this round")
val tricksMapped = round.tricklist
.map(t => t.winner)
.groupBy(identity).map((p, l) => (p, l.size)) //l.size = Anzahl gewonnener Tricks
val winners = tricksMapped
.filter((p, i) => i == tricksMapped.values.max)
.keys
var playersOut = round.firstRound
? List()
|: round.playersin.filter(!tricksMapped.contains(_))
var newMatch = matchImpl
var newRound = round
if (playersOut.nonEmpty && !matchImpl.dogLife) {
newMatch = matchImpl.setDogLife()
val playersUpdated = ListBuffer[AbstractPlayer]()
playersUpdated ++= tricksMapped.keys
playersOut.foreach(p => {
playersUpdated += p.setDogLife()
})
newMatch = newMatch.updatePlayers(playersUpdated.toList)
newRound = newRound.updatePlayersIn(playersUpdated.toList)
playersOut = List()
}
(newMatch, newRound, winners.toList, playersOut)
}
def remainingPlayers(round: Round): List[AbstractPlayer] = {
if (round.playersout == null) {
return round.playersin
}
round.playersin.filter(!round.playersout.contains(_))
}
def provideCards(matchImpl: Match, players: List[AbstractPlayer]): (Match,List[AbstractPlayer]) = {
if (!KnockOutWhist.debugmode) ControlHandler.cardManager.shuffleAndReset()
val listbuff = new ListBuffer[AbstractPlayer]()
for (player <- players) {
if (!player.doglife) {
val newPlayer = player.provideHand(ControlHandler.cardManager.createHand(matchImpl.numberofcards))
listbuff.addOne(newPlayer)
} else {
val newPlayer = player.provideHand(ControlHandler.cardManager.createHand(1))
listbuff.addOne(newPlayer)
}
}
val matchResult = matchImpl.totalplayers.appendedAll(listbuff.toList).filter(!players.contains(_))
(matchImpl.updatePlayers(matchResult), listbuff.toList)
}
def smashResults(round: Round): Round = {
val correctPlayers = round.playersin.groupMapReduce(_.name)(identity)((a, b) => a)
val newTricks = round.tricklist.map(t => Trick(t.cards, correctPlayers.getOrElse(t.winner.name, t.winner), t.finished, t.firstCard))
Round(round.trumpSuit, newTricks, round.playersin, round.playersout, round.startingPlayer, round.winner, round.firstRound)
}
}

View File

@@ -1,83 +0,0 @@
package de.knockoutwhist.control.controllerBaseImpl
import de.knockoutwhist.cards.{Card, Hand}
import de.knockoutwhist.control.{ControlHandler, Tricklogcomponent}
import de.knockoutwhist.control.controllerBaseImpl.PlayerControl
import de.knockoutwhist.events.ERROR_STATUS.{INVALID_INPUT, INVALID_NUMBER, WRONG_CARD}
import de.knockoutwhist.events.ShowErrorStatus
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round, Trick}
import de.knockoutwhist.undo.UndoManager
import de.knockoutwhist.undo.commands.{PlayerPlayCommand, PlayerPlayDogCommand}
import scala.util.Try
object TrickLogic extends Tricklogcomponent {
def controlSuitplayed(card: Try[Card], matchImpl: Match, round: Round, trick: Trick, currentIndex: Int, player: AbstractPlayer): Unit = {
if (card.isFailure) {
ControlHandler.invoke(ShowErrorStatus(INVALID_NUMBER))
ControlHandler.playeractrcomponent.playCard(matchImpl, player, round, trick, currentIndex)
return
}
val realCard = card.get
if (trick.firstCard.isDefined) {
val firstCard = trick.firstCard.get
if (firstCard.suit != realCard.suit) {
var hasSuit = false
for (cardInHand <- player.currentHand().get.cards) {
if (cardInHand.suit == firstCard.suit) {
hasSuit = true
}
}
if (hasSuit) {
ControlHandler.invoke(ShowErrorStatus(WRONG_CARD, firstCard))
ControlHandler.playeractrcomponent.playCard(matchImpl, player, round, trick, currentIndex)
return
}
}
}
UndoManager.doStep(PlayerPlayCommand(matchImpl, round, trick, player, realCard, currentIndex))
}
def controlDogPlayed(card: Try[Option[Card]], matchImpl: Match, round: Round, trick: Trick, currentIndex: Int, player: AbstractPlayer): Unit = {
if (card.isFailure) {
ControlHandler.invoke(ShowErrorStatus(INVALID_INPUT))
ControlHandler.playeractrcomponent.dogplayCard(matchImpl, player, round, trick, currentIndex)
return
}
UndoManager.doStep(PlayerPlayDogCommand(matchImpl, round, trick, player, card.get, currentIndex))
}
def alternativeCards(card: Card, round: Round, trick: Trick, player: AbstractPlayer): List[Card] = {
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 Nil
}
if (alternatives.nonEmpty) {
return alternatives
}
}
}
Nil
}
def wonTrick(trick: Trick, round: Round): (AbstractPlayer, Trick) = {
val winningCard = {
if (trick.cards.keys.exists(_.suit == round.trumpSuit)) {
trick.cards.keys.filter(_.suit == round.trumpSuit).maxBy(_.cardValue.ordinal) //stream
} else {
trick.cards.keys.filter(_.suit == trick.firstCard.get.suit).maxBy(_.cardValue.ordinal) //stream
}
}
val winningPlayer = trick.cards(winningCard)
val finalTrick = Trick(trick.cards, winningPlayer, true)
(winningPlayer, finalTrick)
}
}

View File

@@ -0,0 +1,41 @@
package de.knockoutwhist.control.controllerBaseImpl.sublogic
import de.knockoutwhist.KnockOutWhist
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
import de.knockoutwhist.control.sublogic.PersistenceManager
import de.knockoutwhist.control.{GameLogic, LogicSnapshot}
import de.knockoutwhist.events.util.ReloadAllEvent
import de.knockoutwhist.persistence.{MatchSnapshot, MethodEntryPoint}
class BasePersistenceManager(val gameLogic: BaseGameLogic) extends PersistenceManager {
private var currentSnapshot: MatchSnapshot = MatchSnapshot()
override def update(methodEntryPoint: MethodEntryPoint): MatchSnapshot = {
currentSnapshot = currentSnapshot
.withMethodEntryPoint(methodEntryPoint)
.withGameLogicSnapShot(gameLogic.createSnapshot().asInstanceOf[LogicSnapshot[GameLogic]])
currentSnapshot
}
override def saveFile(path: String): Unit = {
KnockOutWhist.config.fileFormatter.writeToFile(currentSnapshot, path + "." + KnockOutWhist.config.fileFormatter.fileEnding)
}
override def loadFile(path: String): Unit = {
currentSnapshot = KnockOutWhist.config.fileFormatter.readFromFile(path + "." + KnockOutWhist.config.fileFormatter.fileEnding)
if currentSnapshot.entryPoint.isEmpty then
throw new IllegalStateException("Loaded snapshot does not contain an entry point!")
currentSnapshot.gameLogicSnapShot.foreach(_.restore(gameLogic))
gameLogic.invoke(ReloadAllEvent())
currentSnapshot.entryPoint.get match {
case MethodEntryPoint.ControlMatch =>
gameLogic.controlMatch()
case MethodEntryPoint.ControlRound =>
gameLogic.controlRound()
case MethodEntryPoint.ControlTrick =>
gameLogic.controlTrick()
}
}
}

View File

@@ -0,0 +1,89 @@
package de.knockoutwhist.control.controllerBaseImpl.sublogic
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.global.{CardPlayedEvent, TrumpSelectedEvent}
import de.knockoutwhist.events.player.{RequestCardEvent, RequestTrumpSuitEvent}
import de.knockoutwhist.player.AbstractPlayer
final class BasePlayerInputLogic(gameLogic: BaseGameLogic) extends PlayerInputLogic {
val TIME_TO_RESPOND: Int = 30 // seconds
private var _waitingForInput: Boolean = false
override def requestTrumpSuit(player: AbstractPlayer): Unit = {
_waitingForInput = true
gameLogic.invoke(RequestTrumpSuitEvent(player))
}
override def receivedTrumpSuit(suit: Suit): Unit = {
if (!_waitingForInput) throw new IllegalStateException("Not waiting for input")
_waitingForInput = false
val newRound = RoundUtil.createRound(suit)
gameLogic.currentRound = Some(newRound)
gameLogic.invoke(TrumpSelectedEvent(suit))
gameLogic.controlRound()
}
override def requestCard(player: AbstractPlayer): Unit = {
_waitingForInput = true
gameLogic.invoke(RequestCardEvent(player))
}
override def receivedCard(card: Card): Unit = {
if (!_waitingForInput) throw new IllegalStateException("Not waiting for input")
_waitingForInput = false
if (gameLogic.currentTrick.isEmpty) throw new IllegalStateException("No current trick set")
val trickImpl = gameLogic.currentTrick.get
if (gameLogic.currentPlayer.isEmpty) throw new IllegalStateException("No current player set")
val player = gameLogic.currentPlayer.get
val newTrick = if (trickImpl.firstCard.isEmpty) {
trickImpl
.setfirstcard(card)
.addCard(card, player)
} else {
trickImpl
.addCard(card, player)
}
player.removeCard(card)
gameLogic.currentTrick = Some(newTrick)
gameLogic.invoke(CardPlayedEvent(player, newTrick))
gameLogic.controlTrick()
}
override def receivedDog(dog: Option[Card]): Unit = {
if (!_waitingForInput) throw new IllegalStateException("Not waiting for input")
_waitingForInput = false
if (gameLogic.currentTrick.isEmpty) throw new IllegalStateException("No current trick set")
val trickImpl = gameLogic.currentTrick.get
if (gameLogic.currentPlayer.isEmpty) throw new IllegalStateException("No current player set")
val player = gameLogic.currentPlayer.get
if (dog.isDefined) {
val newTrick = if (trickImpl.firstCard.isEmpty) {
trickImpl
.setfirstcard(dog.get)
.addCard(dog.get, player)
} else {
trickImpl
.addCard(dog.get, player)
}
player.removeCard(dog.get)
gameLogic.currentTrick = Some(newTrick)
}
gameLogic.controlTrick()
}
override def isWaitingForInput: Boolean = _waitingForInput
}

View File

@@ -0,0 +1,168 @@
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.{ResultPlayer, RoundResult}
import de.knockoutwhist.control.sublogic.PlayerTieLogic
import de.knockoutwhist.events.global.tie.*
import de.knockoutwhist.events.player.RequestTieChoiceEvent
import de.knockoutwhist.events.util.DelayEvent
import de.knockoutwhist.player.AbstractPlayer
final class BasePlayerTieLogic(gameLogic: BaseGameLogic) extends PlayerTieLogic {
private[control] var roundResult: Option[RoundResult] = None
private[control] var tiedPlayers: List[AbstractPlayer] = Nil
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)
tiedPlayers = roundResult.winners
tieBreakerIndex = -1
lastNumber = 0
selectedCard = Map.empty
if (gameLogic.cardManager.isEmpty) throw new IllegalStateException("No card manager set")
gameLogic.cardManager.get.shuffleAndReset()
handleNextTieBreakerPlayer()
}
override def handleNextTieBreakerPlayer(): Unit = {
tieBreakerIndex += 1
if(tieBreakerIndex >= 0 && tieBreakerIndex < tiedPlayers.size) {
requestTieChoice(currentTiePlayer().get)
} else {
gameLogic.invoke(TieAllPlayersSelectedEvent())
gameLogic.invoke(DelayEvent(1500))
gameLogic.invoke(TieShowPlayerCardsEvent())
gameLogic.invoke(DelayEvent(5000))
val winningEntry = selectedCard.values.maxBy(_.cardValue.ordinal)
val winners = selectedCard.filter((_, card) => card == winningEntry).keySet.toList
gameLogic.invoke(TieWinningPlayersEvent(winners))
gameLogic.invoke(DelayEvent(2000))
if (winners.size > 1) {
gameLogic.invoke(TieTieEvent(winners))
// Still a tie, handle again
tiedPlayers = winners
tieBreakerIndex = -1
lastNumber = 0
selectedCard = Map.empty
gameLogic.cardManager.get.shuffleAndReset()
handleNextTieBreakerPlayer()
return
}
// Tie-breaker resolved
roundResult = None
tiedPlayers = Nil
lastNumber = -1
tieBreakerIndex = -1
selectedCard = Map.empty
val winner = winners.head
// Inform game logic about the winner
gameLogic.returnFromTie(winner)
}
}
override def currentTiePlayer(): Option[AbstractPlayer] = {
if (tieBreakerIndex < 0 || tieBreakerIndex >= tiedPlayers.size)
return None
Some(tiedPlayers(tieBreakerIndex))
}
override def requestTieChoice(player: AbstractPlayer): Unit = {
_waitingForInput = true
gameLogic.invoke(TieTurnEvent(player))
gameLogic.invoke(RequestTieChoiceEvent(player, highestAllowedNumber()))
}
/**
* Called when a player has selected a tie-breaker card
* @param number the index of the selected card
*/
override def receivedTieBreakerCard(number: Int): Unit = {
if (!_waitingForInput) throw new IllegalStateException("Not waiting for input")
_waitingForInput = false
val player = tiedPlayers(tieBreakerIndex)
val highestNumber = highestAllowedNumber()
if (number < 0 || number > 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")
val cardManager = gameLogic.cardManager.get
val card = cardManager.removeCards(number).last
selectedCard += (player -> card)
handleNextTieBreakerPlayer()
}
override def highestAllowedNumber(): Int = {
if (gameLogic.cardManager.isEmpty) throw new IllegalStateException("No card manager set")
val remainingCards = gameLogic.cardManager.get.remainingCards
// 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)) - 1
}
override def isWaitingForInput: Boolean = _waitingForInput
override def createSnapshot(): LogicSnapshot[BasePlayerTieLogic.this.type] = BasePlayerTieLogicSnapshot(this).asInstanceOf[LogicSnapshot[BasePlayerTieLogic.this.type]]
// Getter
override def getRoundResult: Option[RoundResult] = roundResult
override def getTiedPlayers: List[AbstractPlayer] = tiedPlayers
override def getTieBreakerIndex: Int = tieBreakerIndex
override def getLastNumber: Int = lastNumber
override def getSelectedCard: Map[AbstractPlayer, Card] = selectedCard
}
class BasePlayerTieLogicSnapshot(
//Round result
val winners: List[AbstractPlayer],
val tricked: List[ResultPlayer],
val notTricked: List[AbstractPlayer],
val tiedPlayers: List[AbstractPlayer],
val tieBreakerIndex: Int,
val lastNumber: Int,
val selectedCard: Map[AbstractPlayer, Card]
) extends LogicSnapshot[BasePlayerTieLogic] {
def this(logic: BasePlayerTieLogic) = {
this(
logic.roundResult.map(_.winners).getOrElse(Nil),
logic.roundResult.map(_.tricked).getOrElse(Nil),
logic.roundResult.map(_.notTricked).getOrElse(Nil),
logic.tiedPlayers,
logic.tieBreakerIndex,
logic.lastNumber,
logic.selectedCard
)
}
override def restore(logic: BasePlayerTieLogic): Unit = {
if (winners.nonEmpty || tricked.nonEmpty || notTricked.nonEmpty) {
logic.roundResult = Some(RoundResult(winners, tricked, notTricked))
} else {
logic.roundResult = None
}
logic.tiedPlayers = tiedPlayers
logic.tieBreakerIndex = tieBreakerIndex
logic.lastNumber = lastNumber
logic.selectedCard = selectedCard
}
}

View File

@@ -1,19 +1,22 @@
package de.knockoutwhist.undo
package de.knockoutwhist.control.controllerBaseImpl.sublogic
import de.knockoutwhist.control.ControlThread
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
import de.knockoutwhist.control.sublogic.UndoManager
import de.knockoutwhist.undo.{Command, UndoneException}
object UndoManager {
class BaseUndoManager(gameLogic: BaseGameLogic) extends UndoManager {
private var undoStack: List[Command] = Nil
private var redoStack: List[Command] = Nil
def doStep(command: Command): Unit = {
override def doStep(command: Command): Unit = {
redoStack = Nil
undoStack = command :: undoStack
command.doStep()
command.doStep(gameLogic)
}
def undoStep(): Unit = {
override def undoStep(): Unit = {
ControlThread.runLater {
undoStack match {
case Nil => false
@@ -21,7 +24,7 @@ object UndoManager {
undoStack = stack
redoStack = head :: redoStack
try {
head.undoStep()
head.undoStep(gameLogic)
} catch {
case _: UndoneException =>
}
@@ -29,7 +32,7 @@ object UndoManager {
}
}
def redoStep(): Unit = {
override def redoStep(): Unit = {
ControlThread.runLater {
redoStack match {
case Nil => false
@@ -37,7 +40,7 @@ object UndoManager {
redoStack = stack
undoStack = head :: undoStack
try {
head.doStep()
head.doStep(gameLogic)
} catch {
case _: UndoneException =>
}

View File

@@ -0,0 +1,23 @@
package de.knockoutwhist.control.controllerBaseImpl.sublogic.util
import de.knockoutwhist.rounds.{Match, Round}
object MatchUtil {
private def remainingRounds(matchImpl: Match, roundImpl: Round): Int = {
// If no player has any cards left, there are no rounds remaining
if !matchImpl.playersIn
.map(player => player.currentHand()).exists(hand => hand.isDefined && hand.get.cards.nonEmpty) then return 0
// Otherwise, calculate remaining rounds based on number of cards and tricks played
matchImpl.numberofcards - roundImpl.tricklist.size
}
def isRoundOver(matchImpl: Match, roundImpl: Round): Boolean = {
remainingRounds(matchImpl, roundImpl) == 0
}
def dogNeedsToPlay(matchImpl: Match, roundImpl: Round): Boolean = {
remainingRounds(matchImpl, roundImpl) <= 1
}
}

View File

@@ -0,0 +1,52 @@
package de.knockoutwhist.control.controllerBaseImpl.sublogic.util
import de.knockoutwhist.cards.Card
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Round, Trick}
object PlayerUtil {
def canPlayCard(card: Card, round: Round, trick: Trick, player: AbstractPlayer): Boolean = {
if (trick.firstCard.isEmpty)
return true
val alternatives = alternativeCards(card, round, trick, player)
if (alternatives.nonEmpty) {
return false
}
true
}
def alternativeCards(card: Card, round: Round, trick: Trick, player: AbstractPlayer): List[Card] = {
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 Nil
}
if (alternatives.nonEmpty) {
return alternatives
}
}
}
Nil
}
def playableCards(round: Round, trick: Trick, player: AbstractPlayer): List[Card] = {
val handOption = player.currentHand()
if (handOption.isEmpty) {
throw new IllegalStateException("You have no cards!")
}
val hand = handOption.get
if (trick.firstCard.isEmpty) {
return hand.cards
}
val playableCards: List[Card] = for cardInHand <- hand.cards
if canPlayCard(cardInHand, round, trick, player)
yield cardInHand
playableCards
}
}

View File

@@ -0,0 +1,43 @@
package de.knockoutwhist.control.controllerBaseImpl.sublogic.util
import de.knockoutwhist.cards.Suit
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round}
object RoundUtil {
def createRound(trumpSuit: Suit, firstRound: Boolean = false): Round = {
Round(trumpSuit, firstRound)
}
def finishRound(round: Round, matchImpl: Match): RoundResult = {
val tricksMapped = round.tricklist
.map(t => t.winner)
.filter(t => t.isDefined)
.map(t => t.get)
.groupBy(identity).map((p, l) => (p, l.size))
val maxTricks = if (tricksMapped.isEmpty) 0 else tricksMapped.values.max
val winners = tricksMapped
.filter((p, i) => i == maxTricks)
.keys.toList
val trickedPlayers = tricksMapped.map((p, i) => ResultPlayer(p, i)).toList
val notTrickedPlayers = matchImpl.playersIn.filterNot(p => tricksMapped.keySet.contains(p))
RoundResult(winners, trickedPlayers, notTrickedPlayers)
}
def roundEndSnapshot(winner: AbstractPlayer, round: Round): Round = {
Round(
round.trumpSuit,
round.firstRound,
round.tricklist,
Some(winner)
)
}
}
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)

View File

@@ -0,0 +1,13 @@
package de.knockoutwhist.control.controllerBaseImpl.sublogic.util
import de.knockoutwhist.control.{GameLogic, LogicSnapshot}
object SnapshotUtil {
def generateSnapshots(gameLogic: GameLogic): (LogicSnapshot[? <: GameLogic], LogicSnapshot[? <: GameLogic]) = {
val gameLogicSnapshot = gameLogic.createSnapshot()
val playerTieLogicSnapshot = gameLogic.playerTieLogic.createSnapshot()
(gameLogicSnapshot, playerTieLogicSnapshot.asInstanceOf[LogicSnapshot[? <: GameLogic]])
}
}

View File

@@ -0,0 +1,32 @@
package de.knockoutwhist.control.controllerBaseImpl.sublogic.util
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round, Trick}
import de.knockoutwhist.utils.CustomPlayerQueue
object TrickUtil {
def isOver(matchImpl: Match, queue: CustomPlayerQueue[AbstractPlayer]): Boolean = {
queue.playersSinceLastReset() >= matchImpl.playersIn.length
}
private def winningPlayer(trick: Trick, round: Round): AbstractPlayer = {
val winningCard = {
if (trick.cards.keys.exists(_.suit == round.trumpSuit)) {
trick.cards.keys.filter(_.suit == round.trumpSuit).maxBy(_.cardValue.ordinal) //stream
} else {
trick.cards.keys.filter(_.suit == trick.firstCard.get.suit).maxBy(_.cardValue.ordinal) //stream
}
}
val winningPlayer = trick.cards(winningCard)
winningPlayer
}
def finishTrick(trick: Trick, round: Round): TrickResult = {
val winner = winningPlayer(trick, round)
TrickResult(winner)
}
}
case class TrickResult(winner: AbstractPlayer)

View File

@@ -0,0 +1,26 @@
package de.knockoutwhist.control.sublogic
import de.knockoutwhist.KnockOutWhist
import de.knockoutwhist.control.GameLogic
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
import de.knockoutwhist.persistence.{MatchSnapshot, MethodEntryPoint}
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round, Trick}
import de.knockoutwhist.utils.events.{EventListener, SimpleEvent}
import scala.Unit
trait PersistenceManager {
def gameLogic: GameLogic
def update(methodEntryPoint: MethodEntryPoint): MatchSnapshot
def saveFile(path: String): Unit
def loadFile(path: String): Unit
def canLoadfile(path: String): Boolean = {
val file = new java.io.File(path + "." + KnockOutWhist.config.fileFormatter.fileEnding)
file.exists() && file.isFile && file.canRead
}
}

View File

@@ -0,0 +1,17 @@
package de.knockoutwhist.control.sublogic
import de.knockoutwhist.cards.{Card, Suit}
import de.knockoutwhist.player.AbstractPlayer
trait PlayerInputLogic {
def requestTrumpSuit(player: AbstractPlayer): Unit
def receivedTrumpSuit(suit: Suit): Unit
def requestCard(player: AbstractPlayer): Unit
def receivedCard(card: Card): Unit
def receivedDog(dog: Option[Card]): Unit
def isWaitingForInput: Boolean
}

View File

@@ -0,0 +1,25 @@
package de.knockoutwhist.control.sublogic
import de.knockoutwhist.cards.Card
import de.knockoutwhist.control.SnapshottingGameLogic
import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.RoundResult
import de.knockoutwhist.player.AbstractPlayer
trait PlayerTieLogic extends SnapshottingGameLogic {
def handleTie(roundResult: RoundResult): Unit
def handleNextTieBreakerPlayer(): Unit
def currentTiePlayer(): Option[AbstractPlayer]
def requestTieChoice(player: AbstractPlayer): Unit
def receivedTieBreakerCard(number: Int): Unit
def highestAllowedNumber(): Int
def isWaitingForInput: Boolean
def getRoundResult: Option[RoundResult]
def getTiedPlayers: List[AbstractPlayer]
def getTieBreakerIndex: Int
def getLastNumber: Int
def getSelectedCard: Map[AbstractPlayer, Card]
}

View File

@@ -0,0 +1,11 @@
package de.knockoutwhist.control.sublogic
import de.knockoutwhist.undo.Command
trait UndoManager {
def doStep(command: Command): Unit
def undoStep(): Unit
def redoStep(): Unit
}

View File

@@ -1,87 +0,0 @@
package de.knockoutwhist.controlold
import de.knockoutwhist.cards.base.CardBaseManager
import de.knockoutwhist.cards.{Card, Hand, Suit}
import de.knockoutwhist.control.ControlHandler
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Round, Trick}
import scala.util.Random
case object AILogic {
def decideCard(ai: AbstractPlayer, round: Round, trick: Trick): Card = {
if(trick.firstCard.isEmpty) return ai.currentHand().get.cards.maxBy(_.cardValue.ordinal)
val firstCardSuit = trick.firstCard.get.suit
val hand = ai.currentHand().get
val cardsOfSuit = hand.cards.filter(_.suit == firstCardSuit)
val trumpsInGame = trick.cards.keys.filter(_.suit == round.trumpSuit)
if (cardsOfSuit.isEmpty) {
val trumpCards = hand.cards.filter(_.suit == round.trumpSuit)
if (trumpCards.isEmpty) hand.cards.minBy(_.cardValue.ordinal)
else {
val bestOption = decideWhichTrumpCard(hand, round, trick, trumpsInGame.toList)
grabBestResult(bestOption, hand, round, trick)
}
} else {
if(trumpsInGame.nonEmpty) cardsOfSuit.minBy(_.cardValue.ordinal)
else cardsOfSuit.maxBy(_.cardValue.ordinal)
}
ControlHandler.cardManager.nextCard()
}
private def grabBestResult(bestOption: Option[Card], hand: Hand, round: Round, trick: Trick): Card = {
bestOption match {
case Some(card) => card
case None =>
val card = hand.cards.filter(_.suit != round.trumpSuit)
if (card.isEmpty) hand.cards.minBy(_.cardValue.ordinal)
else card.minBy(_.cardValue.ordinal)
}
}
private def decideWhichTrumpCard(hand: Hand, round: Round, trick: Trick, activeTrumps: List[Card]): Option[Card] = {
val trumpCards = hand.cards.filter(_.suit == round.trumpSuit)
if (round.playerQueue.size - trick.cards.size == 1 && activeTrumps.isEmpty) return Some(trumpCards.minBy(_.cardValue.ordinal))
val highestTrump = trumpCards.maxBy(_.cardValue.ordinal)
val activeTrump = activeTrumps.maxBy(_.cardValue.ordinal)
if (highestTrump.cardValue.ordinal < activeTrump.cardValue.ordinal) None
else {
val higherTrumps = trumpCards.filter(_.cardValue.ordinal > activeTrump.cardValue.ordinal)
if(round.playerQueue.size - trick.cards.size <= round.playersin.size * 0.5) Some(higherTrumps.minBy(_.cardValue.ordinal))
else Some(highestTrump)
}
}
def decideTrumpSuit(ai: AbstractPlayer): Suit = {
val hand = ai.currentHand().get
hand.cards.groupBy(_.suit).maxBy(_._2.size)._1
}
def decideTie(min: Int, max: Int): Int = {
Random.between(min, max+1)
}
def decideDogCard(ai: AbstractPlayer, round: Round, trick: Trick, needstoplay: Boolean): Option[Card] = {
val firstCardSuit = trick.firstCard.get.suit
val hand = ai.currentHand().get
val trumpsuit = round.trumpSuit
val trumpsuitPlayed = trick.cards.keys.exists(_.suit == trumpsuit)
if(needstoplay) {
Some(hand.cards.head)
} else if(trumpsuitPlayed) {
sortbestcard(trick, trumpsuit, hand)
} else {
sortbestcard(trick, firstCardSuit, hand)
}
}
private def sortbestcard(trick: Trick, suit: Suit, hand: Hand): Option[Card] = {
val highestCard = trick.cards.keys.filter(_.suit == suit).maxBy(_.cardValue.ordinal)
if (hand.cards.head.suit == suit && hand.cards.head.cardValue.ordinal > highestCard.cardValue.ordinal) {
return Some(hand.cards.head)
}
None
}
}

View File

@@ -0,0 +1,24 @@
package de.knockoutwhist.di
import com.google.inject.AbstractModule
import de.knockoutwhist.cards.CardManager
import de.knockoutwhist.cards.base.CardBaseManager
import de.knockoutwhist.components.{Configuration, DefaultConfiguration}
import de.knockoutwhist.persistence.formats.{FileFormatter, JSONFormatter}
import de.knockoutwhist.utils.baseQueue.{CustomPlayerQueueBuilder, QueueBuilder}
class KnockOutLogicModule extends AbstractModule {
override def configure(): Unit = {
bind(classOf[CardManager]).to(classOf[CardBaseManager])
bind(classOf[QueueBuilder]).to(classOf[CustomPlayerQueueBuilder])
bind(classOf[FileFormatter]).to(classOf[JSONFormatter])
}
}
class KnockOutConfigurationModule extends AbstractModule {
override def configure(): Unit = {
bind(classOf[Configuration]).to(classOf[DefaultConfiguration])
}
}

View File

@@ -1,58 +0,0 @@
package de.knockoutwhist.events
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round}
import de.knockoutwhist.utils.events.SimpleEvent
enum GLOBAL_STATUS {
case SHOW_TIE
case SHOW_TIE_WINNER
case SHOW_TIE_TIE
case SHOW_START_MATCH
case SHOW_TYPE_PLAYERS
case SHOW_FINISHED_MATCH
}
enum PLAYER_STATUS {
case SHOW_TURN
case SHOW_PLAY_CARD
case SHOW_DOG_PLAY_CARD
case SHOW_TIE_NUMBERS
case SHOW_TRUMPSUIT_OPTIONS
case SHOW_NOT_PLAYED
case SHOW_WON_PLAYER_TRICK
}
enum ROUND_STATUS {
case SHOW_START_ROUND
case WON_ROUND
case PLAYERS_OUT
}
enum ERROR_STATUS {
case INVALID_NUMBER
case NOT_A_NUMBER
case INVALID_INPUT
case INVALID_NUMBER_OF_PLAYERS
case IDENTICAL_NAMES
case INVALID_NAME_FORMAT
case WRONG_CARD
}
abstract class ShowStatusEvent extends SimpleEvent
case class ShowGlobalStatus(status: GLOBAL_STATUS, objects: Any*) extends ShowStatusEvent {
override def id: String = "ShowGlobalStatus"
}
case class ShowPlayerStatus(status: PLAYER_STATUS, player: AbstractPlayer, objects: Any*) extends ShowStatusEvent {
override def id: String = "ShowPlayerStatus"
}
case class ShowErrorStatus(status: ERROR_STATUS, objects: Any*) extends ShowStatusEvent {
override def id: String = "ShowErrorStatus"
}
case class ShowRoundStatus(status: ROUND_STATUS, currentRound: Round, objects: Any*) extends ShowStatusEvent {
override def id: String = "ShowRoundStatus"
}

View File

@@ -1,8 +0,0 @@
package de.knockoutwhist.events.cards
import de.knockoutwhist.cards.Hand
import de.knockoutwhist.utils.events.SimpleEvent
final case class RenderHandEvent(hand: Hand, showNumbers: Boolean) extends SimpleEvent {
override def id: String = "RenderHandEvent"
}

View File

@@ -1,9 +0,0 @@
package de.knockoutwhist.events.cards
import de.knockoutwhist.cards.Card
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
case class ShowTieCardsEvent(card: List[(AbstractPlayer, Card)]) extends SimpleEvent {
override def id: String = "ShowTieCardsEvent"
}

View File

@@ -1,12 +0,0 @@
package de.knockoutwhist.events.directional
import de.knockoutwhist.cards.{Card, Hand}
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round, Trick}
import de.knockoutwhist.utils.events.SimpleEvent
import scala.util.Try
case class RequestCardEvent(hand: Hand, matchImpl: Match, round: Round, trick: Trick, currentIndex: Int, player: AbstractPlayer) extends SimpleEvent {
override def id: String = "RequestCardEvent"
}

View File

@@ -1,12 +0,0 @@
package de.knockoutwhist.events.directional
import de.knockoutwhist.cards.{Card, Hand}
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round, Trick}
import de.knockoutwhist.utils.events.SimpleEvent
import scala.util.Try
case class RequestDogPlayCardEvent(hand: Hand, matchImpl: Match, round: Round, trick: Trick, currentIndex: Int, player: AbstractPlayer, needstoplay: Boolean) extends SimpleEvent {
override def id: String = "RequestDogPlayCardEvent"
}

View File

@@ -1,12 +0,0 @@
package de.knockoutwhist.events.directional
import de.knockoutwhist.cards.Suit
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.Match
import de.knockoutwhist.utils.events.SimpleEvent
import scala.util.Try
case class RequestPickTrumpsuitEvent(matchImpl: Match, remaining_players: List[AbstractPlayer], firstRound: Boolean, player: AbstractPlayer) extends SimpleEvent {
override def id: String = "RequestPickTrumpsuitEvent"
}

View File

@@ -1,11 +0,0 @@
package de.knockoutwhist.events.directional
import de.knockoutwhist.cards.Suit
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
import scala.util.Try
case class RequestPlayersEvent() extends SimpleEvent {
override def id: String = "RequestPlayersEvent"
}

View File

@@ -1,13 +0,0 @@
package de.knockoutwhist.events.directional
import de.knockoutwhist.cards.Card
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round}
import de.knockoutwhist.utils.events.SimpleEvent
import scala.collection.immutable
import scala.util.Try
case class RequestTieNumberEvent(winner: List[AbstractPlayer], matchImpl: Match, round: Round, playersout: List[AbstractPlayer], cut: immutable.HashMap[AbstractPlayer, Card], currentStep: Int, remaining: Int, currentIndex: Int = 0) extends SimpleEvent {
override def id: String = "RequestNumberEvent"
}

View File

@@ -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"
}

View File

@@ -0,0 +1,8 @@
package de.knockoutwhist.events.global
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"
}

View File

@@ -0,0 +1,8 @@
package de.knockoutwhist.events.global
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
case class MatchEndEvent(winner: AbstractPlayer) extends SimpleEvent {
override def id: String = "RoundEndEvent"
}

View File

@@ -0,0 +1,7 @@
package de.knockoutwhist.events.global
import de.knockoutwhist.utils.events.SimpleEvent
case class NewRoundEvent() extends SimpleEvent {
override def id: String = s"NewRoundEvent"
}

View File

@@ -0,0 +1,7 @@
package de.knockoutwhist.events.global
import de.knockoutwhist.utils.events.SimpleEvent
case class NewTrickEvent() extends SimpleEvent {
override def id: String = s"NewTrickEvent"
}

View File

@@ -0,0 +1,8 @@
package de.knockoutwhist.events.global
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"
}

View File

@@ -0,0 +1,7 @@
package de.knockoutwhist.events.global
import de.knockoutwhist.utils.events.SimpleEvent
case class SessionClosed() extends SimpleEvent {
override def id: String = s"SessionClosed"
}

View File

@@ -0,0 +1,10 @@
package de.knockoutwhist.events.global
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
case class ShowDogsEvent(dogs: List[AbstractPlayer]) extends SimpleEvent {
override def id: String = "ShowDogsEvent"
}

View File

@@ -0,0 +1,10 @@
package de.knockoutwhist.events.global
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
case class ShowPlayersOutEvent(out: List[AbstractPlayer]) extends SimpleEvent {
override def id: String = "ShowPlayersOutEvent"
}

View File

@@ -0,0 +1,8 @@
package de.knockoutwhist.events.global
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
case class TrickEndEvent(winner: AbstractPlayer) extends SimpleEvent {
override def id: String = s"TrickEndEvent"
}

View File

@@ -0,0 +1,10 @@
package de.knockoutwhist.events.global
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
case class TrumpSelectEvent(player: AbstractPlayer) extends SimpleEvent{
override def id: String = "TrumpSelectEvent"
}

View File

@@ -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"
}

View File

@@ -0,0 +1,8 @@
package de.knockoutwhist.events.global
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
case class TurnEvent(player: AbstractPlayer) extends SimpleEvent {
override def id: String = s"TurnEvent"
}

View File

@@ -0,0 +1,8 @@
package de.knockoutwhist.events.global.tie
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
case class TieAllPlayersSelectedEvent() extends SimpleEvent {
override def id: String = s"TieAllPlayersSelectedEvent"
}

View File

@@ -0,0 +1,8 @@
package de.knockoutwhist.events.global.tie
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
case class TieEvent(players: List[AbstractPlayer]) extends SimpleEvent {
override def id: String = s"TieEvent"
}

View File

@@ -0,0 +1,8 @@
package de.knockoutwhist.events.global.tie
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
case class TieShowPlayerCardsEvent() extends SimpleEvent {
override def id: String = s"TieShowPlayerCardsEvent"
}

View File

@@ -0,0 +1,8 @@
package de.knockoutwhist.events.global.tie
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
case class TieTieEvent(players: List[AbstractPlayer]) extends SimpleEvent {
override def id: String = s"TieTieEvent"
}

View File

@@ -0,0 +1,8 @@
package de.knockoutwhist.events.global.tie
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
case class TieTurnEvent(player: AbstractPlayer) extends SimpleEvent {
override def id: String = s"TieTurnEvent"
}

View File

@@ -0,0 +1,11 @@
package de.knockoutwhist.events.global.tie
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
case class TieWinningPlayersEvent(winners: List[AbstractPlayer]) extends SimpleEvent {
override def id: String = s"TieWinningPlayersEvent"
def isSingleWinner: Boolean = winners.size == 1
}

View File

@@ -0,0 +1,12 @@
package de.knockoutwhist.events.player
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
import java.util.UUID
abstract class PlayerEvent(player: AbstractPlayer) extends SimpleEvent {
def playerId: UUID = player.id
}

View File

@@ -0,0 +1,7 @@
package de.knockoutwhist.events.player
import de.knockoutwhist.player.AbstractPlayer
case class ReceivedHandEvent(player: AbstractPlayer) extends PlayerEvent(player) {
override def id: String = "ReceivedHandEvent"
}

View File

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

View File

@@ -0,0 +1,7 @@
package de.knockoutwhist.events.player
import de.knockoutwhist.player.AbstractPlayer
case class RequestTieChoiceEvent(player: AbstractPlayer, maxNumber: Int) extends PlayerEvent(player) {
override def id: String = "RequestTieChoiceEvent"
}

View File

@@ -0,0 +1,7 @@
package de.knockoutwhist.events.player
import de.knockoutwhist.player.AbstractPlayer
case class RequestTrumpSuitEvent(player: AbstractPlayer) extends PlayerEvent(player) {
override def id: String = "RequestTrumpSuitEvent"
}

View File

@@ -1,8 +0,0 @@
package de.knockoutwhist.events.round
import de.knockoutwhist.rounds.{Round, Trick}
import de.knockoutwhist.utils.events.SimpleEvent
case class ShowCurrentTrickEvent(round: Round, trick: Trick) extends SimpleEvent {
override def id: String = "ShowCurrentTrickEvent"
}

View File

@@ -1,13 +0,0 @@
package de.knockoutwhist.events.ui
import de.knockoutwhist.utils.events.SimpleEvent
enum GameState:
case MAIN_MENU
case INGAME
end GameState
case class GameStateUpdateEvent(gameState: GameState) extends SimpleEvent {
override def id: String = s"GameStateUpdateEvent($gameState)"
}

View File

@@ -0,0 +1,7 @@
package de.knockoutwhist.events.util
import de.knockoutwhist.utils.events.SimpleEvent
case class ReloadAllEvent() extends SimpleEvent {
override def id: String = "ReloadAllEvent"
}

View File

@@ -0,0 +1,25 @@
package de.knockoutwhist.persistence
import de.knockoutwhist.KnockOutWhist
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
import de.knockoutwhist.control.controllerBaseImpl.sublogic.BasePlayerTieLogic
import de.knockoutwhist.control.sublogic.PlayerTieLogic
import de.knockoutwhist.control.{ControlThread, GameLogic, LogicSnapshot}
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round, Trick}
enum MethodEntryPoint:
case ControlMatch
case ControlRound
case ControlTrick
end MethodEntryPoint
case class MatchSnapshot(
entryPoint: Option[MethodEntryPoint] = None,
gameLogicSnapShot: Option[LogicSnapshot[GameLogic]] = None
) {
def withMethodEntryPoint(newEntryPoint: MethodEntryPoint): MatchSnapshot = this.copy(entryPoint = Some(newEntryPoint))
def withGameLogicSnapShot(newGameLogicSnapShot: LogicSnapshot[GameLogic]): MatchSnapshot = this.copy(gameLogicSnapShot = Some(newGameLogicSnapShot))
}

View File

@@ -0,0 +1,11 @@
package de.knockoutwhist.persistence.formats
import de.knockoutwhist.cards.Card
object CardFormatUtil {
def grabSpecificCard(card: Card, cc: List[Card]): Card = {
cc.filter(c => c.suit == card.suit && c.cardValue == card.cardValue).head
}
}

View File

@@ -0,0 +1,50 @@
package de.knockoutwhist.persistence.formats
import de.knockoutwhist.cards.Hand
import de.knockoutwhist.persistence.MatchSnapshot
import de.knockoutwhist.player.{AbstractPlayer, PlayerFactory, Playertype}
import java.util.UUID
import scala.collection.mutable
trait FileFormatter {
def formatName: String
def fileEnding: String
def createFormat(matchSnapshot: MatchSnapshot): Array[Byte]
def parseFormat(bytes: Array[Byte]): MatchSnapshot
def writeToFile(matchSnapshot: MatchSnapshot, path: String): Unit = {
val file = new java.io.File(path)
val out = new java.io.FileOutputStream(file)
out.write(createFormat(matchSnapshot))
out.close()
}
def readFromFile(path: String): MatchSnapshot = {
val file = new java.io.File(path)
val in = new java.io.FileInputStream(file)
val data = new Array[Byte](file.length.toInt)
in.read(data)
in.close()
parseFormat(data)
}
class PlayerUtil {
private val players: mutable.HashMap[UUID, AbstractPlayer] = mutable.HashMap()
def handlePlayer(id: UUID, name: String, hand: Option[Hand], dog_life: Boolean, player_type: Playertype): AbstractPlayer = {
players.get(id) match {
case Some(player) => player
case None =>
val player = PlayerFactory.createPlayer(name, id, player_type)
if(hand.isDefined) player.provideHand(hand.get)
if(dog_life) player.setDogLife()
players.put(id, player)
player
}
}
}
}

View File

@@ -0,0 +1,322 @@
package de.knockoutwhist.persistence.formats
import de.knockoutwhist.KnockOutWhist
import de.knockoutwhist.cards.*
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogicSnapshot
import de.knockoutwhist.control.controllerBaseImpl.sublogic.{BasePlayerTieLogic, BasePlayerTieLogicSnapshot}
import de.knockoutwhist.control.{GameLogic, GameState, LogicSnapshot}
import de.knockoutwhist.persistence.{MatchSnapshot, MethodEntryPoint}
import de.knockoutwhist.player.{AbstractPlayer, PlayerData, PlayerFactory, Playertype}
import de.knockoutwhist.rounds.{Match, Round, Trick}
import play.api.libs.json
import play.api.libs.json.*
import java.util.UUID
import scala.collection.immutable.HashMap
class JSONFormatter extends FileFormatter {
private val playerUtil = new PlayerUtil
override def formatName: String = "JSON"
override def fileEnding: String = "json"
override def createFormat(matchSnapshot: MatchSnapshot): Array[Byte] = {
val js = Json.obj(
matchSnapshot.entryPoint match {
case None => "entryPoint" -> JsString("None")
case Some(entryPoint) => "entryPoint" -> JsString(entryPoint.toString)
},
matchSnapshot.gameLogicSnapShot match {
case None => "gameLogic" -> JsString("None")
case Some(gameLogic) => "gameLogic" -> BaseGameLogicJsonFormatter.createBaseGameLogicJson(gameLogic.asInstanceOf[BaseGameLogicSnapshot])
},
)
js.toString().getBytes
}
override def parseFormat(bytes: Array[Byte]): MatchSnapshot = {
val json = Json.parse(new String(bytes))
val methodEntryPoint = (json \ "entryPoint").get match {
case JsString("None") => None
case JsString(methodEntryPoint) => Some(MethodEntryPoint.valueOf(methodEntryPoint))
case default => None
}
val gameLogic = (json \ "gameLogic").get match {
case JsString("None") => None
case gameLogicJson: JsObject => Some(BaseGameLogicJsonFormatter.createBaseGameLogicFromJson(gameLogicJson, playerUtil))
case default => None
}
MatchSnapshot(methodEntryPoint, gameLogic.asInstanceOf[Option[LogicSnapshot[GameLogic]]])
}
private object BaseGameLogicJsonFormatter {
def createBaseGameLogicJson(baseGameLogic: BaseGameLogicSnapshot): JsValue = {
Json.obj(
"gameState" -> JsString(baseGameLogic.savedState.toString),
"cardManagerContainer" -> Json.obj(
"cardContainer" -> Json.obj(
"card" ->
(if (baseGameLogic.cardContainer.isDefined) JsArray(
baseGameLogic.cardContainer.get
.map(card => CardJsonFormatter.createCardJson(card)))
else JsString("None"))
),
"currentIdx" ->
(if (baseGameLogic.cardIndex.isDefined) JsNumber(baseGameLogic.cardIndex.get) else JsString("None"))
),
"match" -> (if (baseGameLogic.currentMatch.isDefined) MatchJsonFormatter.createMatchJson(baseGameLogic.currentMatch.get) else JsString("None")),
"round" -> (if (baseGameLogic.currentRound.isDefined) RoundJsonFormatter.createRoundJson(baseGameLogic.currentRound.get) else JsString("None")),
"trick" -> (if (baseGameLogic.currentTrick.isDefined) TrickJsonFormatter.createTrickJson(baseGameLogic.currentTrick.get) else JsString("None")),
"player" -> (if (baseGameLogic.currentPlayer.isDefined) PlayerJsonFormatter.createPlayerJson(baseGameLogic.currentPlayer.get) else JsString("None")),
"queue" -> Json.obj(
"currentIndx" -> (if (baseGameLogic.playerIndex.isDefined) JsNumber(baseGameLogic.playerIndex.get) else JsString("None")),
"players" ->
(if (baseGameLogic.players.isDefined) JsArray(
baseGameLogic.players.get
.map(player => PlayerJsonFormatter.createPlayerJson(player)))
else JsString("None"))
),
)
}
def createBaseGameLogicFromJson(baseGameLogicJson: JsObject, playerUtil: PlayerUtil): BaseGameLogicSnapshot = {
val gameState = GameState.valueOf((baseGameLogicJson \ "gameState").get.asInstanceOf[JsString].value)
val cardContainerJson = (baseGameLogicJson \ "cardManagerContainer" \ "cardContainer" \ "card").get
val cardContainer = if (cardContainerJson.isInstanceOf[JsString]) {
None
} else {
Some(cardContainerJson.asInstanceOf[JsArray].value.map(cardJson => CardJsonFormatter.parseCardJson(cardJson.asInstanceOf[JsObject])).toList)
}
val cc = cardContainer match {
case Some(cards) => cards
case None => List.empty[Card]
}
val cardIndex = (baseGameLogicJson \ "cardManagerContainer" \ "currentIdx").get match {
case JsString("None") => None
case JsNumber(idx) => Some(idx.toInt)
case _ => None
}
val matchImpl = (baseGameLogicJson \ "match").get match {
case JsString("None") => None
case matchJson: JsObject => Some(MatchJsonFormatter.parseMatchJson(matchJson, playerUtil, cc))
case _ => None
}
val round = (baseGameLogicJson \ "round").get match {
case JsString("None") => None
case roundJson: JsObject => Some(RoundJsonFormatter.parseRoundJson(roundJson, playerUtil, cc))
case _ => None
}
val trick = (baseGameLogicJson \ "trick").get match {
case JsString("None") => None
case trickJson: JsObject => Some(TrickJsonFormatter.parseTrickJson(trickJson, playerUtil, cc))
case _ => None
}
val player = (baseGameLogicJson \ "player").get match {
case JsString("None") => None
case playerJson: JsObject => Some(PlayerJsonFormatter.parsePlayerJson(playerJson, playerUtil, cc))
case _ => None
}
val queueJson = (baseGameLogicJson \ "queue").get
val playerIndex = (queueJson \ "currentIndx").get match {
case JsString("None") => None
case JsNumber(idx) => Some(idx.toInt)
case _ => None
}
val playersJson = (queueJson \ "players").get
val players = if (playersJson.isInstanceOf[JsString]) {
None
} else {
Some(playersJson.asInstanceOf[JsArray].value.map(playerJson => PlayerJsonFormatter.parsePlayerJson(playerJson.asInstanceOf[JsObject], playerUtil, cc)).toList)
}
BaseGameLogicSnapshot(
gameState,
cardContainer,
cardIndex,
matchImpl,
round,
trick,
player,
playerIndex,
players,
matchImpl match {
case Some(m) => m.totalplayers.map(p => (p.id, p.generatePlayerData())).toMap
case None => Map.empty[UUID, PlayerData]
}
)
}
}
private object PlayerJsonFormatter {
def createPlayerJson(abstractPlayer: AbstractPlayer): JsValue = {
Json.obj(
"id" -> abstractPlayer.id.toString,
"name" -> abstractPlayer.name,
"hand" -> createHandJson(abstractPlayer.currentHand()),
"doglife" -> abstractPlayer.isInDogLife,
"playerType" -> PlayerFactory.parsePlayerType(abstractPlayer).toString
)
}
private def createHandJson(hand: Option[Hand]): JsValue = {
if (hand.isDefined) {
Json.obj(
"cards" -> JsArray(
hand.get.cards.map(card => CardJsonFormatter.createCardJson(card))
)
)
} else {
JsString("None")
}
}
def parsePlayerJson(playerJson: JsObject, playerUtil: PlayerUtil, cards: List[Card]): AbstractPlayer = {
val id = UUID.fromString((playerJson \ "id").get.asInstanceOf[JsString].value)
val name = (playerJson \ "name").get.asInstanceOf[JsString].value
val handJson = playerJson \ "hand"
val doglife = (playerJson \ "doglife").get.asInstanceOf[JsBoolean].value
val playerType = Playertype.valueOf((playerJson \ "playerType").get.asInstanceOf[JsString].value)
val hand = if (handJson.isEmpty || !handJson.get.isInstanceOf[JsObject]) {
None
} else {
Some(parseHandJson(handJson.get.asInstanceOf[JsObject], cards))
}
playerUtil.handlePlayer(id, name, hand, doglife, playerType)
}
private def parseHandJson(handJson: JsObject, cc: List[Card]): Hand = {
val cards = (handJson \ "cards").get.asInstanceOf[JsArray].value.map(cardJson => {
val card = CardJsonFormatter.parseCardJson(cardJson.asInstanceOf[JsObject])
CardFormatUtil.grabSpecificCard(card, cc)
})
Hand(cards.toList)
}
}
private object TrickJsonFormatter {
def createTrickJson(trick: Trick): JsValue = {
Json.obj(
"plays" -> JsArray(
trick.cards.map { case (card, player) => Json.obj(
"card" -> CardJsonFormatter.createCardJson(card),
"player" -> PlayerJsonFormatter.createPlayerJson(player)
)
}.toList
),
"firstCard" -> (if (trick.firstCard.isDefined) CardJsonFormatter.createCardJson(trick.firstCard.get) else JsString("")),
"winner" -> (if (trick.winner.isDefined) PlayerJsonFormatter.createPlayerJson(trick.winner.get) else JsString("")),
)
}
def parseTrickJson(trickJson: JsObject, playerUtil: PlayerUtil, cc: List[Card]): Trick = {
val plays = (trickJson \ "plays").get
val playsList = plays.asInstanceOf[JsArray].value.map(playJson => {
val card = CardJsonFormatter.parseCardJson((playJson \ "card").get.asInstanceOf[JsObject])
val player = PlayerJsonFormatter.parsePlayerJson((playJson \ "player").get.asInstanceOf[JsObject], playerUtil, cc)
(card, player)
}).groupBy(_._1).map((card, list) => (card, list.map(_._2).head))
val firstCard = if ((trickJson \ "firstCard").get.isInstanceOf[JsString]) {
None
} else {
Some(CardJsonFormatter.parseCardJson((trickJson \ "firstCard").get.asInstanceOf[JsObject]))
}
val winner = if ((trickJson \ "winner").get.isInstanceOf[JsString]) {
None
} else {
Some(PlayerJsonFormatter.parsePlayerJson((trickJson \ "winner").get.asInstanceOf[JsObject], playerUtil, cc))
}
val hashed = HashMap(playsList.toSeq *)
Trick(hashed, winner, firstCard)
}
}
private object RoundJsonFormatter {
def createRoundJson(round: Round): JsValue = {
Json.obj(
"tricks" -> JsArray(
round.tricklist.map(trick => TrickJsonFormatter.createTrickJson(trick))
),
"trumpSuit" -> JsNumber(round.trumpSuit.ordinal),
"winner" -> (if (round.winner.isDefined) PlayerJsonFormatter.createPlayerJson(round.winner.get) else JsString("None")),
"firstRound" -> JsBoolean(round.firstRound)
)
}
def parseRoundJson(roundJson: JsObject, playerUtil: PlayerUtil, cc: List[Card]): Round = {
val tricks = (roundJson \ "tricks").get
val tricksList = tricks.asInstanceOf[JsArray].value.map(trickJson => TrickJsonFormatter.parseTrickJson(trickJson.asInstanceOf[JsObject], playerUtil, cc)).toList
val trumpSuit = Suit.fromOrdinal((roundJson \ "trumpSuit").get.asInstanceOf[JsNumber].value.toInt)
val winner = if ((roundJson \ "winner").get.isInstanceOf[JsString]) {
None
} else {
Some(PlayerJsonFormatter.parsePlayerJson((roundJson \ "winner").get.asInstanceOf[JsObject], playerUtil, cc))
}
val firstRound = (roundJson \ "firstRound").get.asInstanceOf[JsBoolean].value
Round(trumpSuit = trumpSuit, tricklist = tricksList, winner = winner, firstRound = firstRound)
}
}
private object MatchJsonFormatter {
def createMatchJson(matchImpl: Match): JsValue = {
Json.obj(
"totalplayers" -> JsArray(
matchImpl.totalplayers.map(player => PlayerJsonFormatter.createPlayerJson(player))
),
"playerIn" -> JsArray(
matchImpl.playersIn.map(player => PlayerJsonFormatter.createPlayerJson(player))
),
"numberofcards" -> JsNumber(matchImpl.numberofcards),
"dogLife" -> JsBoolean(matchImpl.dogLife),
"roundlist" -> JsArray(
matchImpl.roundlist.map(round => RoundJsonFormatter.createRoundJson(round))
)
)
}
def parseMatchJson(matchJson: JsObject, playerUtil: PlayerUtil, cc: List[Card]): Match = {
val totalplayers = (matchJson \ "totalplayers").get.asInstanceOf[JsArray].value.map(playerJson => PlayerJsonFormatter.parsePlayerJson(playerJson.asInstanceOf[JsObject], playerUtil, cc)).toList
val playersIn = (matchJson \ "playerIn").get.asInstanceOf[JsArray].value.map(playerJson => PlayerJsonFormatter.parsePlayerJson(playerJson.asInstanceOf[JsObject], playerUtil, cc)).toList
val numberofcards = (matchJson \ "numberofcards").get.asInstanceOf[JsNumber].value.toInt
val dogLife = (matchJson \ "dogLife").get.asInstanceOf[JsBoolean].value
val roundlist = (matchJson \ "roundlist").get.asInstanceOf[JsArray].value.map(roundJson => RoundJsonFormatter.parseRoundJson(roundJson.asInstanceOf[JsObject], playerUtil, cc)).toList
Match(totalplayers, playersIn, numberofcards, dogLife, roundlist)
}
}
private object CardJsonFormatter {
def createCardJson(card: Card): JsObject = {
Json.obj(
"value" -> JsNumber(card.cardValue.ordinal),
"suit" -> JsNumber(card.suit.ordinal)
)
}
def parseCardJson(cardJson: JsObject): Card = {
val value = (cardJson \ "value").get.asInstanceOf[JsNumber].value.toInt
val suit = (cardJson \ "suit").get.asInstanceOf[JsNumber].value.toInt
Card(CardValue.fromOrdinal(value), Suit.fromOrdinal(suit))
}
}
}

View File

@@ -1,36 +0,0 @@
package de.knockoutwhist.player
import de.knockoutwhist.cards.{Card, Hand, Suit}
import de.knockoutwhist.controlold.AILogic
import de.knockoutwhist.rounds.{Match, Round, Trick}
import scala.collection.immutable
import scala.util.Try
class AIPlayer private[player](name: String, hand: Option[Hand], doglife: Boolean = false) extends AbstractPlayer(name, hand, doglife) {
override def provideHand(hand: Hand): AbstractPlayer = {
AIPlayer(name, Some(hand), doglife)
}
override def removeCard(card: Card): AbstractPlayer = {
AIPlayer(name, Some(hand.get.removeCard(card)), doglife)
}
override def setDogLife(): AbstractPlayer = AIPlayer(name, hand, true)
override def handlePlayCard(hand: Hand, matchImpl: Match, round: Round, trick: Trick, currentIndex: Int): Unit = {
AILogic.decideCard(this, round, trick)
}
override def handleDogPlayCard(hand: Hand, matchImpl: Match, round: Round, trick: Trick, currentIndex: Int, needstoplay: Boolean): Unit = {
AILogic.decideDogCard(this, round, trick, needstoplay)
}
override def handlePickTrumpsuit(matchImpl: Match, remaining_players: List[AbstractPlayer], firstRound: Boolean): Unit = {
AILogic.decideTrumpSuit(this)
}
override def handlePickTieCard(winner: List[AbstractPlayer], matchImpl: Match, round: Round, playersout: List[AbstractPlayer], cut: immutable.HashMap[AbstractPlayer, Card], currentStep: Int, remaining: Int, currentIndex: Int = 0): Unit = {
AILogic.decideTie(1, remaining)
}
}

View File

@@ -1,25 +1,63 @@
package de.knockoutwhist.player
import de.knockoutwhist.cards.{Card, Hand, Suit}
import de.knockoutwhist.rounds.{Match, Round, Trick}
import de.knockoutwhist.cards.{Card, Hand}
import scala.collection.immutable
import scala.util.Try
import java.util.UUID
abstract case class AbstractPlayer private[player](var name: String, hand: Option[Hand], doglife: Boolean = false) {
//If you get an uuid conflict, go play the lottery!!!
abstract case class AbstractPlayer private[player](name: String, id: UUID = UUID.randomUUID()) {
protected var hand: Option[Hand] = None
protected var dogLife: Boolean = false
def currentHand(): Option[Hand] = hand
def provideHand(hand: Hand): AbstractPlayer
def setDogLife(): AbstractPlayer
def removeCard(card: Card): AbstractPlayer
def handlePlayCard(hand: Hand, matchImpl: Match, round: Round, trick: Trick, currentIndex: Int): Unit
def handleDogPlayCard(hand: Hand, matchImpl: Match, round: Round, trick: Trick, currentIndex: Int, needstoplay: Boolean): Unit
def handlePickTrumpsuit(matchImpl: Match, remaining_players: List[AbstractPlayer], firstRound: Boolean): Unit
def handlePickTieCard(winner: List[AbstractPlayer], matchImpl: Match, round: Round, playersout: List[AbstractPlayer], cut: immutable.HashMap[AbstractPlayer, Card], currentStep: Int, remaining: Int, currentIndex: Int = 0): Unit
def isInDogLife: Boolean = dogLife
def provideHand(hand: Hand): Unit = {
this.hand = Some(hand)
}
def setDogLife(): Unit = {
this.dogLife = true
}
def resetDogLife(): Unit = {
this.dogLife = false
}
def removeCard(card: Card): Unit = {
this.hand = this.hand.map(_.removeCard(card))
}
override def toString: String = {
name
}
override def canEqual(that: Any): Boolean = that.isInstanceOf[AbstractPlayer]
override def equals(obj: Any): Boolean = {
obj match {
case that: AbstractPlayer => this.id.equals(that.id)
case _ => false
}
}
def generatePlayerData(): PlayerData = {
PlayerData(id, name, hand, dogLife)
}
def receivePlayerData(data: PlayerData): Unit = {
if (this.id != data.id) {
throw new IllegalArgumentException("Cannot receive PlayerData for a different player!")
}
if (this.name != data.name) {
throw new IllegalArgumentException("Cannot change player name via PlayerData!")
}
this.hand = data.hand
this.dogLife = data.dogLife
}
}
case class PlayerData(id: UUID, name: String, hand: Option[Hand], dogLife: Boolean) {
}

View File

@@ -1,40 +0,0 @@
package de.knockoutwhist.player
import de.knockoutwhist.cards.CardValue.Ten
import de.knockoutwhist.cards.Suit.Spades
import de.knockoutwhist.cards.{Card, Hand}
import de.knockoutwhist.control.ControlHandler
import de.knockoutwhist.control.controllerBaseImpl.{PlayerLogic, TrickLogic}
import de.knockoutwhist.rounds.{Match, Round, Trick}
import scala.collection.immutable
import scala.util.Try
class MockPlayer private[player](name: String, hand: Option[Hand], doglife: Boolean = false) extends AbstractPlayer(name, hand, doglife) {
override def provideHand(hand: Hand): AbstractPlayer = {
MockPlayer(name, Some(hand), doglife)
}
override def removeCard(card: Card): AbstractPlayer = {
MockPlayer(name, Some(hand.get.removeCard(card)), doglife)
}
override def setDogLife(): AbstractPlayer = MockPlayer(name, hand, true)
override def handlePlayCard(hand: Hand, matchImpl: Match, round: Round, trick: Trick, currentIndex: Int): Unit = {
ControlHandler.trickcomponent.controlSuitplayed(Try{Card(Ten, Spades)}, matchImpl, round, trick, currentIndex, this)
}
override def handleDogPlayCard(hand: Hand, matchImpl: Match, round: Round, trick: Trick, currentIndex: Int, needstoplay: Boolean): Unit = {
ControlHandler.trickcomponent.controlDogPlayed(Try{None}, matchImpl, round, trick, currentIndex, this)
}
override def handlePickTrumpsuit(matchImpl: Match, remaining_players: List[AbstractPlayer], firstRound: Boolean): Unit = {
ControlHandler.playerlogcomponent.trumpSuitSelected(matchImpl, Try{Spades}, remaining_players, firstRound, this)
}
override def handlePickTieCard(winner: List[AbstractPlayer], matchImpl: Match, round: Round, playersout: List[AbstractPlayer], cut: immutable.HashMap[AbstractPlayer, Card], currentStep: Int, remaining: Int, currentIndex: Int = 0): Unit = {
ControlHandler.playerlogcomponent.selectedTie(winner, matchImpl, round, playersout, cut, Try{1}, currentStep, remaining, currentIndex)
}
}

View File

@@ -1,28 +1,45 @@
package de.knockoutwhist.player
import de.knockoutwhist.player.Playertype.{AI, HUMAN, MOCK}
import de.knockoutwhist.player.builder.{AIPlayerBuilder, Director, HumanoidBuilder, MockPlayerBuilder, PlayerBuilder}
import de.knockoutwhist.player.Playertype.{HUMAN, STUB}
import de.knockoutwhist.player.baseImpl.HumanPlayer
import de.knockoutwhist.player.builder.*
import java.util.UUID
enum Playertype:
case HUMAN
case AI
case MOCK
case STUB
end Playertype
object PlayerFactory {
def createPlayer(name: String = null, playertype: Playertype): AbstractPlayer = {
def createPlayer(name: String = null, id: UUID = null, playertype: Playertype): AbstractPlayer = {
val buildType: PlayerBuilder = playertype match {
case HUMAN =>
new HumanoidBuilder()
case AI =>
new AIPlayerBuilder()
case MOCK =>
new MockPlayerBuilder
case STUB =>
new StubPlayerBuilder
}
if (name == null) {
Director.constructWithRandomNames(buildType)
if (id != null) {
Director.constructWithRandomNamesAndID(buildType, id)
} else {
Director.constructWithRandomNames(buildType)
}
} else {
Director.constructWithName(buildType, name)
if (id != null) {
Director.constructWithNameAndID(buildType, name, id)
} else {
Director.constructWithName(buildType, name)
}
}
}
def parsePlayerType(player: AbstractPlayer): Playertype = {
player match {
case _: HumanPlayer =>
HUMAN
case _: StubPlayer =>
STUB
}
}

View File

@@ -0,0 +1,7 @@
package de.knockoutwhist.player
import java.util.UUID
class StubPlayer (name: String, id: UUID = UUID.randomUUID()) extends AbstractPlayer(name, id) {
}

View File

@@ -1,40 +1,10 @@
package de.knockoutwhist.player.baseImpl
import de.knockoutwhist.cards.{Card, Hand, Suit}
import de.knockoutwhist.control.ControlHandler
import de.knockoutwhist.events.directional.{RequestCardEvent, RequestDogPlayCardEvent, RequestPickTrumpsuitEvent, RequestTieNumberEvent}
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.{Match, Round, Trick}
import scala.collection.immutable
import scala.util.Try
import java.util.UUID
class HumanPlayer private[player](name: String, hand: Option[Hand], doglife: Boolean = false) extends AbstractPlayer(name, hand, doglife) {
override def provideHand(hand: Hand): AbstractPlayer = {
HumanPlayer(name, Some(hand), doglife)
}
override def setDogLife(): AbstractPlayer = HumanPlayer(name, hand, true)
override def removeCard(card: Card): AbstractPlayer = {
HumanPlayer(name, Some(hand.get.removeCard(card)), doglife)
}
override def handlePlayCard(hand: Hand, matchImpl: Match, round: Round, trick: Trick, currentIndex: Int): Unit = {
ControlHandler.invoke(RequestCardEvent(hand, matchImpl, round, trick, currentIndex, this))
}
override def handleDogPlayCard(hand: Hand, matchImpl: Match, round: Round, trick: Trick, currentIndex: Int, needstoplay: Boolean): Unit = {
ControlHandler.invoke(RequestDogPlayCardEvent(hand, matchImpl, round, trick, currentIndex, this, needstoplay))
}
override def handlePickTrumpsuit(matchImpl: Match, remaining_players: List[AbstractPlayer], firstRound: Boolean): Unit = {
ControlHandler.invoke(RequestPickTrumpsuitEvent(matchImpl, remaining_players, firstRound, this))
}
override def handlePickTieCard(winner: List[AbstractPlayer], matchImpl: Match, round: Round, playersout: List[AbstractPlayer], cut: immutable.HashMap[AbstractPlayer, Card], currentStep: Int, remaining: Int, currentIndex: Int = 0): Unit = {
ControlHandler.invoke(RequestTieNumberEvent(winner, matchImpl, round, playersout, cut, currentStep, remaining, currentIndex))
}
class HumanPlayer (name: String, id: UUID = UUID.randomUUID()) extends AbstractPlayer(name, id) {
}

View File

@@ -1,31 +0,0 @@
package de.knockoutwhist.player.builder
import de.knockoutwhist.player.{AIPlayer, AbstractPlayer}
class AIPlayerBuilder extends PlayerBuilder {
private var unfinished: Option[AIPlayer] = None
override def setName(name: String): PlayerBuilder = {
if (unfinished.isEmpty) {
unfinished = Some(AIPlayer(name, None))
} else {
unfinished.get.name = name
}
this
}
override def reset(): PlayerBuilder = {
unfinished = None
this
}
override def build(): AbstractPlayer = {
if (unfinished.isDefined) {
val player = unfinished.get
reset()
return player
}
throw new IllegalStateException("Trying to build non-existing AI")
}
}

View File

@@ -19,5 +19,19 @@ object Director {
playerBuilder.setName(name)
playerBuilder.build()
}
def constructWithRandomNamesAndID(playerBuilder: PlayerBuilder, id: java.util.UUID): AbstractPlayer = {
playerBuilder.reset()
playerBuilder.setName(playernames(Random.nextInt(playernames.length)))
playerBuilder.setID(id)
playerBuilder.build()
}
def constructWithNameAndID(playerBuilder: PlayerBuilder, name: String, id: java.util.UUID): AbstractPlayer = {
playerBuilder.reset()
playerBuilder.setName(name)
playerBuilder.setID(id)
playerBuilder.build()
}
}

View File

@@ -3,31 +3,36 @@ package de.knockoutwhist.player.builder
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.player.baseImpl.HumanPlayer
import java.util.UUID
import scala.compiletime.uninitialized
class HumanoidBuilder extends PlayerBuilder {
private var unfinished: Option[HumanPlayer] = None
private var name: Option[String] = None
private var id: Option[UUID] = Some(UUID.randomUUID())
override def setName(name: String): PlayerBuilder = {
if (unfinished.isEmpty) {
unfinished = Some(HumanPlayer(name, None))
} else {
unfinished.get.name = name
}
this.name = Some(name)
this
}
override def setID(id: UUID): PlayerBuilder = {
this.id = Some(id)
this
}
override def reset(): PlayerBuilder = {
unfinished = None
this.name = None
this.id = Some(UUID.randomUUID())
this
}
override def build(): AbstractPlayer = {
if(unfinished.isDefined) {
val player = unfinished.get
if (this.name.isDefined && this.id.isDefined) {
val player = new HumanPlayer(this.name.get, id.get)
reset()
return player
}
throw new IllegalStateException("Trying to build non-existing Human")
throw new IllegalStateException("Trying to build non-existing HumanPlayer")
}
}

View File

@@ -1,31 +0,0 @@
package de.knockoutwhist.player.builder
import de.knockoutwhist.player.{MockPlayer, AbstractPlayer}
class MockPlayerBuilder extends PlayerBuilder {
private var unfinished: Option[MockPlayer] = None
override def setName(name: String): PlayerBuilder = {
if (unfinished.isEmpty) {
unfinished = Some(MockPlayer(name, None))
} else {
unfinished.get.name = name
}
this
}
override def reset(): PlayerBuilder = {
unfinished = None
this
}
override def build(): AbstractPlayer = {
if (unfinished.isDefined) {
val player = unfinished.get
reset()
return player
}
throw new IllegalStateException("Trying to build non-existing AI")
}
}

Some files were not shown because too many files have changed in this diff Show More