Compare commits
144 Commits
release
...
ef7397f7f1
| Author | SHA1 | Date | |
|---|---|---|---|
| ef7397f7f1 | |||
| d833932f16 | |||
| c5dd02a5e8 | |||
| 3048552f4c | |||
| ec94ecd46c | |||
| 20e8bae883 | |||
| a5dcf3ee90 | |||
| b9a7b0a2af | |||
| fbc0ea2277 | |||
| f998d5f6f0 | |||
| c5895f094b | |||
| 8645d4a219 | |||
| c9e44bc604 | |||
| 7dad9052f7 | |||
| 65921c289a | |||
| cc4bc9d93e | |||
| c43cc7cac0 | |||
| e17ab6a552 | |||
| 786204dfaf | |||
| 026c666f03 | |||
| 6335cbee23 | |||
| 16857fe42c | |||
| 1a189ef0fe | |||
| f9a6e6af05 | |||
| 11834a7d44 | |||
| 51ef1f1607 | |||
| 7676dd8ed1 | |||
| 51604124fd | |||
| 48cd4d3956 | |||
| d54f814192 | |||
|
05b6ebb2a8
|
|||
| 079e66a5cf | |||
|
220c3d04ea
|
|||
| a6e514880c | |||
|
ec329df768
|
|||
|
a856843fc3
|
|||
|
|
f57537b3eb | ||
|
6c825845ea
|
|||
|
5b37ebb01d
|
|||
| 2b63fbe88d | |||
|
|
0e31134dfb | ||
|
|
d0ab531e16 | ||
|
b6812d1223
|
|||
|
|
169537c479 | ||
| aad61fd10a | |||
| d9b1bb2186 | |||
| d48e3b1daa | |||
|
fa8cab1d6f
|
|||
|
a53f4dda46
|
|||
|
483991bdbb
|
|||
|
bd73997959
|
|||
|
3a41a4bb4d
|
|||
|
474de82cde
|
|||
|
d39e92cfa8
|
|||
| e1d7405f51 | |||
|
a33e404378
|
|||
|
|
2e0d24adbd | ||
| 13199ddd74 | |||
|
3396355193
|
|||
|
|
111242a6aa | ||
|
|
681fdd4253 | ||
|
|
3fe3abe7fc | ||
|
e0b7a68207
|
|||
|
|
67a590e544 | ||
|
|
8742c5dc7a | ||
|
|
7a46bed011 | ||
|
|
9ecf91282e | ||
|
|
0526f99464 | ||
|
95228cbd51
|
|||
|
|
49e378b51a | ||
|
|
3b6ec1cd1b | ||
|
d21540bbe7
|
|||
|
|
05afe8b392 | ||
|
92e9149d55
|
|||
|
|
5324a16cbe | ||
|
|
ecff0a318d | ||
|
|
b8c5b7a389 | ||
|
3c83fec6b5
|
|||
|
a5f7f14d06
|
|||
|
4aafd01f92
|
|||
|
|
2fe0ddcf45 | ||
|
|
e1c2d660f1 | ||
|
648a1b2ce7
|
|||
|
56cc2f1182
|
|||
|
|
f7b09a0bd9 | ||
|
|
ed8356577c | ||
|
085002a35a
|
|||
|
c3dcca6bde
|
|||
|
4e43e25437
|
|||
|
9d5194bf1d
|
|||
|
|
f5250f401f | ||
|
30e395d649
|
|||
|
597c0f196a
|
|||
|
33c14d3731
|
|||
|
961f91eef8
|
|||
|
|
bb51dcb69b | ||
|
82c6398dab
|
|||
|
|
b0e44b6690 | ||
|
0aae73f530
|
|||
|
de63e94342
|
|||
|
b45efc91cb
|
|||
|
fbd2f7a4dd
|
|||
|
7f4a08c673
|
|||
|
|
34d9986979 | ||
|
21e810b0da
|
|||
|
|
452810f9fa | ||
|
|
bfcd207c38 | ||
|
3f958cf20a
|
|||
|
faecdf00be
|
|||
|
de9aa383ea
|
|||
|
2fad96bb2c
|
|||
|
|
aec723d243 | ||
|
28c8f5850e
|
|||
|
5e065c04c7
|
|||
|
4ef7333ef5
|
|||
|
dddbd97362
|
|||
|
|
46075fa356 | ||
|
8553bf7941
|
|||
|
1311cb20b8
|
|||
|
|
87e9d66f46 | ||
|
9edea7082d
|
|||
|
0a4d725669
|
|||
| 9e9941cd70 | |||
| 0ce277fe33 | |||
|
|
4573f4c2a6 | ||
|
|
9c5947a18f | ||
|
337d27abbf
|
|||
|
4e8a8a958d
|
|||
|
a9871e6038
|
|||
| 0a37a583f7 | |||
| 9da9477763 | |||
|
36286be458
|
|||
|
ca3ffa4ba7
|
|||
|
|
82d7ab94b4 | ||
|
7487a0edf6
|
|||
|
6b3b702ffe
|
|||
|
|
1cc776bfaa | ||
|
|
1e50a40868 | ||
|
|
fe4f6a7f46 | ||
|
|
7f69e6f444 | ||
|
|
e589f36c22 | ||
|
|
33e15bca27 | ||
|
8d7420358d
|
|||
|
|
522a1cfa23 |
6
.idea/copilot.data.migration.agent.xml
generated
Normal 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
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AskMigrationStateService">
|
||||
<option name="migrationStatus" value="COMPLETED" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/copilot.data.migration.ask2agent.xml
generated
Normal 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
@@ -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
@@ -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
@@ -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"]
|
||||
35
build.sbt
@@ -1,35 +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"
|
||||
|
||||
Test / testOptions += Tests.Filter(_.equals("de.knockoutwhist.TestSequence"))
|
||||
|
||||
coverageEnabled := true
|
||||
coverageFailOnMinimum := true
|
||||
coverageMinimumStmtTotal := 85
|
||||
coverageMinimumBranchTotal := 100
|
||||
@@ -1 +0,0 @@
|
||||
sbt.version = 1.10.2
|
||||
@@ -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")
|
||||
BIN
src/main/resources/KnockOutLogo.png
Normal file
|
After Width: | Height: | Size: 683 KiB |
BIN
src/main/resources/background.png
Normal file
|
After Width: | Height: | Size: 610 KiB |
BIN
src/main/resources/cards/1B.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
src/main/resources/cards/1J.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src/main/resources/cards/2B.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
src/main/resources/cards/2C.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
src/main/resources/cards/2D.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
src/main/resources/cards/2H.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
src/main/resources/cards/2J.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src/main/resources/cards/2S.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
src/main/resources/cards/3C.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src/main/resources/cards/3D.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
src/main/resources/cards/3H.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
src/main/resources/cards/3S.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
src/main/resources/cards/4C.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
src/main/resources/cards/4D.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
src/main/resources/cards/4H.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
src/main/resources/cards/4S.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
src/main/resources/cards/5C.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/main/resources/cards/5D.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
src/main/resources/cards/5H.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
src/main/resources/cards/5S.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src/main/resources/cards/6C.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/main/resources/cards/6D.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
src/main/resources/cards/6H.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
src/main/resources/cards/6S.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/main/resources/cards/7C.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src/main/resources/cards/7D.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
src/main/resources/cards/7H.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
src/main/resources/cards/7S.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/main/resources/cards/8C.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/main/resources/cards/8D.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/main/resources/cards/8H.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/main/resources/cards/8S.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src/main/resources/cards/9C.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src/main/resources/cards/9D.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/main/resources/cards/9H.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/main/resources/cards/9S.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/main/resources/cards/AC.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
src/main/resources/cards/ACB.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
src/main/resources/cards/AD.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
src/main/resources/cards/ADB.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src/main/resources/cards/AH.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
src/main/resources/cards/AHB.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src/main/resources/cards/AS.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/main/resources/cards/ASB.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/main/resources/cards/JC.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
src/main/resources/cards/JD.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
src/main/resources/cards/JH.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
src/main/resources/cards/JS.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
src/main/resources/cards/KC.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
src/main/resources/cards/KD.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
src/main/resources/cards/KH.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
src/main/resources/cards/KS.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
src/main/resources/cards/QC.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
src/main/resources/cards/QD.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
src/main/resources/cards/QH.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
src/main/resources/cards/QS.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
src/main/resources/cards/TC.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src/main/resources/cards/TD.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/main/resources/cards/TH.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/main/resources/cards/TS.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src/main/resources/checkmark.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
src/main/resources/poker.zip
Normal file
BIN
src/main/resources/return-icon.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
@@ -1,23 +1,45 @@
|
||||
package de.knockoutwhist
|
||||
|
||||
|
||||
import de.knockoutwhist.control.MatchControl
|
||||
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 = DEBUG_MODE_VAR
|
||||
def debugmode: Boolean = DEBUG_MODE_VAR
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
if(!TUIMain.initial) throw new IllegalStateException("Game could not be started.")
|
||||
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 _ =>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
9
src/main/scala/de/knockoutwhist/Worksheet.sc
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,25 @@
|
||||
package de.knockoutwhist.cards
|
||||
|
||||
import de.knockoutwhist.KnockOutWhist
|
||||
trait CardManager {
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import scala.util.Random
|
||||
def cardContainer: List[Card]
|
||||
|
||||
object CardManager {
|
||||
def shuffleAndReset(): Unit
|
||||
|
||||
def resetOrder(): Unit
|
||||
|
||||
var cardContainer: List[Card] = {
|
||||
val cc = ListBuffer[Card]()
|
||||
for (suit <- Suit.values) {
|
||||
for (cardValue <- CardValue.values) {
|
||||
cc += Card(cardValue, suit)
|
||||
}
|
||||
}
|
||||
cc.toList
|
||||
}
|
||||
private var currentIdx = 0
|
||||
|
||||
def shuffleAndReset(): Unit = {
|
||||
cardContainer = Random.shuffle(cardContainer)
|
||||
currentIdx = 0
|
||||
}
|
||||
def nextCard(): Card
|
||||
|
||||
def resetOrder(): Unit = {
|
||||
cardContainer = cardContainer.sortBy(c => (c.suit.ordinal, c.cardValue.ordinal))
|
||||
currentIdx = 0
|
||||
}
|
||||
def remainingCards: Int = cardContainer.size - currentIndx
|
||||
|
||||
def nextCard(): Card = {
|
||||
val card = cardContainer(currentIdx)
|
||||
if (currentIdx + 1 > 51) {
|
||||
throw new IndexOutOfBoundsException("Trying to access card 53(out of bounds)")
|
||||
} else {
|
||||
currentIdx += 1
|
||||
card
|
||||
}
|
||||
}
|
||||
def removeCards(amount: Int): List[Card]
|
||||
|
||||
def createHand(amount: Int = 7): Hand = {
|
||||
val hand = ListBuffer[Card]()
|
||||
for (_ <- 1 to amount) {
|
||||
hand += nextCard()
|
||||
}
|
||||
Hand(hand.toList)
|
||||
}
|
||||
def createHand(amount: Int = 7): Hand
|
||||
|
||||
def grabSpecificCard(card: Card): Card
|
||||
|
||||
def currentIndx: Int
|
||||
|
||||
def setState(cc: List[Card], currentIndex: Int): Unit
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package de.knockoutwhist.cards
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
case class Hand(cards: List[Card]) {
|
||||
|
||||
def removeCard(card: Card): Hand = {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package de.knockoutwhist.cards.base
|
||||
|
||||
import de.knockoutwhist.cards.*
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import scala.util.Random
|
||||
|
||||
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] = {
|
||||
val cc = ListBuffer[Card]()
|
||||
for (suit <- Suit.values) {
|
||||
for (cardValue <- CardValue.values) {
|
||||
cc += Card(cardValue, suit)
|
||||
}
|
||||
}
|
||||
cc.toList
|
||||
}
|
||||
private var currentIdx = 0
|
||||
|
||||
override def currentIndx: Int = currentIdx
|
||||
|
||||
override def shuffleAndReset(): Unit = {
|
||||
cc = Random.shuffle(cc)
|
||||
currentIdx = 0
|
||||
}
|
||||
|
||||
override def resetOrder(): Unit = {
|
||||
cc = cc.sortBy(c => (c.suit.ordinal, c.cardValue.ordinal))
|
||||
currentIdx = 0
|
||||
}
|
||||
|
||||
override def nextCard(): Card = {
|
||||
if (currentIdx > 51)
|
||||
throw new IndexOutOfBoundsException("Trying to access card 53(out of bounds)")
|
||||
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 = {
|
||||
val hand = ListBuffer[Card]()
|
||||
for (_ <- 1 to amount) {
|
||||
hand += nextCard()
|
||||
}
|
||||
Hand(hand.toList)
|
||||
}
|
||||
|
||||
override def grabSpecificCard(card: Card): Card = {
|
||||
cc.filter(c => c.suit == card.suit && c.cardValue == card.cardValue).head
|
||||
}
|
||||
}
|
||||
@@ -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 = {}
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package de.knockoutwhist.control
|
||||
|
||||
import de.knockoutwhist.ui.tui.TUIMain
|
||||
import de.knockoutwhist.utils.DelayHandler
|
||||
import de.knockoutwhist.utils.events.EventHandler
|
||||
|
||||
object ControlHandler extends EventHandler {
|
||||
|
||||
addListener(TUIMain)
|
||||
addListener(DelayHandler)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.knockoutwhist.control
|
||||
|
||||
object ControlThread {
|
||||
|
||||
def runLater[R](op: => R): Unit = {
|
||||
op
|
||||
}
|
||||
|
||||
}
|
||||
41
src/main/scala/de/knockoutwhist/control/GameLogic.scala
Normal file
@@ -0,0 +1,41 @@
|
||||
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 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]]
|
||||
|
||||
}
|
||||
12
src/main/scala/de/knockoutwhist/control/GameState.scala
Normal file
@@ -0,0 +1,12 @@
|
||||
package de.knockoutwhist.control
|
||||
|
||||
enum GameState {
|
||||
|
||||
case MainMenu
|
||||
case Lobby
|
||||
case InGame
|
||||
case SelectTrump
|
||||
case TieBreak
|
||||
case FinishedMatch
|
||||
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package de.knockoutwhist.control
|
||||
|
||||
import de.knockoutwhist.control.RoundControl.controlRound
|
||||
import de.knockoutwhist.events.*
|
||||
import de.knockoutwhist.events.ERROR_STATUS.{IDENTICAL_NAMES, INVALID_NAME_FORMAT, INVALID_NUMBER_OF_PLAYERS}
|
||||
import de.knockoutwhist.events.GLOBAL_STATUS.*
|
||||
import de.knockoutwhist.player.Player
|
||||
import de.knockoutwhist.rounds.Match
|
||||
import de.knockoutwhist.utils.CustomPlayerQueue
|
||||
|
||||
import scala.compiletime.uninitialized
|
||||
import scala.io.StdIn
|
||||
import scala.util.Random
|
||||
|
||||
object MatchControl {
|
||||
|
||||
private[control] var playerQueue: CustomPlayerQueue[Player] = uninitialized
|
||||
|
||||
def startMatch(): Player = {
|
||||
ControlHandler.invoke(ShowGlobalStatus(SHOW_START_MATCH))
|
||||
val players = enterPlayers()
|
||||
playerQueue = CustomPlayerQueue[Player](players, Random.nextInt(players.length))
|
||||
controlMatch()
|
||||
}
|
||||
|
||||
def enterPlayers(): Array[Player] = {
|
||||
ControlHandler.invoke(ShowGlobalStatus(SHOW_TYPE_PLAYERS))
|
||||
val names = StdIn.readLine().split(",")
|
||||
if (names.length < 2) {
|
||||
ControlHandler.invoke(ShowErrorStatus(INVALID_NUMBER_OF_PLAYERS))
|
||||
return enterPlayers()
|
||||
}
|
||||
if (names.distinct.length != names.length) {
|
||||
ControlHandler.invoke(ShowErrorStatus(IDENTICAL_NAMES))
|
||||
return enterPlayers()
|
||||
}
|
||||
if (names.count(_.trim.isBlank) > 0 || names.count(_.trim.length <= 2) > 0 || names.count(_.trim.length > 10) > 0) {
|
||||
ControlHandler.invoke(ShowErrorStatus(INVALID_NAME_FORMAT))
|
||||
return enterPlayers()
|
||||
}
|
||||
names.map(s => Player(s))
|
||||
}
|
||||
|
||||
def controlMatch(): Player = {
|
||||
val matchImpl = Match(playerQueue.toList)
|
||||
while (!isOver(matchImpl)) {
|
||||
controlRound(matchImpl)
|
||||
}
|
||||
val winner = finalizeMatch(matchImpl)
|
||||
ControlHandler.invoke(ShowGlobalStatus(SHOW_FINISHED_MATCH, winner))
|
||||
winner
|
||||
}
|
||||
|
||||
def isOver(matchImpl: Match): Boolean = {
|
||||
if (matchImpl.roundlist.isEmpty) {
|
||||
false
|
||||
} else {
|
||||
RoundControl.remainingPlayers(matchImpl.roundlist.last).size == 1
|
||||
}
|
||||
}
|
||||
|
||||
def finalizeMatch(matchImpl: Match): Player = {
|
||||
if (!isOver(matchImpl)) {
|
||||
throw new IllegalStateException("Match is not over yet.")
|
||||
}
|
||||
RoundControl.remainingPlayers(matchImpl.roundlist.last).head
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package de.knockoutwhist.control
|
||||
|
||||
import de.knockoutwhist.KnockOutWhist
|
||||
import de.knockoutwhist.cards.{Card, CardManager, Suit}
|
||||
import de.knockoutwhist.events.ERROR_STATUS.{INVALID_INPUT, 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.*
|
||||
import de.knockoutwhist.events.cards.{RenderHandEvent, ShowTieCardsEvent}
|
||||
import de.knockoutwhist.events.directional.{RequestCardEvent, RequestDogPlayCardEvent, RequestNumberEvent, RequestPickTrumpsuitEvent}
|
||||
import de.knockoutwhist.events.util.DelayEvent
|
||||
import de.knockoutwhist.events.{ShowErrorStatus, ShowGlobalStatus, ShowPlayerStatus}
|
||||
import de.knockoutwhist.player.Player
|
||||
import de.knockoutwhist.rounds.Round
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.mutable
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
object PlayerControl {
|
||||
|
||||
@tailrec
|
||||
def playCard(player: Player): Card = {
|
||||
ControlHandler.invoke(ShowPlayerStatus(SHOW_TURN, player))
|
||||
ControlHandler.invoke(DelayEvent(3000L))
|
||||
ControlHandler.invoke(ShowPlayerStatus(SHOW_PLAY_CARD, player))
|
||||
ControlHandler.invoke(RenderHandEvent(player.currentHand().get, true))
|
||||
ControlHandler.invoke(RequestCardEvent(player.currentHand().get)) match {
|
||||
case Success(value) =>
|
||||
value
|
||||
case Failure(exception) =>
|
||||
ControlHandler.invoke(ShowErrorStatus(INVALID_NUMBER))
|
||||
playCard(player)
|
||||
}
|
||||
}
|
||||
|
||||
@tailrec
|
||||
def dogplayCard(player: Player, round: Round): Option[Card] = {
|
||||
ControlHandler.invoke(ShowPlayerStatus(SHOW_TURN, player))
|
||||
ControlHandler.invoke(DelayEvent(3000L))
|
||||
ControlHandler.invoke(ShowPlayerStatus(SHOW_DOG_PLAY_CARD, player, RoundControl.dogNeedsToPlay(round)))
|
||||
ControlHandler.invoke(RenderHandEvent(player.currentHand().get, false))
|
||||
ControlHandler.invoke(RequestDogPlayCardEvent(player.currentHand().get, RoundControl.dogNeedsToPlay(round))) match {
|
||||
case Success(value) =>
|
||||
value
|
||||
case Failure(exception) =>
|
||||
ControlHandler.invoke(ShowErrorStatus(INVALID_INPUT))
|
||||
dogplayCard(player, round)
|
||||
}
|
||||
}
|
||||
|
||||
def determineWinnerTie(players: List[Player]): Player = {
|
||||
determineWinnerTie(players, true)
|
||||
}
|
||||
|
||||
private def determineWinnerTie(players: List[Player], tieMessage: Boolean): Player = {
|
||||
if (!KnockOutWhist.debugmode) CardManager.shuffleAndReset()
|
||||
if (tieMessage) ControlHandler.invoke(ShowGlobalStatus(SHOW_TIE))
|
||||
var currentStep = 0
|
||||
var remaining = CardManager.cardContainer.size - (players.length - 1)
|
||||
val cut: mutable.HashMap[Player, Card] = mutable.HashMap()
|
||||
for (player <- players) {
|
||||
var selCard: Card = null
|
||||
while (selCard == null) {
|
||||
ControlHandler.invoke(ShowPlayerStatus(SHOW_TIE_NUMBERS, player, remaining))
|
||||
ControlHandler.invoke(RequestNumberEvent(1, remaining)) match {
|
||||
case Success(value) =>
|
||||
selCard = CardManager.cardContainer(currentStep + (value-1))
|
||||
cut.put(player, selCard)
|
||||
currentStep += value
|
||||
remaining -= (value-1)
|
||||
case Failure(exception) =>
|
||||
ControlHandler.invoke(ShowErrorStatus(NOT_A_NUMBER))
|
||||
}
|
||||
}
|
||||
}
|
||||
ControlHandler.invoke(ShowTieCardsEvent(cut.toList))
|
||||
evaluateTieWinner(cut)
|
||||
}
|
||||
|
||||
private def evaluateTieWinner(cut: mutable.HashMap[Player, Card]): Player = {
|
||||
val winner: ListBuffer[Player] = 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))
|
||||
return winner.head
|
||||
}
|
||||
ControlHandler.invoke(ShowGlobalStatus(SHOW_TIE_TIE))
|
||||
determineWinnerTie(winner.toList, false)
|
||||
}
|
||||
|
||||
@tailrec
|
||||
def pickNextTrumpsuit(player: Player): Suit = {
|
||||
ControlHandler.invoke(ShowPlayerStatus(SHOW_TRUMPSUIT_OPTIONS, player))
|
||||
ControlHandler.invoke(RenderHandEvent(player.currentHand().get, false))
|
||||
ControlHandler.invoke(RequestPickTrumpsuitEvent()) match {
|
||||
case Success(value) =>
|
||||
value
|
||||
case Failure(exception) =>
|
||||
ControlHandler.invoke(ShowErrorStatus(INVALID_NUMBER))
|
||||
pickNextTrumpsuit(player)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package de.knockoutwhist.control
|
||||
|
||||
import de.knockoutwhist.KnockOutWhist
|
||||
import de.knockoutwhist.cards.CardManager
|
||||
import de.knockoutwhist.control.MatchControl.playerQueue
|
||||
import de.knockoutwhist.events.ROUND_STATUS.{PLAYERS_OUT, SHOW_START_ROUND, WON_ROUND}
|
||||
import de.knockoutwhist.events.ShowRoundStatus
|
||||
import de.knockoutwhist.events.util.DelayEvent
|
||||
import de.knockoutwhist.player.Player
|
||||
import de.knockoutwhist.rounds.{Match, Round}
|
||||
import de.knockoutwhist.utils.Implicits.*
|
||||
|
||||
object RoundControl {
|
||||
|
||||
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): (Player, Round) = {
|
||||
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(_))
|
||||
|
||||
if (playersOut.nonEmpty && !matchImpl.dogLife) {
|
||||
matchImpl.dogLife = true
|
||||
playersOut.foreach(p => p.doglife = true)
|
||||
playersOut = List()
|
||||
}
|
||||
|
||||
tricksMapped.keys.foreach(p => {
|
||||
p.doglife = false
|
||||
})
|
||||
|
||||
val winner = (winners.size == 1)
|
||||
? winners.head
|
||||
|: PlayerControl.determineWinnerTie(winners.toList)
|
||||
|
||||
val finalRound = Round(round.trumpSuit, matchImpl, round.tricklist, round.playersin, playersOut, winner, round.firstRound)
|
||||
matchImpl.roundlist += finalRound
|
||||
(winner, finalRound)
|
||||
}
|
||||
|
||||
def remainingPlayers(round: Round): List[Player] = {
|
||||
if (round.playersout == null) {
|
||||
return round.playersin
|
||||
}
|
||||
round.playersin.filter(!round.playersout.contains(_))
|
||||
}
|
||||
|
||||
def createround(matchImpl: Match): Round = {
|
||||
val remainingPlayer = matchImpl.roundlist.isEmpty ? matchImpl.totalplayers |: RoundControl.remainingPlayers(matchImpl.roundlist.last)
|
||||
provideCards(matchImpl, remainingPlayer)
|
||||
if (matchImpl.roundlist.isEmpty) {
|
||||
val randomTrumpsuit = CardManager.nextCard().suit
|
||||
matchImpl.current_round = Some(new Round(randomTrumpsuit, matchImpl, remainingPlayer, true))
|
||||
} else {
|
||||
val winner = matchImpl.roundlist.last.winner
|
||||
val trumpsuit = PlayerControl.pickNextTrumpsuit(winner)
|
||||
|
||||
matchImpl.current_round = Some(new Round(trumpsuit, matchImpl, remainingPlayer, false))
|
||||
}
|
||||
matchImpl.numberofcards -= 1
|
||||
matchImpl.current_round.get
|
||||
}
|
||||
|
||||
def nextRound(matchImpl: Match): Round = {
|
||||
if (MatchControl.isOver(matchImpl)) {
|
||||
return null
|
||||
}
|
||||
createround(matchImpl)
|
||||
}
|
||||
|
||||
|
||||
def controlRound(matchImpl: Match): Round = {
|
||||
val roundImpl = nextRound(matchImpl)
|
||||
ControlHandler.invoke(ShowRoundStatus(SHOW_START_ROUND, roundImpl))
|
||||
while (!RoundControl.isOver(roundImpl)) {
|
||||
TrickControl.controlTrick(roundImpl)
|
||||
}
|
||||
val (roundWinner, finalRound) = RoundControl.finalizeRound(roundImpl, matchImpl)
|
||||
ControlHandler.invoke(ShowRoundStatus(WON_ROUND, finalRound, roundWinner))
|
||||
ControlHandler.invoke(DelayEvent(5000L))
|
||||
if (finalRound.playersout.nonEmpty) {
|
||||
ControlHandler.invoke(ShowRoundStatus(PLAYERS_OUT, finalRound))
|
||||
finalRound.playersout.foreach(p => {
|
||||
playerQueue.remove(p)
|
||||
})
|
||||
}
|
||||
playerQueue.resetAndSetStart(roundWinner)
|
||||
finalRound
|
||||
}
|
||||
|
||||
|
||||
private def provideCards(matchImpl: Match, players: List[Player]): Int = {
|
||||
if (!KnockOutWhist.debugmode) CardManager.shuffleAndReset()
|
||||
var hands = 0
|
||||
for (player <- players) {
|
||||
if (!player.doglife) {
|
||||
player.provideHand(CardManager.createHand(matchImpl.numberofcards))
|
||||
} else {
|
||||
player.provideHand(CardManager.createHand(1))
|
||||
}
|
||||
hands += 1
|
||||
}
|
||||
hands
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package de.knockoutwhist.control
|
||||
|
||||
trait SnapshottingGameLogic {
|
||||
|
||||
def createSnapshot(): LogicSnapshot[this.type]
|
||||
|
||||
}
|
||||
|
||||
trait LogicSnapshot[T <: SnapshottingGameLogic] {
|
||||
|
||||
def restore(logic: T): Unit
|
||||
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package de.knockoutwhist.control
|
||||
|
||||
import de.knockoutwhist.cards.Card
|
||||
import de.knockoutwhist.control.MatchControl.playerQueue
|
||||
import de.knockoutwhist.events.ERROR_STATUS.WRONG_CARD
|
||||
import de.knockoutwhist.events.PLAYER_STATUS.{SHOW_NOT_PLAYED, SHOW_WON_PLAYER_TRICK}
|
||||
import de.knockoutwhist.events.round.ShowCurrentTrickEvent
|
||||
import de.knockoutwhist.events.util.DelayEvent
|
||||
import de.knockoutwhist.events.{ShowErrorStatus, ShowPlayerStatus}
|
||||
import de.knockoutwhist.player.Player
|
||||
import de.knockoutwhist.rounds.{Round, Trick}
|
||||
|
||||
object TrickControl {
|
||||
|
||||
def playCard(trick: Trick, round: Round, card: Card, player: Player): Boolean = {
|
||||
if (trick.finished) {
|
||||
throw new IllegalStateException("This trick is already finished")
|
||||
} else {
|
||||
if (trick.getfirstcard().isEmpty) {
|
||||
trick.setfirstcard(card)
|
||||
trick.cards += (card -> player)
|
||||
true
|
||||
} else if ((card.suit == trick.getfirstcard().getOrElse(card).suit) || (card.suit == round.trumpSuit)) { // Wert aus Option extrahieren
|
||||
trick.cards += (card -> player)
|
||||
true
|
||||
} else {
|
||||
trick.cards += (card -> player)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def wonTrick(trick: Trick, round: Round): (Player, 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.getfirstcard().get.suit).maxBy(_.cardValue.ordinal) //stream
|
||||
}
|
||||
}
|
||||
val winningPlayer = trick.cards(winningCard)
|
||||
val finalTrick = Trick(round, trick.cards, winningPlayer, true)
|
||||
round.tricklist += finalTrick
|
||||
(winningPlayer, finalTrick)
|
||||
}
|
||||
|
||||
def createtrick(round: Round): Trick = {
|
||||
val trick = new Trick(round)
|
||||
round.setcurrenttrick(trick)
|
||||
trick
|
||||
}
|
||||
|
||||
def controlTrick(round: Round): Trick = {
|
||||
val trick = nextTrick(round)
|
||||
for (player <- playerQueue) {
|
||||
ControlHandler.invoke(ShowCurrentTrickEvent(round, trick))
|
||||
if (!player.doglife) {
|
||||
val rightCard = controlSuitplayed(trick, player)
|
||||
player.removeCard(rightCard)
|
||||
TrickControl.playCard(trick, round, rightCard, player)
|
||||
} else if (player.currentHand().exists(_.cards.nonEmpty)) {
|
||||
val card = PlayerControl.dogplayCard(player, round)
|
||||
if (card.isEmpty) {
|
||||
ControlHandler.invoke(ShowPlayerStatus(SHOW_NOT_PLAYED, player))
|
||||
} else {
|
||||
player.removeCard(card.get)
|
||||
TrickControl.playCard(trick, round, card.get, player)
|
||||
}
|
||||
}
|
||||
}
|
||||
val (winner, finalTrick) = TrickControl.wonTrick(trick, round)
|
||||
ControlHandler.invoke(ShowCurrentTrickEvent(round, finalTrick))
|
||||
ControlHandler.invoke(ShowPlayerStatus(SHOW_WON_PLAYER_TRICK, winner))
|
||||
playerQueue.resetAndSetStart(winner)
|
||||
ControlHandler.invoke(DelayEvent(3000L))
|
||||
finalTrick
|
||||
}
|
||||
|
||||
def nextTrick(roundImpl: Round): Trick = {
|
||||
if (RoundControl.isOver(roundImpl)) {
|
||||
return null
|
||||
}
|
||||
createtrick(roundImpl)
|
||||
}
|
||||
|
||||
private[control] def controlSuitplayed(trick: Trick, player: Player): Card = {
|
||||
var card = PlayerControl.playCard(player)
|
||||
if (trick.getfirstcard().isDefined) {
|
||||
val firstCard = trick.getfirstcard().get
|
||||
while (firstCard.suit != card.suit) {
|
||||
var hasSuit = false
|
||||
for (cardInHand <- player.currentHand().get.cards) {
|
||||
if (cardInHand.suit == firstCard.suit) {
|
||||
hasSuit = true
|
||||
}
|
||||
}
|
||||
if (!hasSuit) {
|
||||
return card
|
||||
} else {
|
||||
ControlHandler.invoke(ShowErrorStatus(WRONG_CARD, firstCard))
|
||||
card = PlayerControl.playCard(player)
|
||||
}
|
||||
}
|
||||
}
|
||||
card
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
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
|
||||
invoke(GameStateChangeEvent(state, Lobby))
|
||||
state = 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 (state != InGame)
|
||||
invoke(GameStateChangeEvent(state, InGame))
|
||||
|
||||
if (matchImpl.isOver) {
|
||||
//Winner is the last person in the playersIn list
|
||||
val winner = matchImpl.playersIn.head
|
||||
|
||||
invoke(GameStateChangeEvent(state, FinishedMatch))
|
||||
state = FinishedMatch
|
||||
invoke(MatchEndEvent(winner))
|
||||
} else {
|
||||
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())
|
||||
invoke(DelayEvent(500))
|
||||
|
||||
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)
|
||||
))
|
||||
|
||||
invoke(GameStateChangeEvent(state, SelectTrump))
|
||||
state = SelectTrump
|
||||
|
||||
invoke(TrumpSelectEvent(lastWinner.get))
|
||||
|
||||
playerInputLogic.requestTrumpSuit(lastWinner.get)
|
||||
}
|
||||
|
||||
override def controlRound(): Unit = {
|
||||
if (state != InGame)
|
||||
invoke(GameStateChangeEvent(state, InGame))
|
||||
state = 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) {
|
||||
invoke(GameStateChangeEvent(state, TieBreak))
|
||||
state = TieBreak
|
||||
|
||||
invoke(TieEvent(roundResult.winners))
|
||||
invoke(DelayEvent(2000))
|
||||
|
||||
playerTieLogic.handleTie(roundResult)
|
||||
return
|
||||
}
|
||||
val newMatch = endRound(roundResult.winners.head, roundResult)
|
||||
currentMatch = Some(newMatch)
|
||||
controlMatch()
|
||||
} else {
|
||||
|
||||
invoke(NewTrickEvent())
|
||||
invoke(DelayEvent(1000))
|
||||
|
||||
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))
|
||||
invoke(DelayEvent(2000))
|
||||
|
||||
if (roundResult.notTricked.nonEmpty && !resultingRound.firstRound) {
|
||||
// When the number of cards is less than 2, dog life ends automatically
|
||||
val cantDogLife = (matchImpl.numberofcards - 1) < 2
|
||||
|
||||
if (matchImpl.dogLife && !cantDogLife) {
|
||||
invoke(ShowPlayersOutEvent(roundResult.notTricked))
|
||||
invoke(DelayEvent(2000))
|
||||
matchImpl = matchImpl.updatePlayersIn(matchImpl.playersIn.filterNot(roundResult.notTricked.contains(_)))
|
||||
} else {
|
||||
invoke(ShowDogsEvent(roundResult.notTricked))
|
||||
invoke(DelayEvent(2000))
|
||||
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 {
|
||||
invoke(DelayEvent(2000))
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 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())
|
||||
invoke(GameStateChangeEvent(state, MainMenu))
|
||||
state = 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
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())
|
||||
} else {
|
||||
// All players have selected their tie-breaker cards
|
||||
// Find the highest card among selected cards
|
||||
|
||||
gameLogic.invoke(TieAllPlayersSelectedEvent())
|
||||
gameLogic.invoke(DelayEvent(2000))
|
||||
gameLogic.invoke(TieShowPlayerCardsEvent())
|
||||
|
||||
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(): AbstractPlayer = {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
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}
|
||||
|
||||
class BaseUndoManager(gameLogic: BaseGameLogic) extends UndoManager {
|
||||
|
||||
private var undoStack: List[Command] = Nil
|
||||
private var redoStack: List[Command] = Nil
|
||||
|
||||
override def doStep(command: Command): Unit = {
|
||||
redoStack = Nil
|
||||
undoStack = command :: undoStack
|
||||
command.doStep(gameLogic)
|
||||
}
|
||||
|
||||
override def undoStep(): Unit = {
|
||||
ControlThread.runLater {
|
||||
undoStack match {
|
||||
case Nil => false
|
||||
case head :: stack =>
|
||||
undoStack = stack
|
||||
redoStack = head :: redoStack
|
||||
try {
|
||||
head.undoStep(gameLogic)
|
||||
} catch {
|
||||
case _: UndoneException =>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def redoStep(): Unit = {
|
||||
ControlThread.runLater {
|
||||
redoStack match {
|
||||
case Nil => false
|
||||
case head :: stack =>
|
||||
redoStack = stack
|
||||
undoStack = head :: undoStack
|
||||
try {
|
||||
head.doStep(gameLogic)
|
||||
} catch {
|
||||
case _: UndoneException =>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||