Merge pull request 'Logic + TUI | 100% coverage' (#24) from player-logic into development
All checks were successful
Build and Test (KnockOutWhist) TeamCity build finished

Reviewed-on: #24
This commit is contained in:
2024-11-08 12:06:58 +01:00
34 changed files with 1788 additions and 266 deletions

4
.gitignore vendored
View File

@@ -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/**

8
.idea/.gitignore generated vendored Normal file
View File

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

6
.idea/misc.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_22" default="true" project-jdk-name="22" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

18
.idea/sbt.xml generated
View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ScalaSbtSettings">
<option name="linkedExternalProjectsSettings">
<SbtProjectSettings>
<option name="converterVersion" value="2" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/project" />
</set>
</option>
<option name="sbtVersion" value="1.10.2" />
</SbtProjectSettings>
</option>
</component>
</project>

View File

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

View File

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

View File

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

View File

@@ -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 = {

View File

@@ -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 = {

View File

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

View File

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

View File

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

View File

@@ -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()
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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()
}
}
}

View File

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

View File

@@ -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())
}
}
}
}

View File

@@ -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(),
) {}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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()
}
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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