diff --git a/.gitignore b/.gitignore
index 914a3ab..551b40f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -120,3 +120,7 @@ hs_err_pid*
/.idea/scala_compiler.xml
/.idea/scala_settings.xml
/.idea/vcs.xml
+/.idea/misc.xml
+/.idea/sbt.xml
+/.idea/.name
+/.idea/codeStyles/**
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 1b2d693..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/sbt.xml b/.idea/sbt.xml
deleted file mode 100644
index 2ad1143..0000000
--- a/.idea/sbt.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/build.sbt b/build.sbt
index 9f58222..d0ba4c0 100644
--- a/build.sbt
+++ b/build.sbt
@@ -22,6 +22,8 @@ lazy val root = (project in file("."))
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
diff --git a/src/main/scala/de/knockoutwhist/KnockOutWhist.scala b/src/main/scala/de/knockoutwhist/KnockOutWhist.scala
index c0c9303..f6c9a0d 100644
--- a/src/main/scala/de/knockoutwhist/KnockOutWhist.scala
+++ b/src/main/scala/de/knockoutwhist/KnockOutWhist.scala
@@ -1,20 +1,24 @@
package de.knockoutwhist
-import de.knockoutwhist.cards.CardManager
+
+import de.knockoutwhist.control.MatchControl
+import de.knockoutwhist.control.text.TextMatchControl
object KnockOutWhist {
- class KnockOutWhist {
+ val matchControl: MatchControl = TextMatchControl
+ /*
+ Debug mode:
+
+ - Disables the random shuffle of the cards
+ */
+ private[knockoutwhist] var DEBUG_MODE_VAR: Boolean = true
+
+ def DEBUG_MODE = DEBUG_MODE_VAR
- }
def main(args: Array[String]): Unit = {
- CardManager.shuffleAndReset()
- val hand1 = CardManager.createHand()
- val handtoString = hand1.renderAsString()
- handtoString.foreach(println)
-
-
-
+ if(!matchControl.initial()) throw new IllegalStateException("Game could not be started.")
}
+
}
\ No newline at end of file
diff --git a/src/main/scala/de/knockoutwhist/cards/Card.scala b/src/main/scala/de/knockoutwhist/cards/Card.scala
index 5f418de..21886ca 100644
--- a/src/main/scala/de/knockoutwhist/cards/Card.scala
+++ b/src/main/scala/de/knockoutwhist/cards/Card.scala
@@ -1,10 +1,12 @@
package de.knockoutwhist.cards
import de.knockoutwhist.cards.CardValue.Ten
+import de.knockoutwhist.cards.Suit
enum Suit(identifier: String):
def cardType(): String = identifier
+
case Spades extends Suit("♠")
case Hearts extends Suit("♥")
@@ -32,29 +34,34 @@ enum CardValue(identifier: String):
end CardValue
case class Card(cardValue: CardValue, suit: Suit) {
-
+
+ def cardColour(suit: Suit): String = suit match {
+ case Suit.Hearts | Suit.Diamonds => Console.RED
+ case Suit.Clubs | Suit.Spades => Console.BLACK
+ }
+
def renderAsString(): Vector[String] = {
- if(cardValue == Ten) {
+ if (cardValue == Ten) {
return Vector(
s"┌─────────┐",
- s"│${cardValue.cardType()} │",
+ s"│${cardColour(suit)}${Console.BOLD}${cardValue.cardType()}${Console.RESET} │",
"│ │",
- s"│ ${suit.cardType()} │",
+ s"│ ${cardColour(suit)}${Console.BOLD}${suit.cardType()}${Console.RESET} │",
"│ │",
- s"│ ${cardValue.cardType()}│",
+ s"│ ${cardColour(suit)}${Console.BOLD}${cardValue.cardType()}${Console.RESET}│",
s"└─────────┘"
)
}
Vector(
s"┌─────────┐",
- s"│${cardValue.cardType()} │",
+ s"│${cardColour(suit)}${Console.BOLD}${cardValue.cardType()}${Console.RESET} │",
"│ │",
- s"│ ${suit.cardType()} │",
+ s"│ ${cardColour(suit)}${Console.BOLD}${suit.cardType()}${Console.RESET} │",
"│ │",
- s"│ ${cardValue.cardType()}│",
+ s"│ ${cardColour(suit)}${Console.BOLD}${cardValue.cardType()}${Console.RESET}│",
s"└─────────┘"
)
}
-
- override def toString: String = s"$cardValue of $suit" //Combined String
+ override def toString: String = s"$cardValue of $suit"
+ //Combined String
}
diff --git a/src/main/scala/de/knockoutwhist/cards/CardManager.scala b/src/main/scala/de/knockoutwhist/cards/CardManager.scala
index 57bf281..3449224 100644
--- a/src/main/scala/de/knockoutwhist/cards/CardManager.scala
+++ b/src/main/scala/de/knockoutwhist/cards/CardManager.scala
@@ -1,5 +1,7 @@
package de.knockoutwhist.cards
+import de.knockoutwhist.KnockOutWhist
+
import scala.collection.mutable.ListBuffer
import scala.util.Random
@@ -21,11 +23,20 @@ object CardManager {
cardContainer = Random.shuffle(cardContainer)
currentIdx = 0
}
+
+ def resetOrder(): Unit = {
+ cardContainer = cardContainer.sortBy(c => (c.suit.ordinal, c.cardValue.ordinal))
+ currentIdx = 0
+ }
def nextCard(): Card = {
val card = cardContainer(currentIdx)
- currentIdx += 1
- card
+ if (currentIdx + 1 > 51) {
+ throw new IndexOutOfBoundsException("Trying to access card 53(out of bounds)")
+ } else {
+ currentIdx += 1
+ card
+ }
}
def createHand(amount: Int = 7): Hand = {
diff --git a/src/main/scala/de/knockoutwhist/cards/Hand.scala b/src/main/scala/de/knockoutwhist/cards/Hand.scala
index 592d2c6..30d9766 100644
--- a/src/main/scala/de/knockoutwhist/cards/Hand.scala
+++ b/src/main/scala/de/knockoutwhist/cards/Hand.scala
@@ -1,10 +1,11 @@
package de.knockoutwhist.cards
+import scala.collection.mutable.ListBuffer
+
case class Hand(cards: List[Card]) {
def removeCard(card: Card): Hand = {
Hand(cards.filter(_ != card))
- //Hand(cards.filterNot(_ == card)) isch wurscht welches wir nehmen
}
def hasSuit(suit: Suit): Boolean = {
diff --git a/src/main/scala/de/knockoutwhist/cards/Player.scala b/src/main/scala/de/knockoutwhist/cards/Player.scala
deleted file mode 100644
index c008e8c..0000000
--- a/src/main/scala/de/knockoutwhist/cards/Player.scala
+++ /dev/null
@@ -1,14 +0,0 @@
-package de.knockoutwhist.cards
-
-case class Player(name: String) {
- private var hand: Option[Hand] = None
-
- def provideHand(hand: Hand): Boolean = {
- this.hand = Some(hand)
- true
- }
-
-
-
-
-}
diff --git a/src/main/scala/de/knockoutwhist/control/MatchControl.scala b/src/main/scala/de/knockoutwhist/control/MatchControl.scala
new file mode 100644
index 0000000..26ac3d6
--- /dev/null
+++ b/src/main/scala/de/knockoutwhist/control/MatchControl.scala
@@ -0,0 +1,24 @@
+package de.knockoutwhist.control
+
+import de.knockoutwhist.rounds.{Match, Round, Trick}
+
+trait MatchControl {
+
+ def initial(): Boolean
+ def start(): Unit
+ def playerControl: PlayerControl
+
+ /**
+ * Start the next round
+ * @return the next round or null if the match is over
+ */
+ def nextRound(matchImpl: Match): Round
+
+ /**
+ * Start the next trick
+ * @return the last trick or null if the round is over
+ */
+ def nextTrick(roundImpl: Round): Trick
+
+
+}
diff --git a/src/main/scala/de/knockoutwhist/control/PlayerControl.scala b/src/main/scala/de/knockoutwhist/control/PlayerControl.scala
new file mode 100644
index 0000000..e8dd7ff
--- /dev/null
+++ b/src/main/scala/de/knockoutwhist/control/PlayerControl.scala
@@ -0,0 +1,16 @@
+package de.knockoutwhist.control
+
+import de.knockoutwhist.cards.{Card, Suit}
+import de.knockoutwhist.player.Player
+import de.knockoutwhist.rounds.Round
+
+trait PlayerControl {
+
+ def playCard(player: Player): Card
+ def dogplayCard(player: Player, round: Round): Option[Card]
+ def determineWinnerTie(players: List[Player]): Player
+ def pickNextTrumpsuit(player: Player): Suit
+ def showCards(player: Player): Boolean
+ def showWon(player: Player, round: Round): String
+
+}
diff --git a/src/main/scala/de/knockoutwhist/control/text/TextMatchControl.scala b/src/main/scala/de/knockoutwhist/control/text/TextMatchControl.scala
new file mode 100644
index 0000000..b4dd259
--- /dev/null
+++ b/src/main/scala/de/knockoutwhist/control/text/TextMatchControl.scala
@@ -0,0 +1,205 @@
+package de.knockoutwhist.control.text
+
+import de.knockoutwhist.KnockOutWhist
+import de.knockoutwhist.cards.Card
+import de.knockoutwhist.control.{MatchControl, PlayerControl}
+import de.knockoutwhist.player.Player
+import de.knockoutwhist.rounds.{Match, Round, Trick}
+import de.knockoutwhist.utils.CustomPlayerQueue
+
+import scala.compiletime.uninitialized
+import scala.io.StdIn
+import scala.util.Random
+
+object TextMatchControl extends MatchControl {
+
+ private[control] var playerQueue: CustomPlayerQueue[Player] = uninitialized
+ private var init = false
+
+ override def initial(): Boolean = {
+ if(init) {
+ println("The game is already running.")
+ return false
+ }
+ init = true
+ println("Welcome to Knockout Whist!")
+ start()
+ true
+ }
+
+ override def start(): Unit = {
+ while(true) { //Main Gameplay Loop
+ val input = printMenu()
+ input match {
+ case "1" =>
+ startMatch()
+ case "2" =>
+ println("Exiting the game.")
+ return
+ case _ =>
+ println("Invalid input. Please try again.")
+ }
+ }
+ }
+
+ private[control] def startMatch(): Player = {
+ clearConsole()
+ println("Starting a new match.")
+ val players = enterPlayers()
+ playerQueue = CustomPlayerQueue[Player](players, Random.nextInt(players.length))
+ clearConsole()
+ controlMatch()
+ }
+
+ private[control] def enterPlayers(): Array[Player] = {
+ println("Please enter the names of the players, separated by a comma.")
+ val names = StdIn.readLine().split(",")
+ if(names.length < 2) {
+ println("Please enter at least two names.")
+ return enterPlayers()
+ }
+ if(names.distinct.length != names.length) {
+ println("Please enter unique names.")
+ return enterPlayers()
+ }
+ if(names.count(_.trim.isBlank) > 0 || names.count(_.trim.length <= 2) > 0 || names.count(_.trim.length > 10) > 0) {
+ println("Please enter valid names. Those can not be empty, shorter than 2 or longer then 10 characters.")
+ return enterPlayers()
+ }
+ names.map(s => Player(s))
+ }
+
+ private[control] def controlMatch(): Player = {
+ val matchImpl = Match(playerQueue.toList)
+ while (!matchImpl.isOver) {
+ val roundImpl = controlRound(matchImpl)
+ }
+ clearConsole()
+ println(s"The match is over. The winner is ${matchImpl.finalizeMatch().name}.")
+ matchImpl.finalizeMatch()
+ }
+
+ private[control] def controlRound(matchImpl: Match): Round = {
+ val roundImpl = nextRound(matchImpl)
+ clearConsole(10)
+ println(s"Starting a new round. The trump suit is ${roundImpl.trumpSuit}.")
+ clearConsole(2)
+ while (!roundImpl.isOver) {
+ controlTrick(roundImpl)
+ }
+ val (roundWinner, finalRound) = roundImpl.finalizeRound()
+ println(s"${roundWinner.name} won the round.")
+ if(!KnockOutWhist.DEBUG_MODE) Thread.sleep(5000L)
+ if(finalRound.players_out.nonEmpty) {
+ println("The following players are out of the game:")
+ finalRound.players_out.foreach(p => {
+ println(p.name)
+ playerQueue.remove(p)
+ })
+ }
+ playerQueue.resetAndSetStart(roundWinner)
+ finalRound
+ }
+
+ private[control] def controlTrick(round: Round): Trick = {
+ val trick = nextTrick(round)
+ for (player <- playerQueue) {
+ clearConsole()
+ println(printTrick(round))
+ if (!player.doglife) {
+ val rightCard = controlSuitplayed(trick, player)
+ player.removeCard(rightCard)
+ trick.playCard(rightCard, player)
+ } else if (player.currentHand().exists(_.cards.nonEmpty)) {
+ val card = playerControl.dogplayCard(player, round)
+ if (card.isEmpty) {
+ println(f"Player $player decided to not play his card")
+ } else {
+ player.removeCard(card.get)
+ trick.playCard(card.get, player)
+ }
+ }
+ }
+ val (winner, finalTrick) = trick.wonTrick()
+ clearConsole()
+ println(printTrick(round))
+ println(s"${winner.name} won the trick.")
+ clearConsole(2)
+ playerQueue.resetAndSetStart(winner)
+ if(!KnockOutWhist.DEBUG_MODE) Thread.sleep(3000L)
+ finalTrick
+ }
+ private[control] def controlSuitplayed(trick: Trick, player: Player): Card = {
+ var card = playerControl.playCard(player)
+ if (trick.get_first_card().isDefined) {
+ while (!(trick.get_first_card().get.suit == card.suit)) {
+ var hasSuit = false
+ for (cardInHand <- player.currentHand().get.cards) {
+ if (cardInHand.suit == trick.get_first_card().get.suit) {
+ hasSuit = true
+ }
+ }
+ if(!hasSuit) {
+ return card
+ }else {
+ println(f"You have to play a card of suit: ${trick.get_first_card().get.suit}\n")
+ card = playerControl.playCard(player)
+ }
+ }
+ }
+ card
+ }
+
+ private[control] def printMenu(): String = {
+ println("Please select an option:")
+ println("1. Start a new match")
+ println("2. Exit")
+ StdIn.readLine()
+ }
+
+ private[control] def printTrick(round: Round): String = {
+ val sb = new StringBuilder()
+ sb.append("Current Trick:\n")
+ sb.append("Trump-Suit: " + round.trumpSuit + "\n")
+ if(round.get_current_trick().get_first_card().isDefined) {
+ sb.append(s"Suit to play: ${round.get_current_trick().get_first_card().get.suit}\n")
+ }
+ for((card, player) <- round.get_current_trick().cards) {
+ sb.append(s"${player.name} played ${card.toString}\n")
+ }
+ sb.toString()
+ }
+
+ private def clearConsole(lines: Int = 32): Int = {
+ var l = 0
+ for(_ <- 0 until lines) {
+ println()
+ l += 1
+ }
+ l
+ }
+
+ override def playerControl: PlayerControl = {
+ TextPlayerControl
+ }
+
+ override def nextRound(matchImpl: Match): Round = {
+ if(matchImpl.isOver) {
+ println(s"The match is over. The winner is ${matchImpl.finalizeMatch().name}.")
+ return null
+ }
+ matchImpl.create_round()
+ }
+
+ override def nextTrick(roundImpl: Round): Trick = {
+ if(roundImpl.isOver) {
+ println("The round is over.")
+ return null
+ }
+ roundImpl.create_trick()
+ }
+
+
+
+}
+
diff --git a/src/main/scala/de/knockoutwhist/control/text/TextPlayerControl.scala b/src/main/scala/de/knockoutwhist/control/text/TextPlayerControl.scala
new file mode 100644
index 0000000..5dd584e
--- /dev/null
+++ b/src/main/scala/de/knockoutwhist/control/text/TextPlayerControl.scala
@@ -0,0 +1,194 @@
+package de.knockoutwhist.control.text
+
+import de.knockoutwhist.KnockOutWhist
+import de.knockoutwhist.cards.{Card, CardManager, Suit}
+import de.knockoutwhist.control.PlayerControl
+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.io.StdIn.readLine
+import scala.util.control.Breaks.*
+
+object TextPlayerControl extends PlayerControl {
+
+ override def playCard(player: Player): Card = {
+ println("It's your turn, " + player.name + ".")
+ if(!KnockOutWhist.DEBUG_MODE) Thread.sleep(3000L)
+ println("Which card do you want to play?")
+ showCards(player)
+ try {
+ val card = readLine().toInt-1
+ val handCard = player.currentHand()
+ if (handCard.isEmpty) {
+ println("You don't have any cards.")
+ throw new IllegalStateException("Trying to play a card without any cards.")
+ } else if(card < 0 || card >= handCard.get.cards.length) {
+ println("Please enter a valid number.")
+ playCard(player)
+ } else {
+ handCard.get.cards(card)
+ }
+ } catch {
+ case e: NumberFormatException =>
+ println("Please enter a valid number.")
+ playCard(player)
+ }
+
+ }
+
+ override def dogplayCard(player: Player, round: Round): Option[Card] = {
+ println("It's your turn, " + player.name + ".")
+ if (!KnockOutWhist.DEBUG_MODE) Thread.sleep(3000L)
+ println("You are using your dog life. Do you want to play your final card now?")
+ if(round.dogNeedsToPlay) {
+ println("You have to play your final card this round!")
+ println("Please enter y to play your final card.")
+ }else {
+ println("Please enter y/n to play your final card.")
+ }
+
+ showCards(player)
+ val card = readLine()
+ val handCard = player.currentHand()
+ if (handCard.isEmpty) {
+ println("You don't have any cards.")
+ throw new IllegalStateException("Trying to play a card without any cards.")
+ } else if(card.equalsIgnoreCase("y")) {
+ Some(handCard.get.cards.head)
+ } else if (card.equalsIgnoreCase("n") && !round.dogNeedsToPlay) {
+ None
+ } else {
+ println("Please enter y or n to play your final card.")
+ dogplayCard(player, round)
+ }
+ }
+
+ override def determineWinnerTie(players: List[Player]): Player = {
+ determineWinnerTieText(players, true)
+ }
+
+ @tailrec
+ private def determineWinnerTieText(players: List[Player], tieMessage: Boolean): Player = {
+ if (!KnockOutWhist.DEBUG_MODE) CardManager.shuffleAndReset()
+ if (tieMessage) println("It's a tie! Let's cut to determine the winner.")
+ 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) {
+ println(s"${player.name} enter a number between 1 and $remaining.")
+ try {
+ val selected = readLine().toInt - 1
+ if (selected >= 0 && selected < remaining) {
+ selCard = CardManager.cardContainer(currentStep + selected)
+ cut.put(player, selCard)
+ currentStep += selected + 1
+ remaining -= selected
+ } else {
+ println("Please enter a valid number.")
+ }
+ } catch {
+ case e: NumberFormatException =>
+ println("Please enter a valid number.")
+ }
+ }
+ }
+ println("The cards are:")
+ val a: Array[String] = Array("", "", "", "", "", "", "", "")
+ for ((player, card) <- cut) {
+ val playerNameLength = player.name.length
+ a(0) += " " + player.name + ":" + (" " * (playerNameLength - 1))
+ val rendered = card.renderAsString()
+ a(1) += " " + rendered(0)
+ a(2) += " " + rendered(1)
+ a(3) += " " + rendered(2)
+ a(4) += " " + rendered(3)
+ a(5) += " " + rendered(4)
+ a(6) += " " + rendered(5)
+ a(7) += " " + rendered(6)
+ }
+ a.foreach(println)
+
+ var currentHighest: Card = null
+ val winner: ListBuffer[Player] = ListBuffer()
+ for ((player, card) <- cut) {
+ breakable {
+ if (currentHighest == null) {
+ currentHighest = card
+ winner += player
+ break
+ }
+ 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) {
+ println(s"${winner.head.name} wins the cut!")
+ return winner.head
+ }
+ println("It's a tie again! Let's cut again.")
+ determineWinnerTieText(winner.toList, false)
+ }
+
+ override def pickNextTrumpsuit(player: Player): Suit = {
+ println("Which suit do you want to pick as the next trump suit?")
+ println("1: Hearts")
+ println("2: Diamonds")
+ println("3: Clubs")
+ println("4: Spades")
+ println()
+
+ player.currentHand().get.renderAsString().foreach(println)
+
+ try {
+ val suit = readLine().toInt
+ suit match {
+ case 1 => Suit.Hearts
+ case 2 => Suit.Diamonds
+ case 3 => Suit.Clubs
+ case 4 => Suit.Spades
+ case _ =>
+ println("Please enter a valid number.")
+ pickNextTrumpsuit(player)
+ }
+ } catch {
+ case e: NumberFormatException =>
+ println("Please enter a valid number.")
+ pickNextTrumpsuit(player)
+ }
+ }
+
+ override def showCards(player: Player): Boolean = {
+ val hand = player.currentHand()
+ if (hand.isEmpty) {
+ println("You don't have any cards.")
+ return false
+ }
+ println("Your cards:")
+ var rendered = hand.get.renderAsString()
+ rendered ::= {
+ var s = ""
+ for (i <- hand.get.cards.indices) {
+ s += s" ${i+1} " + " "
+ }
+ s
+ }
+ rendered.foreach(println)
+ true
+ }
+
+ override def showWon(player: Player, round: Round): String = {
+ s"$player won this round."
+ }
+
+}
diff --git a/src/main/scala/de/knockoutwhist/player/Player.scala b/src/main/scala/de/knockoutwhist/player/Player.scala
new file mode 100644
index 0000000..784b2bb
--- /dev/null
+++ b/src/main/scala/de/knockoutwhist/player/Player.scala
@@ -0,0 +1,32 @@
+package de.knockoutwhist.player
+
+import de.knockoutwhist.KnockOutWhist
+import de.knockoutwhist.cards.{Card, Hand, Suit}
+
+import scala.collection.mutable.ListBuffer
+
+case class Player(name: String) {
+ private var hand: Option[Hand] = None
+
+ def currentHand(): Option[Hand] = hand
+ var doglife: Boolean = false
+ def provideHand(hand: Hand): Boolean = {
+ this.hand = Some(hand)
+ true
+ }
+ def pickTrumpsuit(): Suit = {
+ KnockOutWhist.matchControl.playerControl.pickNextTrumpsuit(this)
+ }
+ def removeCard(card: Card): Int = {
+ hand = Some(hand.get.removeCard(card))
+ hand.get.cards.size
+ }
+
+ override def toString: String = {
+ name
+ }
+
+
+
+
+}
diff --git a/src/main/scala/de/knockoutwhist/rounds/Match.scala b/src/main/scala/de/knockoutwhist/rounds/Match.scala
index 56d1200..5284a1b 100644
--- a/src/main/scala/de/knockoutwhist/rounds/Match.scala
+++ b/src/main/scala/de/knockoutwhist/rounds/Match.scala
@@ -1,3 +1,65 @@
package de.knockoutwhist.rounds
-case class Match()
+import de.knockoutwhist.KnockOutWhist
+import de.knockoutwhist.cards.CardManager
+import de.knockoutwhist.player.Player
+
+import scala.collection.mutable.ListBuffer
+import de.knockoutwhist.utils.Implicits._
+
+case class Match(totalplayers: List[Player], private[rounds] var number_of_cards: Int = 7) {
+
+ private[rounds] val roundlist: ListBuffer[Round] = ListBuffer[Round]()
+ private var current_round: Option[Round] = None
+ private[rounds] var dogLife = false
+
+ def create_round(): Round = {
+ val remainingPlayer = roundlist.isEmpty ? totalplayers |: roundlist.last.remainingPlayers()
+ provideCards(remainingPlayer)
+ if (roundlist.isEmpty) {
+ val random_trumpsuit = CardManager.nextCard().suit
+ current_round = Some(new Round(random_trumpsuit, this, remainingPlayer, true))
+ } else {
+ val winner = roundlist.last.winner
+ val trumpsuit = winner.pickTrumpsuit()
+
+ current_round = Some(new Round(trumpsuit, this, remainingPlayer, false))
+ }
+ number_of_cards -= 1
+ current_round.get
+ }
+
+ def isOver: Boolean = {
+ if(roundlist.isEmpty) {
+ false
+ } else {
+ roundlist.last.remainingPlayers().size == 1
+ }
+ }
+
+ private def provideCards(players: List[Player]): Int = {
+ if(!KnockOutWhist.DEBUG_MODE) CardManager.shuffleAndReset()
+ var hands = 0
+ for (player <- players) {
+ if(!player.doglife) {
+ player.provideHand(CardManager.createHand(number_of_cards))
+ } else {
+ player.provideHand(CardManager.createHand(1))
+ }
+ hands += 1
+ }
+ hands
+ }
+
+ def finalizeMatch(): Player = {
+ if(!isOver) {
+ throw new IllegalStateException("Match is not over yet.")
+ }
+ roundlist.last.remainingPlayers().head
+ }
+
+ override def toString: String = {
+ s"${totalplayers}, ${number_of_cards}"
+ }
+}
+
diff --git a/src/main/scala/de/knockoutwhist/rounds/Round.scala b/src/main/scala/de/knockoutwhist/rounds/Round.scala
index 84a7417..92ea9f6 100644
--- a/src/main/scala/de/knockoutwhist/rounds/Round.scala
+++ b/src/main/scala/de/knockoutwhist/rounds/Round.scala
@@ -1,15 +1,81 @@
package de.knockoutwhist.rounds
-import de.knockoutwhist.rounds.Trick
-import de.knockoutwhist.cards.Suit
-import de.knockoutwhist.cards.Player
+import de.knockoutwhist.KnockOutWhist
+import de.knockoutwhist.cards.{CardManager, Suit}
+import de.knockoutwhist.player.Player
+import de.knockoutwhist.utils.Implicits._
+
+import scala.collection.mutable
import scala.collection.mutable.ListBuffer
-case class Round(trumpSuit: Suit, cardAmount: Int, tricklist: ListBuffer[Trick], players_in: List[Player]) {
-// def create_trick(trumpSuit: Suit, players_in: List[Player]): Unit =
-// {
-//
-// }
+case class Round private[rounds](trumpSuit: Suit, matchImpl: Match, private[rounds] val tricklist: ListBuffer[Trick], players_in: List[Player], players_out: List[Player] = null, winner: Player = null, firstRound: Boolean) {
+ def this(trumpSuit: Suit, matchImpl: Match, players_in: List[Player], firstRound: Boolean) = {
+ this(trumpSuit, matchImpl, ListBuffer[Trick](), players_in, firstRound = firstRound)
+ }
+
+ private var current_trick: Option[Trick] = None
+
+ def get_current_trick(): Trick = {
+ current_trick.getOrElse(create_trick())
+ }
+
+ def get_tricks(): List[Trick] = tricklist.toList
+ def create_trick(): Trick = {
+ val trick = new Trick(this)
+ current_trick = Some(trick)
+ trick
+ }
+
+ def isOver: Boolean = {
+ players_in.map(_.currentHand()).count(_.get.cards.isEmpty) == players_in.size
+ }
+
+ def dogNeedsToPlay: Boolean = {
+ players_in.filter(!_.doglife).map(_.currentHand()).exists(_.get.cards.isEmpty)
+ }
+
+ def finalizeRound(force: Boolean = false): (Player, Round) = {
+ if(!force && tricklist.isEmpty)
+ throw new IllegalStateException("No tricks played in this round")
+ if(!force && !isOver)
+ throw new IllegalStateException("Not all tricks were played in this round")
+ val tricksMapped = 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 = firstRound
+ ? List()
+ |: players_in.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
+ |: KnockOutWhist.matchControl.playerControl.determineWinnerTie(winners.toList)
+
+ val finalRound = Round(trumpSuit, matchImpl, tricklist, players_in, playersOut, winner, firstRound)
+ matchImpl.roundlist += finalRound
+ (winner, finalRound)
+ }
+
+ def remainingPlayers(): List[Player] = {
+ if (players_out == null) {
+ return players_in
+ }
+ players_in.filter(!players_out.contains(_))
+ }
+ override def toString: String = {
+ s"$trumpSuit, $tricklist, $players_in, $players_out, $winner, $firstRound"
+ }
}
diff --git a/src/main/scala/de/knockoutwhist/rounds/Trick.scala b/src/main/scala/de/knockoutwhist/rounds/Trick.scala
index 8147eac..18cc53d 100644
--- a/src/main/scala/de/knockoutwhist/rounds/Trick.scala
+++ b/src/main/scala/de/knockoutwhist/rounds/Trick.scala
@@ -1,15 +1,21 @@
package de.knockoutwhist.rounds
-import de.knockoutwhist.cards.{Card, Player}
+import de.knockoutwhist.cards.Card
import de.knockoutwhist.cards.Suit
+import de.knockoutwhist.player.Player
import scala.collection.mutable
case class Trick private(round: Round, cards: mutable.HashMap[Card, Player], winner: Player = null, finished: Boolean = false) {
- def this(round: Round) = this(round, mutable.HashMap[Card, Player]())
- var first_card: Option[Suit] = None // statt als Parameter im Konstruktor
+ def this(round: Round) = {
+ this(round, mutable.HashMap[Card, Player]())
+ }
+ private var first_card: Option[Card] = None // statt als Parameter im Konstruktor
+
+ def get_first_card(): Option[Card] = first_card
+
/**
* Play a card in the trick
* @param card The card to play
@@ -19,11 +25,11 @@ case class Trick private(round: Round, cards: mutable.HashMap[Card, Player], win
if (finished) {
throw new IllegalStateException("This trick is already finished")
} else {
- if (cards.isEmpty) {
- first_card = Some(card.suit)
+ if (first_card.isEmpty) {
+ first_card = Some(card)
cards += (card -> player)
true
- } else if (card.suit == first_card.getOrElse(card.suit)) { // Wert aus Option extrahieren
+ } else if (card.suit == first_card.getOrElse(card).suit) { // Wert aus Option extrahieren
cards += (card -> player)
true
} else if (card.suit == round.trumpSuit) {
@@ -41,12 +47,18 @@ case class Trick private(round: Round, cards: mutable.HashMap[Card, Player], win
if (cards.keys.exists(_.suit == round.trumpSuit)) {
cards.keys.filter(_.suit == round.trumpSuit).maxBy(_.cardValue.ordinal) //stream
} else {
- cards.keys.filter(_.suit == first_card.getOrElse(Suit.Spades)).maxBy(_.cardValue.ordinal) //stream
+ cards.keys.filter(_.suit == first_card.get.suit).maxBy(_.cardValue.ordinal) //stream
}
}
val winningPlayer = cards(winningCard)
- (winningPlayer, Trick(round, cards, winningPlayer, true))
- }
+ val finalTrick = Trick(round, cards, winningPlayer, true)
+ round.tricklist += finalTrick
+ (winningPlayer, finalTrick)
}
+ override def toString: String = {
+ s"$cards, $winner, $finished"
+ }
+}
+
diff --git a/src/main/scala/de/knockoutwhist/utils/CustomPlayerQueue.scala b/src/main/scala/de/knockoutwhist/utils/CustomPlayerQueue.scala
new file mode 100644
index 0000000..41f2e54
--- /dev/null
+++ b/src/main/scala/de/knockoutwhist/utils/CustomPlayerQueue.scala
@@ -0,0 +1,42 @@
+package de.knockoutwhist.utils
+
+class CustomPlayerQueue[A] (protected var players: Array[A], val start: Int = 0) extends Iterable[A] {
+
+ private var current = start
+
+ def nextPlayer(): A = {
+ val player = players(current)
+ current = (current + 1) % players.length
+ player
+ }
+
+ def remove(player: A): Int = {
+ players = players.filter(_ != player)
+ players.size
+ }
+
+ def resetAndSetStart(player: A): Boolean = {
+ if(players.contains(player)) {
+ current = players.indexOf(player)
+ true
+ } else {
+ false
+ }
+ }
+
+ override def toList: List[A] = players.toList
+
+ override def isEmpty: Boolean = players.isEmpty
+ override def size: Int = players.length
+
+
+
+ def iterator: Iterator[A] = new Iterator[A] {
+ private var index = 0
+ def hasNext: Boolean = index < players.length
+ def next(): A = {
+ index += 1
+ CustomPlayerQueue.this.nextPlayer()
+ }
+ }
+}
diff --git a/src/main/scala/de/knockoutwhist/utils/Implicits.scala b/src/main/scala/de/knockoutwhist/utils/Implicits.scala
new file mode 100644
index 0000000..915f85e
--- /dev/null
+++ b/src/main/scala/de/knockoutwhist/utils/Implicits.scala
@@ -0,0 +1,23 @@
+package de.knockoutwhist.utils
+
+import scala.annotation.targetName
+
+object Implicits {
+
+ implicit class Ternable(condition: Boolean) {
+ @targetName("tern")
+ def ?[T](ifTrue: => T): Option[T] = {
+ if (condition) Some(ifTrue) else None
+ }
+ }
+
+ implicit class Colonable[T](ifFalse: => T) {
+ @targetName("colon")
+ def |:(intermediate: Option[T]): T =
+ intermediate match {
+ case Some(ifTrue) => ifTrue
+ case None => ifFalse
+ }
+ }
+
+}
diff --git a/src/test/scala/de/knockoutwhist/MainTests.scala b/src/test/scala/de/knockoutwhist/MainTests.scala
new file mode 100644
index 0000000..b8a5146
--- /dev/null
+++ b/src/test/scala/de/knockoutwhist/MainTests.scala
@@ -0,0 +1,22 @@
+package de.knockoutwhist
+
+import de.knockoutwhist.testutils.TestUtil
+import org.scalatest.funsuite.AnyFunSuite
+
+class MainTests extends AnyFunSuite {
+
+ test("Main should be able to go to the main menu") {
+ TestUtil.simulateInput("2\n") {
+ KnockOutWhist.main(Array())
+ }
+ }
+
+ test("Main should be able to be executed twice") {
+ TestUtil.simulateInput("2\n") {
+ assertThrows[IllegalStateException] {
+ KnockOutWhist.main(Array())
+ }
+ }
+ }
+
+}
diff --git a/src/test/scala/de/knockoutwhist/TestSequence.scala b/src/test/scala/de/knockoutwhist/TestSequence.scala
new file mode 100644
index 0000000..0ed1026
--- /dev/null
+++ b/src/test/scala/de/knockoutwhist/TestSequence.scala
@@ -0,0 +1,24 @@
+package de.knockoutwhist
+
+import de.knockoutwhist.cards.{CardTests, DeckTests, HandTests}
+import de.knockoutwhist.control.text.{TextMatchControllerTests, TextPlayerControllerTests}
+import de.knockoutwhist.player.PlayerTests
+import de.knockoutwhist.rounds.{GameplayTests, MatchTests, TrickTests}
+import de.knockoutwhist.utils.{ImplicitTests, QueueTests}
+import org.scalatest.Sequential
+
+class TestSequence extends Sequential(
+ new GameplayTests(),
+ new MainTests(),
+ new MatchTests(),
+ new TrickTests(),
+ new QueueTests(),
+ new ImplicitTests(),
+ new PlayerTests(),
+ new TextPlayerControllerTests(),
+ new TextMatchControllerTests(),
+ new CardTests(),
+ new DeckTests(),
+ new HandTests(),
+
+) {}
diff --git a/src/test/scala/de/knockoutwhist/cards/CardTests.scala b/src/test/scala/de/knockoutwhist/cards/CardTests.scala
new file mode 100644
index 0000000..8d7fa71
--- /dev/null
+++ b/src/test/scala/de/knockoutwhist/cards/CardTests.scala
@@ -0,0 +1,49 @@
+package de.knockoutwhist.cards
+
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+
+class CardTests extends AnyWordSpec with Matchers{
+
+ "A card" should {
+ "be displayed with correct value and Suit" in {
+ val card = Card(CardValue.Ace, Suit.Spades)
+ val e = "Ace of Spades"
+ card.toString.equals(e) shouldBe true
+ }
+ "can be rendered" in {
+ val card = Card(CardValue.Ace, Suit.Spades)
+ val expectedResult = Vector[String](
+ "┌─────────┐",
+ s"│${Console.BLACK}${Console.BOLD}A${Console.RESET} │",
+ "│ │",
+ s"│ ${Console.BLACK}${Console.BOLD}♠${Console.RESET} │",
+ "│ │",
+ s"│ ${Console.BLACK}${Console.BOLD}A${Console.RESET}│",
+ "└─────────┘"
+ )
+ card.renderAsString() shouldBe expectedResult
+ }
+ "can be rendered for CardValue Ten" in {
+ val card = Card(CardValue.Ten, Suit.Spades)
+ val expectedResult = Vector[String](
+ "┌─────────┐",
+ s"│${Console.BLACK}${Console.BOLD}10${Console.RESET} │",
+ "│ │",
+ s"│ ${Console.BLACK}${Console.BOLD}♠${Console.RESET} │",
+ "│ │",
+ s"│ ${Console.BLACK}${Console.BOLD}10${Console.RESET}│",
+ "└─────────┘"
+ )
+ card.renderAsString() shouldBe expectedResult
+ }
+ "be able to reset the order" in {
+ CardManager.shuffleAndReset()
+ CardManager.resetOrder()
+ val card = CardManager.nextCard()
+ card.suit shouldBe Suit.Spades
+ card.cardValue shouldBe CardValue.Two
+ }
+ }
+
+}
diff --git a/src/test/scala/de/knockoutwhist/cards/DeckTests.scala b/src/test/scala/de/knockoutwhist/cards/DeckTests.scala
index f6bd3bd..e7f7231 100644
--- a/src/test/scala/de/knockoutwhist/cards/DeckTests.scala
+++ b/src/test/scala/de/knockoutwhist/cards/DeckTests.scala
@@ -1,13 +1,9 @@
package de.knockoutwhist.cards
-import de.knockoutwhist.rounds.{Round, Trick}
import org.scalatest.matchers.must.Matchers
import org.scalatest.matchers.should.Matchers.{should, shouldBe}
import org.scalatest.wordspec.AnyWordSpec
-import scala.collection.mutable
-import scala.collection.mutable.ListBuffer
-
class DeckTests extends AnyWordSpec with Matchers{
"A deck" should {
@@ -52,192 +48,14 @@ class DeckTests extends AnyWordSpec with Matchers{
val hand = CardManager.createHand(2)
hand.cards should have size 2
}
- }
- "A card" should {
- "be displayed with correct value and Suit" in {
- val card = Card(CardValue.Ace, Suit.Spades)
- val e = "Ace of Spades"
- card.toString.equals(e) shouldBe true
- }
- "can be rendered" in {
- val card = Card(CardValue.Ace, Suit.Spades)
- val expectedResult = Vector[String](
- "┌─────────┐",
- "│A │",
- "│ │",
- "│ ♠ │",
- "│ │",
- "│ A│",
- "└─────────┘"
- )
- card.renderAsString() shouldBe expectedResult
- }
- "can be rendered for CardValue Ten" in {
- val card = Card(CardValue.Ten, Suit.Spades)
- val expectedResult = Vector[String](
- "┌─────────┐",
- "│10 │",
- "│ │",
- "│ ♠ │",
- "│ │",
- "│ 10│",
- "└─────────┘"
- )
- card.renderAsString() shouldBe expectedResult
- }
- }
- "A player" should {
- "be able to remove cards from its hand" in {
- val handholder = ListBuffer[Card]()
- handholder.addOne(Card(CardValue.Ace, Suit.Spades))
- val hand = Hand(handholder.toList)
- val removedhand = hand.removeCard(Card(CardValue.Ace, Suit.Spades))
- removedhand.cards should have size 0
- }
- "be able to see, if he has a certain suit" in {
- val handholder = ListBuffer[Card]()
- handholder.addOne(Card(CardValue.Ace, Suit.Spades))
- val hand = Hand(handholder.toList)
- hand.hasSuit(Suit.Spades) shouldBe true
- }
- "be able to see, if he has a certain value" in {
- val handholder = ListBuffer[Card]()
- handholder.addOne(Card(CardValue.Ace, Suit.Spades))
- val hand = Hand(handholder.toList)
- hand.hasValue(CardValue.Ace) shouldBe true
- }
- "be able to see, if he has a card of Trumpsuit" in {
- val handholder = ListBuffer[Card]()
- handholder.addOne(Card(CardValue.Ace, Suit.Spades))
- val hand = Hand(handholder.toList)
- hand.hasTrumpSuit(Suit.Spades) shouldBe true
- }
- "be able to render his hand" in {
- val handholder = ListBuffer[Card]()
- handholder.addOne(Card(CardValue.Ace, Suit.Spades))
- handholder.addOne(Card(CardValue.Queen, Suit.Diamonds))
- val hand = Hand(handholder.toList)
- val expectedResult = List(
- "┌─────────┐ ┌─────────┐",
- "│A │ │Q │",
- "│ │ │ │",
- "│ ♠ │ │ ♦ │",
- "│ │ │ │",
- "│ A│ │ Q│",
- "└─────────┘ └─────────┘"
- )
- hand.renderAsString() shouldBe expectedResult
- }
- }
- "The playCard() Function" should {
- "be true for the first card played in a trick" in {
- val playerlist = List(Player("Gunter"))
- val player = Player("Gunter")
- val tricks_played: ListBuffer[Trick] = ListBuffer.empty[Trick]
- val round = Round(Suit.Spades, 7, tricks_played, playerlist)
- val card = Card(CardValue.Ace, Suit.Spades)
- val trick = new Trick(round)
- trick.playCard(card, player) shouldBe true
- }
- "be true if the suit matches the first card played" in {
- val player = Player("Gunter")
- val player2 = Player("Peter")
- val playerlist = List(player, player2)
- val tricks_played: ListBuffer[Trick] = ListBuffer.empty[Trick]
- val round = Round(Suit.Diamonds, 7, tricks_played, playerlist)
- val card = Card(CardValue.Two, Suit.Spades)
- val card2 = Card(CardValue.Ace, Suit.Spades)
- val trick = new Trick(round)
- trick.playCard(card, player)
- trick.playCard(card2, player2) shouldBe true
- }
- "be true if the card matches the trump-card" in {
- val player = Player("Gunter")
- val player2 = Player("Peter")
- val playerlist = List(player, player2)
- val tricks_played: ListBuffer[Trick] = ListBuffer.empty[Trick]
- val round = Round(Suit.Diamonds, 7, tricks_played, playerlist)
- val card = Card(CardValue.Ace, Suit.Spades)
- val card2 = Card(CardValue.Two, Suit.Diamonds)
- val trick = new Trick(round)
- trick.playCard(card, player)
- trick.playCard(card2, player2) shouldBe true
- }
- "be false if the card doesn't match the suit of the trump-card + first-card" in {
- val player = Player("Gunter")
- val player2 = Player("Peter")
- val playerlist = List(player, player2)
- val tricks_played: ListBuffer[Trick] = ListBuffer.empty[Trick]
- val round = Round(Suit.Diamonds, 7, tricks_played, playerlist)
- val card = Card(CardValue.Ace, Suit.Spades)
- val card2 = Card(CardValue.Two, Suit.Clubs)
- val trick = new Trick(round)
- trick.playCard(card, player)
- trick.playCard(card2, player2) shouldBe false
- }
- }
- "A trick" should {
- "be able to tell who won the trick" in {
- val playerlist = List(Player("Gunter"))
- val player = Player("Gunter")
- val tricks_played: ListBuffer[Trick] = ListBuffer.empty[Trick]
- val round = Round(Suit.Spades, 7, tricks_played, playerlist)
- val card = Card(CardValue.Ace, Suit.Spades)
- val trick = new Trick(round)
-
- trick.playCard(card, player)
-
- val won = trick.wonTrick()
-
- won(0) shouldBe player
- won(1).cards should equal(trick.cards)
- won(1).winner should equal(player)
- won(1).winner should equal(won(0))
- }
- "throw a IllegalStateException if it is already finished" in {
- val playerlist = List(Player("Gunter"))
- val player = Player("Gunter")
- val tricks_played: ListBuffer[Trick] = ListBuffer.empty[Trick]
- val round = Round(Suit.Spades, 7, tricks_played, playerlist)
- val card = Card(CardValue.Ace, Suit.Spades)
- val trick = new Trick(round)
- trick.playCard(card, player)
-
- val won = trick.wonTrick()
- assertThrows[IllegalStateException] { //If exception is thrown, assertThrows returns succeeded
- won(1).playCard(card, player)
+ "throw an exception if you request more then 52 cards without shuffling" in {
+ assertThrows[IndexOutOfBoundsException] {
+ for (_ <- 1 to 53) {
+ CardManager.nextCard()
+ }
}
}
- "filter the cards by suit correctly if no trump was played" in {
- val player = Player("Gunter")
- val player2 = Player("Peter")
- val playerlist = List(player, player2)
- val tricks_played: ListBuffer[Trick] = ListBuffer.empty[Trick]
- val round = Round(Suit.Hearts, 7, tricks_played, playerlist)
- val card = Card(CardValue.Ace, Suit.Spades)
- val card2 = Card(CardValue.Ten, Suit.Spades)
- val trick = new Trick(round)
- trick.playCard(card, player)
- trick.playCard(card2, player2)
- val won = trick.wonTrick()
- won(0) shouldBe player
-
- }
-
}
- "A player" should {
- "be able to have a hand" in {
- val card = Card(CardValue.Ace, Suit.Spades)
- val card2 = Card(CardValue.Ten, Suit.Spades)
- val card3 = Card(CardValue.Ten, Suit.Diamonds)
- val listCard = List(card, card2, card3)
- val testhand = Hand(listCard)
- val player = Player("Gunter")
- player.provideHand(testhand) shouldBe true
-
-
- }
- }
}
diff --git a/src/test/scala/de/knockoutwhist/cards/HandTests.scala b/src/test/scala/de/knockoutwhist/cards/HandTests.scala
new file mode 100644
index 0000000..e58c1ad
--- /dev/null
+++ b/src/test/scala/de/knockoutwhist/cards/HandTests.scala
@@ -0,0 +1,55 @@
+package de.knockoutwhist.cards
+
+import de.knockoutwhist.cards.{Card, CardValue, Hand, Suit}
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+
+import scala.collection.mutable.ListBuffer
+
+class HandTests extends AnyWordSpec with Matchers {
+
+ "The hand" should {
+ "be able to remove cards from its hand" in {
+ val handholder = ListBuffer[Card]()
+ handholder.addOne(Card(CardValue.Ace, Suit.Spades))
+ val hand = Hand(handholder.toList)
+ val removedhand = hand.removeCard(Card(CardValue.Ace, Suit.Spades))
+ removedhand.cards should have size 0
+ }
+ "be able to see, if he has a certain suit" in {
+ val handholder = ListBuffer[Card]()
+ handholder.addOne(Card(CardValue.Ace, Suit.Spades))
+ val hand = Hand(handholder.toList)
+ hand.hasSuit(Suit.Spades) shouldBe true
+ }
+ "be able to see, if he has a certain value" in {
+ val handholder = ListBuffer[Card]()
+ handholder.addOne(Card(CardValue.Ace, Suit.Spades))
+ val hand = Hand(handholder.toList)
+ hand.hasValue(CardValue.Ace) shouldBe true
+ }
+ "be able to see, if he has a card of Trumpsuit" in {
+ val handholder = ListBuffer[Card]()
+ handholder.addOne(Card(CardValue.Ace, Suit.Spades))
+ val hand = Hand(handholder.toList)
+ hand.hasTrumpSuit(Suit.Spades) shouldBe true
+ }
+ "be able to render his hand" in {
+ val handholder = ListBuffer[Card]()
+ handholder.addOne(Card(CardValue.Ace, Suit.Spades))
+ handholder.addOne(Card(CardValue.Queen, Suit.Diamonds))
+ val hand = Hand(handholder.toList)
+ val expectedResult = List(
+ "┌─────────┐ ┌─────────┐",
+ s"│${Console.BLACK}${Console.BOLD}A${Console.RESET} │ │${Console.RED}${Console.BOLD}Q${Console.RESET} │",
+ "│ │ │ │",
+ s"│ ${Console.BLACK}${Console.BOLD}♠${Console.RESET} │ │ ${Console.RED}${Console.BOLD}♦${Console.RESET} │",
+ "│ │ │ │",
+ s"│ ${Console.BLACK}${Console.BOLD}A${Console.RESET}│ │ ${Console.RED}${Console.BOLD}Q${Console.RESET}│",
+ "└─────────┘ └─────────┘"
+ )
+ hand.renderAsString() shouldBe expectedResult
+ }
+ }
+
+}
diff --git a/src/test/scala/de/knockoutwhist/control/text/TextMatchControllerTests.scala b/src/test/scala/de/knockoutwhist/control/text/TextMatchControllerTests.scala
new file mode 100644
index 0000000..c19a4bb
--- /dev/null
+++ b/src/test/scala/de/knockoutwhist/control/text/TextMatchControllerTests.scala
@@ -0,0 +1,179 @@
+package de.knockoutwhist.control.text
+
+import de.knockoutwhist.cards.CardManager
+import de.knockoutwhist.cards.CardValue.Ace
+import de.knockoutwhist.cards.Suit.Hearts
+import de.knockoutwhist.cards.{Card, CardValue, Hand, Suit}
+import de.knockoutwhist.player.Player
+import de.knockoutwhist.rounds.{Match, Round, Trick}
+import de.knockoutwhist.testutils.TestUtil
+import de.knockoutwhist.utils.CustomPlayerQueue
+import org.scalatest.matchers.must.Matchers
+import org.scalatest.matchers.should.Matchers.{should, shouldBe}
+import org.scalatest.wordspec.AnyWordSpec
+
+class TextMatchControllerTests extends AnyWordSpec with Matchers {
+ "The start function" should {
+ "throw no exception" in {
+ TestUtil.cancelOut() {
+ TestUtil.simulateInput("a\n2\n") {
+ TextMatchControl.start()
+ }
+ }
+ }
+ }
+
+ "The enter players function" should {
+ "throw no exception" in {
+ TestUtil.cancelOut() {
+ TestUtil.simulateInput("foo,bar\n") {
+ TextMatchControl.enterPlayers() should be (List(Player("foo"), Player("bar")))
+ }
+ }
+ }
+ "not accept less than 2 players" in {
+ TestUtil.cancelOut() {
+ TestUtil.simulateInput("foo\nbar,foo2\n") {
+ TextMatchControl.enterPlayers() should be (List(Player("bar"), Player("foo2")))
+ }
+ }
+ }
+ "not accept players with the same name" in {
+ TestUtil.cancelOut() {
+ TestUtil.simulateInput("foo,foo\nbar,foo\n") {
+ TextMatchControl.enterPlayers() should be (List(Player("bar"), Player("foo")))
+ }
+ }
+ }
+ "not accept player names less than 2 or greater than 10 characters" in {
+ TestUtil.cancelOut() {
+ TestUtil.simulateInput("f,b\nbarrrrrrrrrrrrrrrrr,foooooooooooooooooooo\nbar,foo\n") {
+ TextMatchControl.enterPlayers() should be (List(Player("bar"), Player("foo")))
+ }
+ }
+ }
+ }
+
+ "The control round function" should {
+ "throw no exception and return a winner" in {
+ val players = List(Player("foo"), Player("bar"))
+ val matchImpl = Match(players, 1)
+ TestUtil.disableDebugMode()
+ TextMatchControl.playerQueue = CustomPlayerQueue[Player](players.toArray[Player], 0)
+ TestUtil.cancelOut() {
+ TestUtil.simulateInput("1\n1\n1\n") {
+ TextMatchControl.controlRound(matchImpl).winner should be (players.head).or(be (players(1)))
+ }
+ }
+ }
+ "throw no exception and return a winner if both players stay in" in {
+ val players = List(Player("foo"), Player("bar"))
+ val matchImpl = Match(players)
+ TestUtil.enableDebugMode()
+ CardManager.shuffleAndReset()
+ CardManager.resetOrder()
+
+ TextMatchControl.playerQueue = CustomPlayerQueue[Player](players.toArray[Player], 0)
+ TestUtil.cancelOut() {
+ TestUtil.simulateInput("1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n") {
+ TextMatchControl.controlRound(matchImpl).winner should be(players.head).or(be(players(1)))
+ }
+ }
+ }
+ }
+
+ "The next round function" should {
+ "return null if the match is over" in {
+ val players = List(Player("foo"))
+ val matchImpl = Match(players, 2)
+ TestUtil.enableDebugMode()
+ TextMatchControl.playerQueue = CustomPlayerQueue[Player](players.toArray[Player], 0)
+ TestUtil.cancelOut() {
+ TestUtil.simulateInput("1\n1\n1\n") {
+ TextMatchControl.controlRound(matchImpl)
+ TextMatchControl.nextRound(matchImpl) should be (null)
+ }
+ }
+ }
+ }
+
+ "The next trick function" should {
+ "return null if the round is over" in {
+ val players = List(Player("foo"))
+ val matchImpl = Match(players, 2)
+ TestUtil.enableDebugMode()
+ TextMatchControl.playerQueue = CustomPlayerQueue[Player](players.toArray[Player], 0)
+ TestUtil.cancelOut() {
+ TestUtil.simulateInput("1\n1\n1\n") {
+ val round = TextMatchControl.controlRound(matchImpl)
+ TextMatchControl.nextTrick(round) should be (null)
+ }
+ }
+ }
+ }
+ "The controlSuit function" should {
+ "check if a player can play from the correct suit but doesnt" in {
+ val player1 = Player("Gunter")
+ val player2 = Player("Peter")
+ val players = List(player1, player2)
+ val hand = Hand(List(Card(CardValue.Ten, Suit.Spades),Card(CardValue.Two, Suit.Hearts)))
+ player1.provideHand(hand)
+ val matchImpl = Match(players, 2)
+ val round = new Round(Suit.Clubs, matchImpl, players, false)
+ val trick = new Trick(round)
+ trick.playCard(Card(Ace, Suit.Hearts), player2)
+ TestUtil.enableDebugMode()
+ TextMatchControl.playerQueue = CustomPlayerQueue[Player](players.toArray[Player], 0)
+ TestUtil.cancelOut() {
+ TestUtil.simulateInput("1\n2\n") {
+ val card = TextMatchControl.controlSuitplayed(trick, player1)
+ }
+ }
+ }
+ }
+
+ "The control Trick function" should {
+ "return the other player if the dog decides not to play" in {
+ val foo = Player("foo")
+ foo.doglife = true
+ foo.provideHand(CardManager.createHand(1))
+ val bar = Player("bar")
+ bar.provideHand(CardManager.createHand(3))
+ val players = List(foo, bar)
+ val matchImpl = Match(players, 2)
+ TestUtil.enableDebugMode()
+ TextMatchControl.playerQueue = CustomPlayerQueue[Player](players.toArray[Player], 0)
+ val round = new Round(Hearts,matchImpl,players,false)
+ TestUtil.cancelOut() {
+ TestUtil.simulateInput("n\n1\n") {
+ val finalTrick = TextMatchControl.controlTrick(round)
+ finalTrick.winner should be(bar)
+ }
+ }
+ }
+ "return the dog if he wins" in {
+ TestUtil.enableDebugMode()
+ CardManager.resetOrder()
+ for (i <- 0 to 12) {
+ CardManager.nextCard()
+ }
+ val foo = Player("foo")
+ foo.doglife = true
+ foo.provideHand(CardManager.createHand(1))
+ val bar = Player("bar")
+ bar.provideHand(CardManager.createHand(3))
+ val players = List(foo, bar)
+ val matchImpl = Match(players, 2)
+ TextMatchControl.playerQueue = CustomPlayerQueue[Player](players.toArray[Player], 0)
+ val round = new Round(foo.currentHand().get.cards.head.suit, matchImpl, players, false)
+ TestUtil.cancelOut() {
+ TestUtil.simulateInput("y\n1\n") {
+ val finalTrick = TextMatchControl.controlTrick(round)
+ finalTrick.winner should be(bar)
+ }
+ }
+ }
+ }
+
+
+}
diff --git a/src/test/scala/de/knockoutwhist/control/text/TextPlayerControllerTests.scala b/src/test/scala/de/knockoutwhist/control/text/TextPlayerControllerTests.scala
new file mode 100644
index 0000000..637cb6f
--- /dev/null
+++ b/src/test/scala/de/knockoutwhist/control/text/TextPlayerControllerTests.scala
@@ -0,0 +1,235 @@
+package de.knockoutwhist.control.text
+
+import de.knockoutwhist.cards.{CardManager, Hand}
+import de.knockoutwhist.cards.Suit._
+import de.knockoutwhist.player.Player
+import de.knockoutwhist.rounds.Round
+import de.knockoutwhist.testutils.TestUtil
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+
+class TextPlayerControllerTests extends AnyWordSpec with Matchers {
+
+ "The text player controller play function" should {
+ CardManager.shuffleAndReset()
+ TestUtil.cancelOut() {
+ "throw an exception of the player has no hand" in {
+ assertThrows[IllegalStateException] {
+ TestUtil.simulateInput("1\n") {
+ TextPlayerControl.playCard(Player("Foo"))
+ }
+ }
+ }
+ "ask again on an invalid input" in {
+ val player = Player("Foo")
+ CardManager.shuffleAndReset()
+ val hand = CardManager.createHand(1)
+ player.provideHand(hand)
+ val card = TestUtil.simulateInput("0\na\n1\n") {
+ TextPlayerControl.playCard(player)
+ }
+ card should be(hand.cards.head)
+ }
+ "provide the card the player selected" in {
+ val player = Player("Foo")
+ CardManager.shuffleAndReset()
+ val hand = CardManager.createHand(1)
+ player.provideHand(hand)
+ val card = TestUtil.simulateInput("1\n") {
+ TextPlayerControl.playCard(player)
+ }
+ card should be(hand.cards.head)
+ }
+ }
+ }
+
+ "The text player controller dogplay function" should {
+ val player = Player("Bar")
+ CardManager.shuffleAndReset()
+ val hand = CardManager.createHand(2)
+ player.provideHand(hand)
+ val round = new Round(Spades, null, List(player), false)
+ TestUtil.cancelOut() {
+ "throw an exception of the player has no hand" in {
+ assertThrows[IllegalStateException] {
+ TestUtil.simulateInput("y\n") {
+ TestUtil.disableDebugMode()
+ TextPlayerControl.dogplayCard(Player("Foo"), round)
+ }
+ }
+ }
+ "ask again on an invalid input" in {
+ TestUtil.enableDebugMode()
+ val player = Player("Foo")
+ CardManager.shuffleAndReset()
+ val hand = CardManager.createHand(1)
+ player.provideHand(hand)
+ val card = TestUtil.simulateInput("a\ny\n") {
+ TextPlayerControl.dogplayCard(player, round)
+ }
+ card should be(Some(hand.cards.head))
+ }
+ "provide the card the player selected" in {
+ TestUtil.enableDebugMode()
+ val player = Player("Foo")
+ CardManager.shuffleAndReset()
+ val hand = CardManager.createHand(1)
+ player.provideHand(hand)
+ val card = TestUtil.simulateInput("y\n") {
+ TextPlayerControl.dogplayCard(player, round)
+ }
+ card should be(Some(hand.cards.head))
+ }
+ "allow the dog to not play this trick" in {
+ TestUtil.enableDebugMode()
+ val player = Player("Foo")
+ CardManager.shuffleAndReset()
+ val hand = CardManager.createHand(1)
+ player.provideHand(hand)
+ val card = TestUtil.simulateInput("n\n") {
+ TextPlayerControl.dogplayCard(player, round)
+ }
+ card should be(None)
+ }
+ "force the dog to play in the last round" in {
+ TestUtil.enableDebugMode()
+ val player1 = Player("Foo")
+ CardManager.shuffleAndReset()
+ player1.provideHand(CardManager.createHand(1))
+ player1.doglife = true
+ val player2 = Player("Bar")
+ player2.provideHand(Hand(List()))
+ val round2 = new Round(Spades, null, List(player1,player2), true)
+ val card = TestUtil.simulateInput("n\ny\n") {
+ TextPlayerControl.dogplayCard(player1, round2)
+ }
+ card should be(Some(player1.currentHand().get.cards.head))
+ }
+ }
+ }
+
+ "The text player controller determineWinnerTie function" should {
+ TestUtil.cancelOut() {
+ "return the player with the highest card" in {
+ TestUtil.disableDebugMode()
+ val player1 = Player("Foo")
+ val player2 = Player("Bar")
+ val players = List(player1, player2)
+ val winner = TestUtil.simulateInput("1\n2\n") {
+ TextPlayerControl.determineWinnerTie(players)
+ }
+ winner should be(player2).or(be(player1))
+ }
+ "return the player with the highest card after a tie" in {
+ TestUtil.enableDebugMode()
+ CardManager.resetOrder()
+ val player1 = Player("Foo")
+ val player2 = Player("Bar")
+ val players = List(player1, player2)
+ val winner = TestUtil.simulateInput("1\n13\n5\n1\n") {
+ TextPlayerControl.determineWinnerTie(players)
+ }
+ winner should be(player2)
+ }
+ "return the player with the highest card after a tie (winner first)" in {
+ TestUtil.enableDebugMode()
+ CardManager.resetOrder()
+ val player1 = Player("Foo")
+ val player2 = Player("Bar")
+ val players = List(player1, player2)
+ val winner = TestUtil.simulateInput("13\n1\n") {
+ TextPlayerControl.determineWinnerTie(players)
+ }
+ winner should be(player1)
+ }
+ "ask again on an invalid input" in {
+ TestUtil.enableDebugMode()
+ CardManager.resetOrder()
+ val player1 = Player("Foo")
+ val player2 = Player("Bar")
+ val players = List(player1, player2)
+ val winner = TestUtil.simulateInput("a\n200\n1\n2\n") {
+ TextPlayerControl.determineWinnerTie(players)
+ }
+ winner should be(player2)
+ }
+ }
+ }
+
+ "The text player controller pickNextTrumpsuit function" should {
+ TestUtil.cancelOut() {
+ "return the suit the player selected (Spades)" in {
+ TestUtil.disableDebugMode()
+ val player = Player("Foo")
+ player.provideHand(CardManager.createHand(4))
+ val suit = TestUtil.simulateInput("4\n") {
+ TextPlayerControl.pickNextTrumpsuit(player)
+ }
+ suit should be(Spades)
+ }
+ "return the suit the player selected (Hearts)" in {
+ TestUtil.disableDebugMode()
+ val player = Player("Foo")
+ player.provideHand(CardManager.createHand(4))
+ val suit = TestUtil.simulateInput("1\n") {
+ TextPlayerControl.pickNextTrumpsuit(player)
+ }
+ suit should be(Hearts)
+ }
+ "return the suit the player selected (Diamonds)" in {
+ TestUtil.disableDebugMode()
+ val player = Player("Foo")
+ player.provideHand(CardManager.createHand(4))
+ val suit = TestUtil.simulateInput("2\n") {
+ TextPlayerControl.pickNextTrumpsuit(player)
+ }
+ suit should be(Diamonds)
+ }
+ "return the suit the player selected (Clubs)" in {
+ TestUtil.disableDebugMode()
+ val player = Player("Foo")
+ player.provideHand(CardManager.createHand(4))
+ val suit = TestUtil.simulateInput("3\n") {
+ TextPlayerControl.pickNextTrumpsuit(player)
+ }
+ suit should be(Clubs)
+ }
+ "ask again on an invalid input" in {
+ TestUtil.enableDebugMode()
+ val player = Player("Foo")
+ player.provideHand(CardManager.createHand(4))
+ val suit = TestUtil.simulateInput("a\n10\n1\n") {
+ TextPlayerControl.pickNextTrumpsuit(player)
+ }
+ suit should be(Hearts)
+ }
+ }
+ }
+
+ "The text player controller showCards function" should {
+ TestUtil.cancelOut() {
+ "return true if the player wants to show their cards" in {
+ TestUtil.disableDebugMode()
+ val player = Player("Foo")
+ player.provideHand(CardManager.createHand(4))
+ TextPlayerControl.showCards(player) should be(true)
+ }
+ "return false if the player does not want to show their cards" in {
+ TestUtil.disableDebugMode()
+ val player = Player("Foo")
+ TextPlayerControl.showCards(player) should be(false)
+ }
+ }
+ }
+
+ "The text player controller showWon function" should {
+ TestUtil.cancelOut() {
+ "print the winner of the match" in {
+ TestUtil.disableDebugMode()
+ val player = Player("Foo")
+ TextPlayerControl.showWon(player, null) should be("Foo won this round.")
+ }
+ }
+ }
+
+}
diff --git a/src/test/scala/de/knockoutwhist/player/PlayerTests.scala b/src/test/scala/de/knockoutwhist/player/PlayerTests.scala
new file mode 100644
index 0000000..4ffed5d
--- /dev/null
+++ b/src/test/scala/de/knockoutwhist/player/PlayerTests.scala
@@ -0,0 +1,32 @@
+package de.knockoutwhist.player
+
+import de.knockoutwhist.cards.{Card, CardValue, Hand, Suit}
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+
+class PlayerTests extends AnyWordSpec with Matchers {
+
+ "A player" should {
+ "be able to have a hand" in {
+ val card = Card(CardValue.Ace, Suit.Spades)
+ val card2 = Card(CardValue.Ten, Suit.Spades)
+ val card3 = Card(CardValue.Ten, Suit.Diamonds)
+ val listCard = List(card, card2, card3)
+ val testhand = Hand(listCard)
+ val player = Player("Gunter")
+ player.provideHand(testhand) shouldBe true
+ }
+ "be able to remove a Card" in {
+ val card = Card(CardValue.Ace, Suit.Spades)
+ val card2 = Card(CardValue.Ten, Suit.Spades)
+ val card3 = Card(CardValue.Ten, Suit.Diamonds)
+ val listCard = List(card, card2, card3)
+ val testhand = Hand(listCard)
+ val player = Player("Gunter")
+ player.provideHand(testhand)
+ player.removeCard(card) shouldBe 2
+ player.currentHand().get.cards should be (List(card2, card3))
+ }
+ }
+
+}
diff --git a/src/test/scala/de/knockoutwhist/rounds/GameplayTests.scala b/src/test/scala/de/knockoutwhist/rounds/GameplayTests.scala
new file mode 100644
index 0000000..8d4427d
--- /dev/null
+++ b/src/test/scala/de/knockoutwhist/rounds/GameplayTests.scala
@@ -0,0 +1,28 @@
+package de.knockoutwhist.rounds
+
+import de.knockoutwhist.cards.CardManager
+import de.knockoutwhist.control.text.TextMatchControl
+import de.knockoutwhist.testutils.TestUtil
+import org.scalatest.matchers.must.Matchers
+import org.scalatest.matchers.should.Matchers.{should, shouldBe}
+import org.scalatest.wordspec.AnyWordSpec
+
+import scala.List
+
+
+class GameplayTests extends AnyWordSpec with Matchers {
+ "The Match Control" must {
+ "not throw an exception" in {
+ TestUtil.enableDebugMode()
+ CardManager.shuffleAndReset()
+ CardManager.resetOrder()
+ TestUtil.cancelOut() {
+ TestUtil.simulateInput("1\nLeon,Janis\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\ny\n1\n1\n1\n2\n") {
+ TextMatchControl.start()
+ }
+ }
+ }
+ }
+
+
+}
diff --git a/src/test/scala/de/knockoutwhist/rounds/MatchTests.scala b/src/test/scala/de/knockoutwhist/rounds/MatchTests.scala
new file mode 100644
index 0000000..816e30f
--- /dev/null
+++ b/src/test/scala/de/knockoutwhist/rounds/MatchTests.scala
@@ -0,0 +1,151 @@
+package de.knockoutwhist.rounds
+
+import de.knockoutwhist.KnockOutWhist
+import de.knockoutwhist.cards.{Card, CardManager, CardValue, Suit}
+import de.knockoutwhist.player.Player
+import de.knockoutwhist.testutils.TestUtil
+import org.scalatest.matchers.must.Matchers
+import org.scalatest.matchers.should.Matchers.{should, shouldBe}
+import org.scalatest.wordspec.AnyWordSpec
+
+import scala.List
+import scala.collection.mutable.ListBuffer
+
+
+class MatchTests extends AnyWordSpec with Matchers{
+
+ "A Match" should {
+ TestUtil.cancelOut() {
+ TestUtil.disableDebugMode()
+ val player1 = Player("Gunter")
+ val player2 = Player("Peter")
+ val player_list = List(player1, player2)
+ val match1 = Match(player_list)
+ val round1 = match1.create_round()
+ val trumpsuit = round1.trumpSuit
+ val trick1 = round1.create_trick()
+ val playedcard1 = TestUtil.simulateInput("1\n") {
+ KnockOutWhist.matchControl.playerControl.playCard(player1)
+ }
+ trick1.playCard(playedcard1, player1)
+ val playedcard2 = TestUtil.simulateInput("1\n") {
+ KnockOutWhist.matchControl.playerControl.playCard(player2)
+ }
+ trick1.playCard(playedcard2, player2)
+ "return the players ingame in players_remaining" in {
+ round1.remainingPlayers() should be(player_list)
+ }
+ val rtrick1 = trick1.wonTrick()
+ round1.finalizeRound(true)
+ val round2 = TestUtil.simulateInput("1\n") {
+ match1.create_round()
+ }
+ TestUtil.enableDebugMode()
+ var trick2: Trick = null
+ "should be able to create a trick with requesting the current trick" in {
+ trick2 = round2.get_current_trick()
+ }
+ if (trick2 == null) {
+ trick2 = round2.get_current_trick()
+ }
+ val playedcard3 = TestUtil.simulateInput("1\n") {
+ KnockOutWhist.matchControl.playerControl.playCard(player1)
+ }
+ trick1.playCard(playedcard3, player1)
+ val playedcard4 = TestUtil.simulateInput("1\n") {
+ KnockOutWhist.matchControl.playerControl.playCard(player2)
+ }
+ trick2.playCard(playedcard4, player2)
+ "be able to return the current trick of the round" in {
+ round2.get_current_trick() should be(trick2)
+ }
+ val rtrick2 = trick2.wonTrick()
+ "return false if the round isn't over" in {
+ round2.isOver shouldBe false
+ }
+ "be able to return all the tricks of the round" in {
+ round1.get_tricks() should be(List(rtrick1._2))
+ }
+ "be able to tell if a dog needs to play" in {
+ round2.dogNeedsToPlay shouldBe false
+ }
+ "error out if a round is finalized without all tricks played" in {
+ assertThrows[IllegalStateException] {
+ round2.finalizeRound()
+ }
+ }
+ round2.finalizeRound(true)
+ val round3 = TestUtil.simulateInput("1\n") {
+ match1.create_round()
+ }
+ val trick3 = round3.create_trick()
+ val playedcard5 = TestUtil.simulateInput("1\n") {
+ KnockOutWhist.matchControl.playerControl.playCard(player1)
+ }
+ trick1.playCard(playedcard5, player1)
+ val playedcard6 = TestUtil.simulateInput("1\n") {
+ KnockOutWhist.matchControl.playerControl.playCard(player2)
+ }
+ trick3.playCard(playedcard6, player2)
+ trick3.wonTrick()
+ "throw an exception if a match gets finalized before it is finished" in {
+ assertThrows[IllegalStateException] { //If exception is thrown, assertThrows returns succeeded
+ match1.finalizeMatch()
+ }
+ }
+ "be able to create a random trumpsuit for first round" in {
+ round2.trumpSuit shouldBe Suit.Hearts
+ }
+ "return false when no round has been completed" in {
+ CardManager.shuffleAndReset()
+ val match3 = Match(List(Player("Gunter")))
+ match3.create_round()
+ match3.isOver shouldBe false
+ }
+ "return true if one player is remaining after a round has been played" in {
+ round3.finalizeRound(true)
+ match1.isOver shouldBe true
+ }
+ val round4 = TestUtil.simulateInput("1\n") {
+ match1.create_round()
+ }
+ val trick4 = round4.create_trick()
+ val playedcard7 = Card(CardValue.Ace, Suit.Hearts)
+ val playedcard8 = Card(CardValue.Two, Suit.Hearts)
+ trick4.playCard(playedcard7, player1)
+ trick4.playCard(playedcard8, player2)
+ trick4.wonTrick()
+
+ val trick5 = round4.create_trick()
+ trick5.playCard(playedcard8, player1)
+ trick5.playCard(playedcard7, player2)
+ trick5.wonTrick()
+ CardManager.shuffleAndReset()
+ val roundResult = TestUtil.simulateInput("1\n13\n") {
+ round4.finalizeRound(true)
+ }
+
+ val round5 = TestUtil.simulateInput("1\n") {
+ match1.create_round()
+ }
+ "error out if a round is finalized without any tricks played" in {
+ assertThrows[IllegalStateException] {
+ round5.finalizeRound()
+ }
+ }
+ "be able to finalize a round" in {
+ roundResult._1 should be(player1).or(be(player2))
+ }
+
+ "provide a toString for the rounds" in {
+ round5.toString should be(s"${Suit.Hearts}, ${ListBuffer()}, $player_list, null, null, false")
+ }
+ "show the winner of the match when it has ended" in {
+ match1.finalizeMatch() shouldBe Player("Peter")
+ }
+ "have a working toString Method" in {
+ match1.toString shouldBe "List(Gunter, Peter), 2"
+ }
+ }
+ }
+}
diff --git a/src/test/scala/de/knockoutwhist/rounds/TrickTests.scala b/src/test/scala/de/knockoutwhist/rounds/TrickTests.scala
new file mode 100644
index 0000000..cb93e8e
--- /dev/null
+++ b/src/test/scala/de/knockoutwhist/rounds/TrickTests.scala
@@ -0,0 +1,129 @@
+package de.knockoutwhist.rounds
+
+import de.knockoutwhist.cards.{Card, CardValue, Suit}
+import de.knockoutwhist.player.Player
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+
+class TrickTests extends AnyWordSpec with Matchers {
+
+ "A trick" should {
+ "be able to return the first card after it was played" in {
+ val player = Player("Gunter")
+ val player2 = Player("Peter")
+ val playerlist = List(player, player2)
+ val round = new Round(Suit.Diamonds, Match(playerlist), playerlist, false)
+ val card = Card(CardValue.Ace, Suit.Spades)
+ val card2 = Card(CardValue.Two, Suit.Clubs)
+ val trick = new Trick(round)
+ trick.playCard(card, player)
+ trick.get_first_card().isEmpty shouldBe false
+ trick.get_first_card().get shouldBe card
+ }
+ "be able to return no first card when none was played" in {
+ val player = Player("Gunter")
+ val player2 = Player("Peter")
+ val playerlist = List(player, player2)
+ val round = new Round(Suit.Diamonds, Match(playerlist), playerlist, false)
+ val trick = new Trick(round)
+ trick.get_first_card().isEmpty shouldBe true
+ }
+ "be able to tell who won the trick" in {
+ val playerlist = List(Player("Gunter"))
+ val player = Player("Gunter")
+ val round = new Round(Suit.Spades, Match(playerlist), playerlist, false)
+ val card = Card(CardValue.Ace, Suit.Spades)
+ val trick = new Trick(round)
+
+ trick.playCard(card, player)
+
+ val won = trick.wonTrick()
+
+ won(0) shouldBe player
+ won(1).cards should equal(trick.cards)
+ won(1).winner should equal(player)
+ won(1).winner should equal(won(0))
+ }
+ "throw a IllegalStateException if it is already finished" in {
+ val playerlist = List(Player("Gunter"))
+ val player = Player("Gunter")
+ val round = new Round(Suit.Spades, Match(playerlist), playerlist, false)
+ val card = Card(CardValue.Ace, Suit.Spades)
+ val trick = new Trick(round)
+ trick.playCard(card, player)
+
+ val won = trick.wonTrick()
+ assertThrows[IllegalStateException] { //If exception is thrown, assertThrows returns succeeded
+ won(1).playCard(card, player)
+ }
+ }
+ "filter the cards by suit correctly if no trump was played" in {
+ val player = Player("Gunter")
+ val player2 = Player("Peter")
+ val playerlist = List(player, player2)
+ val round = new Round(Suit.Hearts, Match(playerlist), playerlist, false)
+ val card = Card(CardValue.Ace, Suit.Spades)
+ val card2 = Card(CardValue.Ten, Suit.Spades)
+ val trick = new Trick(round)
+ trick.playCard(card, player)
+ trick.playCard(card2, player2)
+ val won = trick.wonTrick()
+ won(0) shouldBe player
+
+ }
+ "return true for the first card played in a trick" in {
+ val playerlist = List(Player("Gunter"))
+ val player = Player("Gunter")
+ val round = new Round(Suit.Spades, Match(playerlist), playerlist, false)
+ val card = Card(CardValue.Ace, Suit.Spades)
+ val trick = new Trick(round)
+ trick.playCard(card, player) shouldBe true
+ }
+ "return true if the suit matches the first card played" in {
+ val player = Player("Gunter")
+ val player2 = Player("Peter")
+ val playerlist = List(player, player2)
+ val round = new Round(Suit.Diamonds, Match(playerlist), playerlist, false)
+ val card = Card(CardValue.Two, Suit.Spades)
+ val card2 = Card(CardValue.Ace, Suit.Spades)
+ val trick = round.create_trick()
+ trick.playCard(card, player)
+ trick.playCard(card2, player2) shouldBe true
+ }
+ "return true if the card matches the trump-card" in {
+ val player = Player("Gunter")
+ val player2 = Player("Peter")
+ val playerlist = List(player, player2)
+ val round = new Round(Suit.Diamonds, Match(playerlist), playerlist, false)
+ val card = Card(CardValue.Ace, Suit.Spades)
+ val card2 = Card(CardValue.Two, Suit.Diamonds)
+ val trick = new Trick(round)
+ trick.playCard(card, player)
+ trick.playCard(card2, player2) shouldBe true
+ }
+ "return false if the card doesn't match the suit of the trump-card + first-card" in {
+ val player = Player("Gunter")
+ val player2 = Player("Peter")
+ val playerlist = List(player, player2)
+ val round = new Round(Suit.Diamonds, Match(playerlist), playerlist, false)
+ val card = Card(CardValue.Ace, Suit.Spades)
+ val card2 = Card(CardValue.Two, Suit.Clubs)
+ val trick = new Trick(round)
+ trick.playCard(card, player)
+ trick.playCard(card2, player2) shouldBe false
+ }
+ "have a working to string" in {
+ val player = Player("Gunter")
+ val player2 = Player("Peter")
+ val playerlist = List(player, player2)
+ val round = new Round(Suit.Diamonds, Match(playerlist), playerlist, false)
+ val card = Card(CardValue.Ace, Suit.Spades)
+ val card2 = Card(CardValue.Two, Suit.Clubs)
+ val trick = new Trick(round)
+ trick.playCard(card, player)
+ trick.playCard(card2, player2)
+ trick.toString() shouldBe s"${trick.cards}, ${null}, ${false}"
+ }
+ }
+
+}
diff --git a/src/test/scala/de/knockoutwhist/testutils/TestUtil.scala b/src/test/scala/de/knockoutwhist/testutils/TestUtil.scala
new file mode 100644
index 0000000..b35fc38
--- /dev/null
+++ b/src/test/scala/de/knockoutwhist/testutils/TestUtil.scala
@@ -0,0 +1,30 @@
+package de.knockoutwhist.testutils
+
+import de.knockoutwhist.KnockOutWhist
+
+import java.io.{ByteArrayInputStream, OutputStream}
+
+
+object TestUtil {
+
+ def simulateInput[T](input: String)(block: => T): T = {
+ Console.withIn(new ByteArrayInputStream(input.getBytes())) {
+ block
+ }
+ }
+
+ def cancelOut[T]()(block: => T): T = {
+ Console.withOut((b: Int) => {}) {
+ block
+ }
+ }
+
+ def enableDebugMode(): Unit = {
+ KnockOutWhist.DEBUG_MODE_VAR = true
+ }
+
+ def disableDebugMode(): Unit = {
+ KnockOutWhist.DEBUG_MODE_VAR = false
+ }
+
+}
diff --git a/src/test/scala/de/knockoutwhist/utils/ImplicitTests.scala b/src/test/scala/de/knockoutwhist/utils/ImplicitTests.scala
new file mode 100644
index 0000000..61fe0df
--- /dev/null
+++ b/src/test/scala/de/knockoutwhist/utils/ImplicitTests.scala
@@ -0,0 +1,22 @@
+package de.knockoutwhist.utils
+
+import de.knockoutwhist.utils.Implicits.*
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+
+class ImplicitTests extends AnyWordSpec with Matchers {
+ "The ternary operator" should {
+ "return the left if the condition is met" in {
+ 1 should be (true ? 1 |: 2)
+ }
+ "return the right if the condition is not met" in {
+ 2 should be (false ? 1 |: 2)
+ }
+ "return None if the condition is not met" in {
+ None should be (false ? 1)
+ }
+ "return an Option if the condition is met" in {
+ Some(1) should be (true ? 1)
+ }
+ }
+}
diff --git a/src/test/scala/de/knockoutwhist/utils/QueueTests.scala b/src/test/scala/de/knockoutwhist/utils/QueueTests.scala
new file mode 100644
index 0000000..63e0bc3
--- /dev/null
+++ b/src/test/scala/de/knockoutwhist/utils/QueueTests.scala
@@ -0,0 +1,73 @@
+package de.knockoutwhist.utils
+
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+
+class QueueTests extends AnyWordSpec with Matchers {
+ "A queue" should {
+
+ "return the next player" in {
+ val queue = new CustomPlayerQueue[Int](Array(1, 2, 3, 4, 5))
+ queue.nextPlayer() should be(1)
+ queue.nextPlayer() should be(2)
+ queue.nextPlayer() should be(3)
+ queue.nextPlayer() should be(4)
+ queue.nextPlayer() should be(5)
+ queue.nextPlayer() should be(1)
+ }
+ "remove a player" in {
+ val queue = new CustomPlayerQueue[Int](Array(1, 2, 3, 4, 5))
+ queue.remove(3) should be(4)
+ queue.nextPlayer() should be(1)
+ queue.nextPlayer() should be(2)
+ queue.nextPlayer() should be(4)
+ queue.nextPlayer() should be(5)
+ }
+ "reset and set start" in {
+ val queue = new CustomPlayerQueue[Int](Array(1, 2, 3, 4, 5))
+ queue.resetAndSetStart(5) should be(true)
+ queue.nextPlayer() should be(5)
+ queue.nextPlayer() should be(1)
+ queue.nextPlayer() should be(2)
+ queue.nextPlayer() should be(3)
+ queue.nextPlayer() should be(4)
+ }
+ "reset and set start from an invalid number" in {
+ val queue = new CustomPlayerQueue[Int](Array(1, 2, 3, 4, 5))
+ queue.resetAndSetStart(6) should be(false)
+ queue.nextPlayer() should be(1)
+ queue.nextPlayer() should be(2)
+ queue.nextPlayer() should be(3)
+ queue.nextPlayer() should be(4)
+ queue.nextPlayer() should be(5)
+ }
+ "return a list" in {
+ val queue = new CustomPlayerQueue[Int](Array(1, 2, 3, 4, 5))
+ queue.toList should be(List(1, 2, 3, 4, 5))
+ }
+ "be empty" in {
+ val queue = new CustomPlayerQueue[Int](Array(1, 4, 5))
+ queue.isEmpty should be(false)
+ queue.remove(1)
+ queue.remove(4)
+ queue.remove(5)
+ queue.isEmpty should be(true)
+ }
+ "return the size" in {
+ var queue = new CustomPlayerQueue[Int](Array())
+ queue.size should be(0)
+ queue = new CustomPlayerQueue[Int](Array(1, 2, 3, 4, 5))
+ queue.size should be(5)
+ }
+ "iterate over the queue" in {
+ val queue = new CustomPlayerQueue[Int](Array(1, 2, 3, 4, 5))
+ val iterator = queue.iterator
+ iterator.next() should be(1)
+ iterator.next() should be(2)
+ iterator.next() should be(3)
+ iterator.next() should be(4)
+ iterator.next() should be(5)
+ iterator.hasNext should be(false)
+ }
+ }
+}