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) + } + } +}