28 Commits

Author SHA1 Message Date
b9e896fc0b Merge pull request 'Reduced Test Time by a lot' (#34) from test-improvements into development
All checks were successful
Build and Test (KnockOutWhist) TeamCity build finished
Reviewed-on: #34
2024-11-21 20:57:49 +01:00
a0e2eadc93 Reduced Test Time by a lot
All checks were successful
Build and Test (KnockOutWhist) TeamCity build finished
2024-11-21 19:42:14 +01:00
0b5bfc69e5 Merge pull request 'Coveralls' (#29) from coverall into development
All checks were successful
Build and Test (KnockOutWhist) TeamCity build finished
Reviewed-on: #29
2024-11-18 10:45:23 +01:00
ad0494f0ba Merge remote-tracking branch 'origin/development' into coverall
All checks were successful
Build and Test (KnockOutWhist) TeamCity build finished
# Conflicts:
#	project/plugins.sbt
2024-11-18 10:42:48 +01:00
355addf8a5 Merge pull request 'eventsystem' (#28) from eventsystem into development
All checks were successful
Build and Test (KnockOutWhist) TeamCity build finished
Reviewed-on: #28
2024-11-18 10:38:50 +01:00
efd5e0fae6 Coveralls
All checks were successful
Build and Test (KnockOutWhist) TeamCity build finished
2024-11-18 10:37:44 +01:00
8cb5012c8f Remove unnecessary method
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-15 12:35:55 +01:00
LQ63
6304ce44b9 Changed GameplayTests
Some checks reported errors
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-15 12:35:39 +01:00
ee44f6940b Added more tests
Some checks reported errors
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-15 12:33:26 +01:00
LQ63
0c6de4b86f Added Tests for Events
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-15 12:00:48 +01:00
094afc5631 Added DelayHandlerTests
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-15 11:55:16 +01:00
bb0ad578be Merge pull request 'Pipeline Test' (#26) from pipelineWorks into development
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
Reviewed-on: #26
2024-11-15 11:38:13 +01:00
ce5efd7a05 Pipeline Test
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-15 11:35:26 +01:00
174fe3c993 Fixed Tests
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-15 11:17:34 +01:00
03ab3f3650 Improved Code Coverage
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-15 11:13:00 +01:00
LQ63
a7f839d01c Changed Tests according to the new structure.
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-14 20:35:12 +01:00
a7e84b9815 Fixed DelayHandler
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-14 20:14:22 +01:00
4c7fb1c8bc Fixed EventHandler
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-14 20:09:11 +01:00
ef1eb29fe3 Merge remote-tracking branch 'origin/eventsystem' into eventsystem
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-14 18:06:51 +01:00
LQ63
619cbcfaac Changed structure of Control
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-14 18:06:33 +01:00
fe46690fb4 Added a clear console for the main menu 2024-11-14 17:31:32 +01:00
24bfd7925c Splitted Logic between UI and Logic
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-14 17:28:39 +01:00
11c0b61933 Rearranged everything Data Structure got no more logic
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-14 16:47:38 +01:00
0cc7756556 Switcher Handlers over to one
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
Added UI Trait
Renamed Controls
Deleted old Logic
2024-11-14 12:31:17 +01:00
153d028959 Added a delay handler
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-13 22:04:41 +01:00
LQ63
177ec3772e Added more Events, finished GenericPlayerControl. Startet with GenericMatchControl
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-13 18:07:28 +01:00
c397d079bf Reworked Events
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
Created Events
2024-11-13 13:45:19 +01:00
a99182beaf WIP - Event System
Some checks failed
Build and Test (KnockOutWhist) TeamCity build failed
2024-11-11 23:14:56 +01:00
49 changed files with 1793 additions and 792 deletions

View File

@@ -1,5 +1,5 @@
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "3.5.1"
Compile/mainClass := Some("de.knockoutwhist.KnockOutWhist")
@@ -10,11 +10,16 @@ version := {
val buildNR = sys.env.getOrElse("BUI_COUNTER", "1")
s"$major.$minor.$buildNR"
}
organization := "de.knockoutwhist"
ThisBuild / organization := "de.knockoutwhist"
ThisBuild / version := version.value
ThisBuild / scalaVersion := "3.5.1"
lazy val root = (project in file("."))
.settings(
name := "Projekt-zu-SE"
name := "Projekt-zu-SE",
assembly / mainClass := Some("de.knockoutwhist.KnockOutWhist"),
assembly / assemblyJarName := s"KnockOutWhist-${version.value}.jar",
)

View File

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

View File

@@ -1,5 +0,0 @@
import de.knockoutwhist.cards.CardManager
val originalDeck = List(CardManager.cardContainer)
CardManager.shuffleAndReset()
val shuffledDeck = CardManager.cardContainer

View File

@@ -2,23 +2,22 @@ package de.knockoutwhist
import de.knockoutwhist.control.MatchControl
import de.knockoutwhist.control.text.TextMatchControl
import de.knockoutwhist.ui.tui.TUIMain
object KnockOutWhist {
val matchControl: MatchControl = TextMatchControl
/*
Debug mode:
- Disables the random shuffle of the cards
*/
private[knockoutwhist] var DEBUG_MODE_VAR: Boolean = true
private[knockoutwhist] var DEBUG_MODE_VAR: Boolean = false
def DEBUG_MODE = DEBUG_MODE_VAR
def main(args: Array[String]): Unit = {
if(!matchControl.initial()) throw new IllegalStateException("Game could not be started.")
if(!TUIMain.initial) throw new IllegalStateException("Game could not be started.")
}
}

View File

@@ -1,8 +1,5 @@
package de.knockoutwhist.cards
import de.knockoutwhist.cards.CardValue.Ten
import de.knockoutwhist.cards.Suit
enum Suit(identifier: String):
def cardType(): String = identifier
@@ -34,34 +31,6 @@ 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) {
return Vector(
s"┌─────────┐",
s"${cardColour(suit)}${Console.BOLD}${cardValue.cardType()}${Console.RESET}",
"│ │",
s"${cardColour(suit)}${Console.BOLD}${suit.cardType()}${Console.RESET}",
"│ │",
s"${cardColour(suit)}${Console.BOLD}${cardValue.cardType()}${Console.RESET}",
s"└─────────┘"
)
}
Vector(
s"┌─────────┐",
s"${cardColour(suit)}${Console.BOLD}${cardValue.cardType()}${Console.RESET}",
"│ │",
s"${cardColour(suit)}${Console.BOLD}${suit.cardType()}${Console.RESET}",
"│ │",
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"
}

View File

@@ -19,11 +19,5 @@ case class Hand(cards: List[Card]) {
def hasTrumpSuit(trumpSuit: Suit): Boolean = {
cards.exists(_.suit == trumpSuit)
}
def renderAsString() : List[String] = {
val cardStrings = cards.map(_.renderAsString())
val zipped = cardStrings.transpose
zipped.map(_.mkString(" "))
}
}

View File

@@ -0,0 +1,12 @@
package de.knockoutwhist.control
import de.knockoutwhist.ui.tui.TUIMain
import de.knockoutwhist.utils.DelayHandler
import de.knockoutwhist.utils.events.EventHandler
object ControlHandler extends EventHandler {
addListener(TUIMain)
addListener(DelayHandler)
}

View File

@@ -1,24 +1,77 @@
package de.knockoutwhist.control
import de.knockoutwhist.KnockOutWhist
import de.knockoutwhist.cards.{Card, CardManager}
import de.knockoutwhist.control.RoundControl.controlRound
import de.knockoutwhist.events.*
import de.knockoutwhist.events.ERROR_STATUS.{IDENTICAL_NAMES, INVALID_NAME_FORMAT, INVALID_NUMBER_OF_PLAYERS, WRONG_CARD}
import de.knockoutwhist.events.GLOBAL_STATUS.*
import de.knockoutwhist.events.PLAYER_STATUS.{SHOW_NOT_PLAYED, SHOW_WON_PLAYER_TRICK}
import de.knockoutwhist.events.ROUND_STATUS.{PLAYERS_OUT, SHOW_START_ROUND, WON_ROUND}
import de.knockoutwhist.events.round.ShowCurrentTrickEvent
import de.knockoutwhist.events.util.DelayEvent
import de.knockoutwhist.player.Player
import de.knockoutwhist.rounds.{Match, Round, Trick}
import de.knockoutwhist.utils.CustomPlayerQueue
import de.knockoutwhist.utils.Implicits.*
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
import scala.compiletime.uninitialized
import scala.io.StdIn
import scala.util.Random
/**
* Start the next trick
* @return the last trick or null if the round is over
*/
def nextTrick(roundImpl: Round): Trick
object MatchControl {
private[control] var playerQueue: CustomPlayerQueue[Player] = uninitialized
def startMatch(): Player = {
ControlHandler.invoke(ShowGlobalStatus(SHOW_START_MATCH))
val players = enterPlayers()
playerQueue = CustomPlayerQueue[Player](players, Random.nextInt(players.length))
controlMatch()
}
def enterPlayers(): Array[Player] = {
ControlHandler.invoke(ShowGlobalStatus(SHOW_TYPE_PLAYERS))
val names = StdIn.readLine().split(",")
if (names.length < 2) {
ControlHandler.invoke(ShowErrorStatus(INVALID_NUMBER_OF_PLAYERS))
return enterPlayers()
}
if (names.distinct.length != names.length) {
ControlHandler.invoke(ShowErrorStatus(IDENTICAL_NAMES))
return enterPlayers()
}
if (names.count(_.trim.isBlank) > 0 || names.count(_.trim.length <= 2) > 0 || names.count(_.trim.length > 10) > 0) {
ControlHandler.invoke(ShowErrorStatus(INVALID_NAME_FORMAT))
return enterPlayers()
}
names.map(s => Player(s))
}
def controlMatch(): Player = {
val matchImpl = Match(playerQueue.toList)
while (!isOver(matchImpl)) {
val roundImpl = controlRound(matchImpl)
}
val winner = finalizeMatch(matchImpl)
val playerwinner = winner.name
ControlHandler.invoke(ShowGlobalStatus(SHOW_FINISHED_MATCH, winner))
winner
}
def isOver(matchImpl: Match): Boolean = {
if (matchImpl.roundlist.isEmpty) {
false
} else {
RoundControl.remainingPlayers(matchImpl.roundlist.last).size == 1
}
}
def finalizeMatch(matchImpl: Match): Player = {
if (!isOver(matchImpl)) {
throw new IllegalStateException("Match is not over yet.")
}
RoundControl.remainingPlayers(matchImpl.roundlist.last).head
}
}

View File

@@ -1,16 +1,126 @@
package de.knockoutwhist.control
import de.knockoutwhist.cards.{Card, Suit}
import de.knockoutwhist.KnockOutWhist
import de.knockoutwhist.cards.{Card, CardManager, Suit}
import de.knockoutwhist.control.PlayerControl
import de.knockoutwhist.events.ERROR_STATUS.{INVALID_INPUT, INVALID_NUMBER, NOT_A_NUMBER}
import de.knockoutwhist.events.GLOBAL_STATUS.{SHOW_TIE, SHOW_TIE_TIE, SHOW_TIE_WINNER}
import de.knockoutwhist.events.PLAYER_STATUS.{SHOW_DOG_PLAY_CARD, SHOW_PLAY_CARD, SHOW_TIE_NUMBERS, SHOW_TRUMPSUIT_OPTIONS, SHOW_TURN}
import de.knockoutwhist.events.cards.{RenderHandEvent, ShowTieCardsEvent}
import de.knockoutwhist.events.directional.{RequestCardEvent, RequestDogPlayCardEvent, RequestNumberEvent, RequestPickTrumpsuitEvent}
import de.knockoutwhist.events.util.DelayEvent
import de.knockoutwhist.events.{ShowErrorStatus, ShowGlobalStatus, ShowPlayerStatus}
import de.knockoutwhist.player.Player
import de.knockoutwhist.rounds.Round
import de.knockoutwhist.ui.tui.TUIMain
import de.knockoutwhist.utils.DelayHandler
import de.knockoutwhist.utils.events.EventHandler
trait PlayerControl {
import scala.annotation.tailrec
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.io.StdIn.readLine
import scala.util.{Failure, Success}
import scala.util.control.Breaks.*
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
object PlayerControl {
def playCard(player: Player): Card = {
ControlHandler.invoke(ShowPlayerStatus(SHOW_TURN, player))
ControlHandler.invoke(DelayEvent(3000L))
ControlHandler.invoke(ShowPlayerStatus(SHOW_PLAY_CARD, player))
ControlHandler.invoke(RenderHandEvent(player.currentHand().get, true))
ControlHandler.invoke(RequestCardEvent(player.currentHand().get)) match {
case Success(value) => {
value
}
case Failure(exception) => {
ControlHandler.invoke(ShowErrorStatus(INVALID_NUMBER))
playCard(player)
}
}
}
def dogplayCard(player: Player, round: Round): Option[Card] = {
ControlHandler.invoke(ShowPlayerStatus(SHOW_TURN, player))
ControlHandler.invoke(DelayEvent(3000L))
ControlHandler.invoke(ShowPlayerStatus(SHOW_DOG_PLAY_CARD, player, RoundControl.dogNeedsToPlay(round)))
ControlHandler.invoke(RenderHandEvent(player.currentHand().get, false))
ControlHandler.invoke(RequestDogPlayCardEvent(player.currentHand().get, RoundControl.dogNeedsToPlay(round))) match {
case Success(value) => {
value
}
case Failure(exception) => {
ControlHandler.invoke(ShowErrorStatus(INVALID_INPUT))
dogplayCard(player, round)
}
}
}
def determineWinnerTie(players: List[Player]): Player = {
determineWinnerTieText(players, true)
}
@tailrec
private def determineWinnerTieText(players: List[Player], tieMessage: Boolean): Player = {
if (!KnockOutWhist.DEBUG_MODE) CardManager.shuffleAndReset()
if (tieMessage) ControlHandler.invoke(ShowGlobalStatus(SHOW_TIE))
var currentStep = 0
var remaining = CardManager.cardContainer.size - (players.length - 1)
val cut: mutable.HashMap[Player, Card] = mutable.HashMap()
for (player <- players) {
var selCard: Card = null
while (selCard == null) {
ControlHandler.invoke(ShowPlayerStatus(SHOW_TIE_NUMBERS, player, remaining))
ControlHandler.invoke(RequestNumberEvent(1, remaining)) match {
case Success(value) =>
selCard = CardManager.cardContainer(currentStep + (value-1))
cut.put(player, selCard)
currentStep += value
remaining -= (value-1)
case Failure(exception) =>
ControlHandler.invoke(ShowErrorStatus(NOT_A_NUMBER))
}
}
}
ControlHandler.invoke(ShowTieCardsEvent(cut.toList))
var currentHighest: Card = null
val winner: ListBuffer[Player] = ListBuffer()
for ((player, card) <- cut) {
if (currentHighest == null) {
currentHighest = card
winner += player
}else {
val compared = card.cardValue.ordinal.compareTo(currentHighest.cardValue.ordinal)
if (compared > 0) {
currentHighest = card
winner.clear()
winner += player
} else if (compared == 0) {
winner += player
}
}
}
if (winner.size == 1) {
ControlHandler.invoke(ShowGlobalStatus(SHOW_TIE_WINNER, winner.head))
return winner.head
}
ControlHandler.invoke(ShowGlobalStatus(SHOW_TIE_TIE))
determineWinnerTieText(winner.toList, false)
}
def pickNextTrumpsuit(player: Player): Suit = {
ControlHandler.invoke(ShowPlayerStatus(SHOW_TRUMPSUIT_OPTIONS, player))
ControlHandler.invoke(RenderHandEvent(player.currentHand().get, false))
ControlHandler.invoke(RequestPickTrumpsuitEvent()) match {
case Success(value) => {
value
}
case Failure(exception) => {
ControlHandler.invoke(ShowErrorStatus(INVALID_NUMBER))
pickNextTrumpsuit(player)
}
}
}
}

View File

@@ -0,0 +1,124 @@
package de.knockoutwhist.control
import de.knockoutwhist.KnockOutWhist
import de.knockoutwhist.cards.CardManager
import de.knockoutwhist.control.MatchControl.playerQueue
import de.knockoutwhist.player.Player
import de.knockoutwhist.rounds.{Match, Round, Trick}
import de.knockoutwhist.utils.Implicits.*
import de.knockoutwhist.control.RoundControl
import de.knockoutwhist.control.TrickControl.controlTrick
import de.knockoutwhist.events.ROUND_STATUS.{PLAYERS_OUT, SHOW_START_ROUND, WON_ROUND}
import de.knockoutwhist.events.ShowRoundStatus
import de.knockoutwhist.events.util.DelayEvent
object RoundControl {
def isOver(round: Round): Boolean = {
round.players_in.map(_.currentHand()).count(_.get.cards.isEmpty) == round.players_in.size
}
def dogNeedsToPlay(round: Round): Boolean = {
round.players_in.filter(!_.doglife).map(_.currentHand()).exists(_.get.cards.isEmpty)
}
def finalizeRound(round: Round, matchImpl: Match,force: Boolean = false): (Player, Round) = {
if (!force && round.tricklist.isEmpty)
throw new IllegalStateException("No tricks played in this round")
if (!force && !isOver(round))
throw new IllegalStateException("Not all tricks were played in this round")
val tricksMapped = round.tricklist
.map(t => t.winner)
.groupBy(identity).map((p, l) => (p, l.size)) //l.size = Anzahl gewonnener Tricks
val winners = tricksMapped
.filter((p, i) => i == tricksMapped.values.max)
.keys
var playersOut = round.firstRound
? List()
|: round.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
|: PlayerControl.determineWinnerTie(winners.toList)
val finalRound = Round(round.trumpSuit, matchImpl, round.tricklist, round.players_in, playersOut, winner, round.firstRound)
matchImpl.roundlist += finalRound
(winner, finalRound)
}
def remainingPlayers(round: Round): List[Player] = {
if (round.players_out == null) {
return round.players_in
}
round.players_in.filter(!round.players_out.contains(_))
}
def create_round(matchImpl: Match): Round = {
val remainingPlayer = matchImpl.roundlist.isEmpty ? matchImpl.totalplayers |: RoundControl.remainingPlayers(matchImpl.roundlist.last)
provideCards(matchImpl, remainingPlayer)
if (matchImpl.roundlist.isEmpty) {
val random_trumpsuit = CardManager.nextCard().suit
matchImpl.current_round = Some(new Round(random_trumpsuit, matchImpl, remainingPlayer, true))
} else {
val winner = matchImpl.roundlist.last.winner
val trumpsuit = PlayerControl.pickNextTrumpsuit(winner)
matchImpl.current_round = Some(new Round(trumpsuit, matchImpl, remainingPlayer, false))
}
matchImpl.number_of_cards -= 1
matchImpl.current_round.get
}
def nextRound(matchImpl: Match): Round = {
if (MatchControl.isOver(matchImpl)) {
return null
}
create_round(matchImpl)
}
def controlRound(matchImpl: Match): Round = {
val roundImpl = nextRound(matchImpl)
ControlHandler.invoke(ShowRoundStatus(SHOW_START_ROUND, roundImpl))
while (!RoundControl.isOver(roundImpl)) {
TrickControl.controlTrick(roundImpl)
}
val (roundWinner, finalRound) = RoundControl.finalizeRound(roundImpl, matchImpl)
ControlHandler.invoke(ShowRoundStatus(WON_ROUND, finalRound, roundWinner))
ControlHandler.invoke(DelayEvent(5000L))
if (finalRound.players_out.nonEmpty) {
ControlHandler.invoke(ShowRoundStatus(PLAYERS_OUT, finalRound))
finalRound.players_out.foreach(p => {
playerQueue.remove(p)
})
}
playerQueue.resetAndSetStart(roundWinner)
finalRound
}
private def provideCards(matchImpl: Match, players: List[Player]): Int = {
if (!KnockOutWhist.DEBUG_MODE) CardManager.shuffleAndReset()
var hands = 0
for (player <- players) {
if (!player.doglife) {
player.provideHand(CardManager.createHand(matchImpl.number_of_cards))
} else {
player.provideHand(CardManager.createHand(1))
}
hands += 1
}
hands
}
}

View File

@@ -0,0 +1,111 @@
package de.knockoutwhist.control
import de.knockoutwhist.KnockOutWhist
import de.knockoutwhist.cards.Card
import de.knockoutwhist.control.MatchControl.playerQueue
import de.knockoutwhist.events.ERROR_STATUS.WRONG_CARD
import de.knockoutwhist.events.PLAYER_STATUS.{SHOW_NOT_PLAYED, SHOW_WON_PLAYER_TRICK}
import de.knockoutwhist.events.{ShowErrorStatus, ShowPlayerStatus}
import de.knockoutwhist.events.round.ShowCurrentTrickEvent
import de.knockoutwhist.events.util.DelayEvent
import de.knockoutwhist.rounds.{Round, Trick}
import de.knockoutwhist.player.Player
object TrickControl {
def playCard(trick: Trick, round: Round, card: Card, player: Player): Boolean = {
if (trick.finished) {
throw new IllegalStateException("This trick is already finished")
} else {
if (trick.get_first_card().isEmpty) {
trick.set_first_card(card)
trick.cards += (card -> player)
true
} else if (card.suit == trick.get_first_card().getOrElse(card).suit) { // Wert aus Option extrahieren
trick.cards += (card -> player)
true
} else if (card.suit == round.trumpSuit) {
trick.cards += (card -> player)
true
} else {
trick.cards += (card -> player)
false
}
}
}
def wonTrick(trick: Trick, round: Round): (Player, Trick) = {
val winningCard = {
if (trick.cards.keys.exists(_.suit == round.trumpSuit)) {
trick.cards.keys.filter(_.suit == round.trumpSuit).maxBy(_.cardValue.ordinal) //stream
} else {
trick.cards.keys.filter(_.suit == trick.get_first_card().get.suit).maxBy(_.cardValue.ordinal) //stream
}
}
val winningPlayer = trick.cards(winningCard)
val finalTrick = Trick(round, trick.cards, winningPlayer, true)
round.tricklist += finalTrick
(winningPlayer, finalTrick)
}
def create_trick(round: Round): Trick = {
val trick = new Trick(round)
round.set_current_trick(trick)
trick
}
def controlTrick(round: Round): Trick = {
val trick = nextTrick(round)
for (player <- playerQueue) {
ControlHandler.invoke(ShowCurrentTrickEvent(round, trick))
if (!player.doglife) {
val rightCard = controlSuitplayed(trick, player)
player.removeCard(rightCard)
TrickControl.playCard(trick, round, rightCard, player)
} else if (player.currentHand().exists(_.cards.nonEmpty)) {
val card = PlayerControl.dogplayCard(player, round)
if (card.isEmpty) {
ControlHandler.invoke(ShowPlayerStatus(SHOW_NOT_PLAYED, player))
} else {
player.removeCard(card.get)
TrickControl.playCard(trick, round, card.get, player)
}
}
}
val (winner, finalTrick) = TrickControl.wonTrick(trick, round)
ControlHandler.invoke(ShowCurrentTrickEvent(round, finalTrick))
ControlHandler.invoke(ShowPlayerStatus(SHOW_WON_PLAYER_TRICK, winner))
playerQueue.resetAndSetStart(winner)
ControlHandler.invoke(DelayEvent(3000L))
finalTrick
}
def nextTrick(roundImpl: Round): Trick = {
if (RoundControl.isOver(roundImpl)) {
return null
}
create_trick(roundImpl)
}
private[control] def controlSuitplayed(trick: Trick, player: Player): Card = {
var card = PlayerControl.playCard(player)
if (trick.get_first_card().isDefined) {
val firstCard = trick.get_first_card().get
while (!(firstCard.suit == card.suit)) {
var hasSuit = false
for (cardInHand <- player.currentHand().get.cards) {
if (cardInHand.suit == firstCard.suit) {
hasSuit = true
}
}
if (!hasSuit) {
return card
} else {
ControlHandler.invoke(ShowErrorStatus(WRONG_CARD, firstCard))
card = PlayerControl.playCard(player)
}
}
}
card
}
}

View File

@@ -1,205 +0,0 @@
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

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

View File

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

View File

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

View File

@@ -0,0 +1,10 @@
package de.knockoutwhist.events.directional
import de.knockoutwhist.cards.{Card, Hand}
import de.knockoutwhist.utils.events.ReturnableEvent
import scala.util.Try
case class RequestCardEvent(hand: Hand) extends ReturnableEvent[Try[Card]] {
override def id: String = "RequestCardEvent"
}

View File

@@ -0,0 +1,10 @@
package de.knockoutwhist.events.directional
import de.knockoutwhist.cards.{Card, Hand}
import de.knockoutwhist.utils.events.ReturnableEvent
import scala.util.Try
case class RequestDogPlayCardEvent(hand: Hand, needs_to_play: Boolean) extends ReturnableEvent[Try[Option[Card]]] {
override def id: String = "RequestDogPlayCardEvent"
}

View File

@@ -0,0 +1,10 @@
package de.knockoutwhist.events.directional
import de.knockoutwhist.utils.events.{SimpleEvent, ReturnableEvent}
import scala.io.StdIn
import scala.util.Try
case class RequestNumberEvent(min: Int, max: Int) extends ReturnableEvent[Try[Int]] {
override def id: String = "RequestNumberEvent"
}

View File

@@ -0,0 +1,10 @@
package de.knockoutwhist.events.directional
import de.knockoutwhist.cards.Suit
import de.knockoutwhist.utils.events.ReturnableEvent
import scala.util.Try
case class RequestPickTrumpsuitEvent() extends ReturnableEvent[Try[Suit]] {
override def id: String = "RequestPickTrumpsuitEvent"
}

View File

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

View File

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

View File

@@ -14,9 +14,7 @@ case class Player(name: String) {
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

View File

@@ -7,56 +7,11 @@ 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) {
case class Match(totalplayers: List[Player], 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
}
val roundlist: ListBuffer[Round] = ListBuffer[Round]()
var current_round: Option[Round] = None
var dogLife = false
override def toString: String = {
s"${totalplayers}, ${number_of_cards}"

View File

@@ -2,77 +2,26 @@ package de.knockoutwhist.rounds
import de.knockoutwhist.KnockOutWhist
import de.knockoutwhist.cards.{CardManager, Suit}
import de.knockoutwhist.player.Player
import de.knockoutwhist.utils.Implicits._
import de.knockoutwhist.utils.Implicits.*
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
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) {
case class Round (trumpSuit: Suit, matchImpl: Match, 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_current_trick(): Option[Trick] = {
current_trick
}
def get_tricks(): List[Trick] = tricklist.toList
def create_trick(): Trick = {
val trick = new Trick(this)
def set_current_trick(trick: Trick): Unit = {
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

@@ -7,54 +7,21 @@ 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) {
case class Trick (round: Round, cards: mutable.HashMap[Card, Player], winner: Player = null, finished: Boolean = false) {
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
* @return True if the card has a chance of winning the trick, false otherwise
*/
def playCard(card: Card, player: Player): Boolean = {
if (finished) {
def set_first_card(card: Card): Option[Card] = {
if(first_card.isDefined) {
throw new IllegalStateException("This trick is already finished")
} else {
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
cards += (card -> player)
true
} else if (card.suit == round.trumpSuit) {
cards += (card -> player)
true
} else {
cards += (card -> player)
false
}
}
first_card = Some(card)
first_card
}
def wonTrick(): (Player, Trick) = {
val winningCard = {
if (cards.keys.exists(_.suit == round.trumpSuit)) {
cards.keys.filter(_.suit == round.trumpSuit).maxBy(_.cardValue.ordinal) //stream
} else {
cards.keys.filter(_.suit == first_card.get.suit).maxBy(_.cardValue.ordinal) //stream
}
}
val winningPlayer = cards(winningCard)
val finalTrick = Trick(round, cards, winningPlayer, true)
round.tricklist += finalTrick
(winningPlayer, finalTrick)
}
def get_first_card(): Option[Card] = first_card
override def toString: String = {
s"$cards, $winner, $finished"

View File

@@ -0,0 +1,7 @@
package de.knockoutwhist.ui
trait UI {
def initial: Boolean
}

View File

@@ -0,0 +1,319 @@
package de.knockoutwhist.ui.tui
import de.knockoutwhist.cards.{Card, CardValue, Hand, Suit}
import de.knockoutwhist.control.{ControlHandler, MatchControl}
import de.knockoutwhist.events.*
import de.knockoutwhist.events.ERROR_STATUS.*
import de.knockoutwhist.events.GLOBAL_STATUS.*
import de.knockoutwhist.events.PLAYER_STATUS.*
import de.knockoutwhist.events.ROUND_STATUS.{PLAYERS_OUT, SHOW_START_ROUND, WON_ROUND}
import de.knockoutwhist.events.cards.{RenderHandEvent, ShowTieCardsEvent}
import de.knockoutwhist.events.directional.{RequestCardEvent, RequestDogPlayCardEvent, RequestNumberEvent, RequestPickTrumpsuitEvent}
import de.knockoutwhist.events.round.ShowCurrentTrickEvent
import de.knockoutwhist.player.Player
import de.knockoutwhist.ui.UI
import de.knockoutwhist.utils.events.{EventListener, ReturnableEvent}
import scala.annotation.tailrec
import scala.io.StdIn.readLine
import scala.util.{Failure, Success, Try}
object TUIMain extends EventListener with UI {
var init = false
override def listen[R](event: ReturnableEvent[R]): Option[R] = {
event match {
case event: RenderHandEvent =>
TUICards.renderHandEvent(event.hand, event.showNumbers).foreach(println)
Some(true)
case event: ShowTieCardsEvent =>
val a: Array[String] = Array("", "", "", "", "", "", "", "")
for ((player,card) <- event.card) {
val playerNameLength = player.name.length
a(0) += " " + player.name + ":" + (" " * (playerNameLength - 1))
val rendered = TUICards.renderCardAsString(card)
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)
Some(true)
case event: ShowGlobalStatus =>
event.status match {
case SHOW_TIE =>
println("It's a tie! Let's cut to determine the winner.")
Some(true)
case SHOW_TIE_WINNER =>
if(event.objects.length != 1 || !event.objects.head.isInstanceOf[Player]) {
None
} else {
println(s"${event.objects.head.asInstanceOf[Player].name} wins the cut!")
Some(true)
}
case SHOW_TIE_TIE =>
println("It's a tie again! Let's cut again.")
Some(true)
case SHOW_START_MATCH =>
TUIUtil.clearConsole()
println("Starting a new match...")
TUIUtil.clearConsole(2)
Some(true)
case SHOW_TYPE_PLAYERS =>
println("Please enter the names of the players, separated by a comma.")
Some(true)
case SHOW_FINISHED_MATCH =>
if (event.objects.length != 1 || !event.objects.head.isInstanceOf[Player]) {
None
} else {
TUIUtil.clearConsole()
println(s"The match is over. The winner is ${event.objects.head.asInstanceOf[Player]}")
Some(true)
}
}
case event: ShowPlayerStatus =>
val player = event.player
event.status match {
case SHOW_TURN =>
println("It's your turn, " + player.name + ".")
Some(true)
case SHOW_PLAY_CARD =>
println("Which card do you want to play?")
Some(true)
case SHOW_DOG_PLAY_CARD =>
if(event.objects.length != 1 || !event.objects.head.isInstanceOf[Boolean]) {
None
} else {
println("You are using your dog life. Do you want to play your final card now?")
if (event.objects.head.asInstanceOf[Boolean]) {
println("You have to play your final card this round!")
println("Please enter y to play your final card.")
Some(true)
} else {
println("Please enter y/n to play your final card.")
Some(true)
}
}
case SHOW_TIE_NUMBERS =>
if(event.objects.length != 1 || !event.objects.head.isInstanceOf[Int]) {
None
} else {
println(s"${player.name} enter a number between 1 and ${event.objects.head.asInstanceOf[Int]}.")
Some(true)
}
case SHOW_TRUMPSUIT_OPTIONS =>
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()
Some(true)
case SHOW_NOT_PLAYED =>
println(s"Player ${event.player} decided to not play his card")
Some(true)
case SHOW_WON_PLAYER_TRICK =>
println(s"${event.player.name} won the trick.")
TUIUtil.clearConsole(2)
Some(true)
}
case event: ShowRoundStatus =>
event.status match {
case SHOW_START_ROUND =>
TUIUtil.clearConsole()
println(s"Starting a new round. The trump suit is ${event.currentRound.trumpSuit}.")
TUIUtil.clearConsole(2)
Some(true)
case WON_ROUND =>
if (event.objects.length != 1 || !event.objects.head.isInstanceOf[Player]) {
None
} else {
println(s"${event.objects.head.asInstanceOf[Player].name} won the round.")
Some(true)
}
case PLAYERS_OUT =>
println("The following players are out of the game:")
event.currentRound.players_out.foreach(p => {
println(p.name)
})
Some(true)
}
case event: ShowErrorStatus =>
event.status match {
case INVALID_NUMBER =>
println("Please enter a valid number.")
Some(true)
case NOT_A_NUMBER =>
println("Please enter a valid number.")
Some(true)
case INVALID_INPUT =>
println("Please enter a valid input")
Some(true)
case INVALID_NUMBER_OF_PLAYERS =>
println("Please enter at least two names.")
Some(true)
case IDENTICAL_NAMES =>
println("Please enter unique names.")
Some(true)
case INVALID_NAME_FORMAT =>
println("Please enter valid names. Those can not be empty, shorter than 2 or longer then 10 characters.")
Some(true)
case WRONG_CARD =>
if(event.objects.length != 1 || !event.objects.head.isInstanceOf[Card]) {
None
} else {
println(f"You have to play a card of suit: ${event.objects.head.asInstanceOf[Card].suit}\n")
Some(true)
}
}
case event: RequestNumberEvent =>
Some(Try {
val input = readLine()
val number = input.toInt
if (number < event.min || number > event.max) {
throw new IllegalArgumentException(s"Number must be between ${event.min} and ${event.max}")
}
number
})
case event: RequestCardEvent =>
Some(Try {
val card = readLine().toInt - 1
if (card < 0 || card >= event.hand.cards.length) {
throw new IllegalArgumentException(s"Number has to be between 1 and ${event.hand.cards.length}")
} else {
event.hand.cards(card)
}
})
case event: RequestDogPlayCardEvent =>
Some(Try {
val card = readLine()
if (card.equalsIgnoreCase("y")) {
Some(event.hand.cards.head)
} else if (card.equalsIgnoreCase("n") && !event.needs_to_play) {
None
} else {
throw new IllegalArgumentException("Didn't want to play card but had to")
}
}
)
case event: RequestPickTrumpsuitEvent =>
Some(Try {
val suit = readLine().toInt
suit match {
case 1 => Suit.Hearts
case 2 => Suit.Diamonds
case 3 => Suit.Clubs
case 4 => Suit.Spades
case _ => throw IllegalArgumentException("Didn't enter a number between 1 and 4")
}
})
case event: ShowCurrentTrickEvent =>
TUIUtil.clearConsole()
val sb = new StringBuilder()
sb.append("Current Trick:\n")
sb.append("Trump-Suit: " + event.round.trumpSuit + "\n")
if (event.trick.get_first_card().isDefined) {
sb.append(s"Suit to play: ${event.trick.get_first_card().get.suit}\n")
}
for ((card, player) <- event.trick.cards) {
sb.append(s"${player.name} played ${card.toString}\n")
}
println(sb.toString())
Some(true)
case _ => None
}
}
object TUICards {
def renderCardAsString(card: Card): Vector[String] = {
if (card.cardValue == CardValue.Ten) {
return Vector(
s"┌─────────┐",
s"${cardColour(card.suit)}${Console.BOLD}${card.cardValue.cardType()}${Console.RESET}",
"│ │",
s"${cardColour(card.suit)}${Console.BOLD}${card.suit.cardType()}${Console.RESET}",
"│ │",
s"${cardColour(card.suit)}${Console.BOLD}${card.cardValue.cardType()}${Console.RESET}",
s"└─────────┘"
)
}
Vector(
s"┌─────────┐",
s"${cardColour(card.suit)}${Console.BOLD}${card.cardValue.cardType()}${Console.RESET}",
"│ │",
s"${cardColour(card.suit)}${Console.BOLD}${card.suit.cardType()}${Console.RESET}",
"│ │",
s"${cardColour(card.suit)}${Console.BOLD}${card.cardValue.cardType()}${Console.RESET}",
s"└─────────┘"
)
}
private[ui] def cardColour(suit: Suit): String = suit match {
case Suit.Hearts | Suit.Diamonds => Console.RED
case Suit.Clubs | Suit.Spades => Console.BLACK
}
def renderHandEvent(hand: Hand, showNumbers: Boolean): Vector[String] = {
val cardStrings = hand.cards.map(TUICards.renderCardAsString)
var zipped = cardStrings.transpose
if (showNumbers) zipped = {
List.tabulate(hand.cards.length) { i =>
s" ${i + 1} "
}
} :: zipped
zipped.map(_.mkString(" ")).toVector
}
}
private object TUIUtil {
def clearConsole(lines: Int = 32): Int = {
var l = 0
for (_ <- 0 until lines) {
println()
l += 1
}
l
}
}
@tailrec
override def initial: Boolean = {
if (init) {
return false
}
init = true
TUIUtil.clearConsole()
println("Welcome to Knockout Whist!")
println()
println("Please select an option:")
println("1. Start a new match")
println("2. Exit")
Try{
readLine().toInt
} match {
case Success(value) =>
value match {
case 1 =>
MatchControl.startMatch()
init = false
initial
case 2 =>
println("Exiting the game.")
true
case _ =>
ControlHandler.invoke(ShowErrorStatus(INVALID_NUMBER))
init = false
initial
}
case Failure(_) =>
ControlHandler.invoke(ShowErrorStatus(NOT_A_NUMBER))
init = false
initial
}
}
}

View File

@@ -1,9 +1,10 @@
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

View File

@@ -0,0 +1,18 @@
package de.knockoutwhist.utils
import de.knockoutwhist.events.util.DelayEvent
import de.knockoutwhist.utils.events.{EventListener, ReturnableEvent}
object DelayHandler extends EventListener {
private[knockoutwhist] var activateDelay: Boolean = true
override def listen[R](event: ReturnableEvent[R]): Option[R] = {
event match {
case event: DelayEvent =>
if(activateDelay) Thread.sleep(event.delay)
Some(true)
case _ => None
}
}
}

View File

@@ -0,0 +1,30 @@
package de.knockoutwhist.utils.events
import scala.collection.mutable
abstract class EventHandler {
private[events] var listeners: mutable.ListBuffer[EventListener] = mutable.ListBuffer[EventListener]()
def addListener(listener: EventListener): Int = {
listeners = (listeners += listener).sorted
listeners.size
}
def removeListener(listener: EventListener): Int = {
listeners = (listeners -= listener).sorted
listeners.size
}
def invoke[R](event: ReturnableEvent[R]): R = {
event match {
case simpleEvent: SimpleEvent =>
val result = listeners.map(_.listen(simpleEvent)).filter(_.isDefined).map(_.get).toList
if(result.isEmpty) return false
result.reduce((a,b) => a && b)
case returnableEvent: ReturnableEvent[R] =>
val result = listeners.view.map(_.listen(returnableEvent)).find(_.isDefined).flatten
result.getOrElse(throw new IllegalStateException("No listener returned a result"))
}
}
}

View File

@@ -0,0 +1,21 @@
package de.knockoutwhist.utils.events
enum Priority extends Ordered[Priority] :
case High, Normal, Low
override def compare(that: Priority): Int = {
if this == that then 0
else if this == High then 1
else if that == High then -1
else if this == Low then -1
else 1
}
end Priority
trait EventListener extends Ordered[EventListener] {
def priority: Priority = Priority.Normal
def listen[R](event: ReturnableEvent[R]): Option[R]
override def compare(that: EventListener): Int = that.priority.compare(priority)
}

View File

@@ -0,0 +1,7 @@
package de.knockoutwhist.utils.events
trait ReturnableEvent[R] {
def id: String
}

View File

@@ -0,0 +1,3 @@
package de.knockoutwhist.utils.events
trait SimpleEvent extends ReturnableEvent[Boolean]

View File

@@ -0,0 +1,37 @@
package de.knockoutwhist
import de.knockoutwhist.events.util.DelayEvent
import de.knockoutwhist.utils.DelayHandler
import de.knockoutwhist.utils.events.EventHandler
import org.scalatest.concurrent.*
import org.scalatest.matchers.should.Matchers
import org.scalatest.time.SpanSugar.*
import org.scalatest.wordspec.AnyWordSpec
import scala.language.postfixOps
class DelayHandlerTests extends AnyWordSpec with TimeLimits with Matchers {
private val eventHandler = new EventHandler() {}
eventHandler.addListener(DelayHandler)
"A delay event" should {
val delayEvent = DelayEvent(100)
"be able to be created" in {
delayEvent should not be null
}
"have the correct id" in {
delayEvent.id should be ("DelayEvent")
}
"have the correct delay" in {
delayEvent.delay should be (100)
}
"not be longer than 120ms if it's set to 100ms" in {
DelayHandler.activateDelay = true
failAfter(200 millis) {
eventHandler.invoke(delayEvent)
}
}
}
}

View File

@@ -1,17 +1,21 @@
package de.knockoutwhist
import de.knockoutwhist.testutils.TestUtil
import de.knockoutwhist.ui.tui.TUIMain
import org.scalatest.funsuite.AnyFunSuite
class MainTests extends AnyFunSuite {
test("Main should be able to go to the main menu") {
TestUtil.disableDelay()
TUIMain.init = false
TestUtil.simulateInput("2\n") {
KnockOutWhist.main(Array())
}
}
test("Main should be able to be executed twice") {
TestUtil.disableDelay()
TestUtil.simulateInput("2\n") {
assertThrows[IllegalStateException] {
KnockOutWhist.main(Array())

View File

@@ -1,9 +1,11 @@
package de.knockoutwhist
import de.knockoutwhist.cards.{CardTests, DeckTests, HandTests}
import de.knockoutwhist.control.text.{TextMatchControllerTests, TextPlayerControllerTests}
import de.knockoutwhist.control.{MatchControllerTests, PlayerControllerTests}
import de.knockoutwhist.events.TestAllEvent
import de.knockoutwhist.player.PlayerTests
import de.knockoutwhist.rounds.{GameplayTests, MatchTests, TrickTests}
import de.knockoutwhist.utils.events.EventTests
import de.knockoutwhist.utils.{ImplicitTests, QueueTests}
import org.scalatest.Sequential
@@ -15,10 +17,12 @@ class TestSequence extends Sequential(
new QueueTests(),
new ImplicitTests(),
new PlayerTests(),
new TextPlayerControllerTests(),
new TextMatchControllerTests(),
new PlayerControllerTests(),
new MatchControllerTests(),
new CardTests(),
new DeckTests(),
new HandTests(),
) {}
new EventTests(),
new TestAllEvent(),
new DelayHandlerTests()
)

View File

@@ -1,11 +1,16 @@
package de.knockoutwhist.cards
import de.knockoutwhist.utils.events.EventHandler
import de.knockoutwhist.cards.CardValue.{Ace, Ten}
import de.knockoutwhist.events.cards.RenderHandEvent
import de.knockoutwhist.testutils.TestUtil
import de.knockoutwhist.ui.tui.TUIMain.TUICards.renderCardAsString
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class CardTests extends AnyWordSpec with Matchers{
"A card" should {
TestUtil.disableDelay()
"be displayed with correct value and Suit" in {
val card = Card(CardValue.Ace, Suit.Spades)
val e = "Ace of Spades"
@@ -22,7 +27,7 @@ class CardTests extends AnyWordSpec with Matchers{
s"${Console.BLACK}${Console.BOLD}A${Console.RESET}",
"└─────────┘"
)
card.renderAsString() shouldBe expectedResult
renderCardAsString(Card(CardValue.Ace, Suit.Spades)) shouldBe expectedResult
}
"can be rendered for CardValue Ten" in {
val card = Card(CardValue.Ten, Suit.Spades)
@@ -35,7 +40,7 @@ class CardTests extends AnyWordSpec with Matchers{
s"${Console.BLACK}${Console.BOLD}10${Console.RESET}",
"└─────────┘"
)
card.renderAsString() shouldBe expectedResult
renderCardAsString(Card(CardValue.Ten, Suit.Spades)) shouldBe expectedResult
}
"be able to reset the order" in {
CardManager.shuffleAndReset()

View File

@@ -1,5 +1,6 @@
package de.knockoutwhist.cards
import de.knockoutwhist.testutils.TestUtil
import org.scalatest.matchers.must.Matchers
import org.scalatest.matchers.should.Matchers.{should, shouldBe}
import org.scalatest.wordspec.AnyWordSpec
@@ -7,6 +8,7 @@ import org.scalatest.wordspec.AnyWordSpec
class DeckTests extends AnyWordSpec with Matchers{
"A deck" should {
TestUtil.disableDelay()
"not be empty" in {
CardManager.cardContainer must not be empty
}

View File

@@ -1,6 +1,8 @@
package de.knockoutwhist.cards
import de.knockoutwhist.cards.{Card, CardValue, Hand, Suit}
import de.knockoutwhist.testutils.TestUtil
import de.knockoutwhist.ui.tui.TUIMain.TUICards.renderHandEvent
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
@@ -9,6 +11,7 @@ import scala.collection.mutable.ListBuffer
class HandTests extends AnyWordSpec with Matchers {
"The hand" should {
TestUtil.disableDelay()
"be able to remove cards from its hand" in {
val handholder = ListBuffer[Card]()
handholder.addOne(Card(CardValue.Ace, Suit.Spades))
@@ -48,7 +51,7 @@ class HandTests extends AnyWordSpec with Matchers {
s"${Console.BLACK}${Console.BOLD}A${Console.RESET}│ │ ${Console.RED}${Console.BOLD}Q${Console.RESET}",
"└─────────┘ └─────────┘"
)
hand.renderAsString() shouldBe expectedResult
renderHandEvent(Hand(List(Card(CardValue.Ace, Suit.Spades), Card(CardValue.Queen, Suit.Diamonds))), false) shouldBe expectedResult
}
}

View File

@@ -1,68 +1,63 @@
package de.knockoutwhist.control.text
package de.knockoutwhist.control
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.control.{MatchControl, RoundControl, TrickControl}
import de.knockoutwhist.player.Player
import de.knockoutwhist.rounds.{Match, Round, Trick}
import de.knockoutwhist.testutils.TestUtil
import de.knockoutwhist.ui.tui.TUIMain
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()
}
}
}
}
class MatchControllerTests extends AnyWordSpec with Matchers {
"The enter players function" should {
TestUtil.disableDelay()
"throw no exception" in {
TestUtil.cancelOut() {
TestUtil.simulateInput("foo,bar\n") {
TextMatchControl.enterPlayers() should be (List(Player("foo"), Player("bar")))
MatchControl.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")))
MatchControl.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")))
MatchControl.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")))
MatchControl.enterPlayers() should be (List(Player("bar"), Player("foo")))
}
}
}
}
"The control round function" should {
TestUtil.disableDelay()
"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)
MatchControl.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)))
RoundControl.controlRound(matchImpl).winner should be (players.head).or(be (players(1)))
}
}
}
@@ -73,45 +68,48 @@ class TextMatchControllerTests extends AnyWordSpec with Matchers {
CardManager.shuffleAndReset()
CardManager.resetOrder()
TextMatchControl.playerQueue = CustomPlayerQueue[Player](players.toArray[Player], 0)
MatchControl.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)))
RoundControl.controlRound(matchImpl).winner should be(players.head).or(be(players(1)))
}
}
}
}
"The next round function" should {
TestUtil.disableDelay()
"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)
MatchControl.playerQueue = CustomPlayerQueue[Player](players.toArray[Player], 0)
TestUtil.cancelOut() {
TestUtil.simulateInput("1\n1\n1\n") {
TextMatchControl.controlRound(matchImpl)
TextMatchControl.nextRound(matchImpl) should be (null)
RoundControl.controlRound(matchImpl)
RoundControl.nextRound(matchImpl) should be (null)
}
}
}
}
"The next trick function" should {
TestUtil.disableDelay()
"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)
MatchControl.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)
val round = RoundControl.controlRound(matchImpl)
TrickControl.nextTrick(round) should be (null)
}
}
}
}
"The controlSuit function" should {
TestUtil.disableDelay()
"check if a player can play from the correct suit but doesnt" in {
val player1 = Player("Gunter")
val player2 = Player("Peter")
@@ -121,18 +119,19 @@ class TextMatchControllerTests extends AnyWordSpec with Matchers {
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)
TrickControl.playCard(trick, round, Card(Ace, Suit.Hearts), player2)
TestUtil.enableDebugMode()
TextMatchControl.playerQueue = CustomPlayerQueue[Player](players.toArray[Player], 0)
MatchControl.playerQueue = CustomPlayerQueue[Player](players.toArray[Player], 0)
TestUtil.cancelOut() {
TestUtil.simulateInput("1\n2\n") {
val card = TextMatchControl.controlSuitplayed(trick, player1)
val card = TrickControl.controlSuitplayed(trick, player1)
}
}
}
}
"The control Trick function" should {
TestUtil.disableDelay()
"return the other player if the dog decides not to play" in {
val foo = Player("foo")
foo.doglife = true
@@ -142,11 +141,11 @@ class TextMatchControllerTests extends AnyWordSpec with Matchers {
val players = List(foo, bar)
val matchImpl = Match(players, 2)
TestUtil.enableDebugMode()
TextMatchControl.playerQueue = CustomPlayerQueue[Player](players.toArray[Player], 0)
MatchControl.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)
val finalTrick = TrickControl.controlTrick(round)
finalTrick.winner should be(bar)
}
}
@@ -164,11 +163,11 @@ class TextMatchControllerTests extends AnyWordSpec with Matchers {
bar.provideHand(CardManager.createHand(3))
val players = List(foo, bar)
val matchImpl = Match(players, 2)
TextMatchControl.playerQueue = CustomPlayerQueue[Player](players.toArray[Player], 0)
MatchControl.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)
val finalTrick = TrickControl.controlTrick(round)
finalTrick.winner should be(bar)
}
}

View File

@@ -1,22 +1,24 @@
package de.knockoutwhist.control.text
package de.knockoutwhist.control
import de.knockoutwhist.cards.{CardManager, Hand}
import de.knockoutwhist.cards.Suit._
import de.knockoutwhist.cards.Suit.*
import de.knockoutwhist.control.PlayerControl
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 {
class PlayerControllerTests extends AnyWordSpec with Matchers {
"The text player controller play function" should {
TestUtil.disableDelay()
CardManager.shuffleAndReset()
TestUtil.cancelOut() {
"throw an exception of the player has no hand" in {
assertThrows[IllegalStateException] {
assertThrows[NoSuchElementException] {
TestUtil.simulateInput("1\n") {
TextPlayerControl.playCard(Player("Foo"))
PlayerControl.playCard(Player("Foo"))
}
}
}
@@ -26,7 +28,7 @@ class TextPlayerControllerTests extends AnyWordSpec with Matchers {
val hand = CardManager.createHand(1)
player.provideHand(hand)
val card = TestUtil.simulateInput("0\na\n1\n") {
TextPlayerControl.playCard(player)
PlayerControl.playCard(player)
}
card should be(hand.cards.head)
}
@@ -36,7 +38,7 @@ class TextPlayerControllerTests extends AnyWordSpec with Matchers {
val hand = CardManager.createHand(1)
player.provideHand(hand)
val card = TestUtil.simulateInput("1\n") {
TextPlayerControl.playCard(player)
PlayerControl.playCard(player)
}
card should be(hand.cards.head)
}
@@ -51,10 +53,10 @@ class TextPlayerControllerTests extends AnyWordSpec with Matchers {
val round = new Round(Spades, null, List(player), false)
TestUtil.cancelOut() {
"throw an exception of the player has no hand" in {
assertThrows[IllegalStateException] {
assertThrows[NoSuchElementException] {
TestUtil.simulateInput("y\n") {
TestUtil.disableDebugMode()
TextPlayerControl.dogplayCard(Player("Foo"), round)
PlayerControl.dogplayCard(Player("Foo"), round)
}
}
}
@@ -65,7 +67,7 @@ class TextPlayerControllerTests extends AnyWordSpec with Matchers {
val hand = CardManager.createHand(1)
player.provideHand(hand)
val card = TestUtil.simulateInput("a\ny\n") {
TextPlayerControl.dogplayCard(player, round)
PlayerControl.dogplayCard(player, round)
}
card should be(Some(hand.cards.head))
}
@@ -76,7 +78,7 @@ class TextPlayerControllerTests extends AnyWordSpec with Matchers {
val hand = CardManager.createHand(1)
player.provideHand(hand)
val card = TestUtil.simulateInput("y\n") {
TextPlayerControl.dogplayCard(player, round)
PlayerControl.dogplayCard(player, round)
}
card should be(Some(hand.cards.head))
}
@@ -87,7 +89,7 @@ class TextPlayerControllerTests extends AnyWordSpec with Matchers {
val hand = CardManager.createHand(1)
player.provideHand(hand)
val card = TestUtil.simulateInput("n\n") {
TextPlayerControl.dogplayCard(player, round)
PlayerControl.dogplayCard(player, round)
}
card should be(None)
}
@@ -101,7 +103,7 @@ class TextPlayerControllerTests extends AnyWordSpec with Matchers {
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)
PlayerControl.dogplayCard(player1, round2)
}
card should be(Some(player1.currentHand().get.cards.head))
}
@@ -116,7 +118,7 @@ class TextPlayerControllerTests extends AnyWordSpec with Matchers {
val player2 = Player("Bar")
val players = List(player1, player2)
val winner = TestUtil.simulateInput("1\n2\n") {
TextPlayerControl.determineWinnerTie(players)
PlayerControl.determineWinnerTie(players)
}
winner should be(player2).or(be(player1))
}
@@ -127,7 +129,7 @@ class TextPlayerControllerTests extends AnyWordSpec with Matchers {
val player2 = Player("Bar")
val players = List(player1, player2)
val winner = TestUtil.simulateInput("1\n13\n5\n1\n") {
TextPlayerControl.determineWinnerTie(players)
PlayerControl.determineWinnerTie(players)
}
winner should be(player2)
}
@@ -138,7 +140,7 @@ class TextPlayerControllerTests extends AnyWordSpec with Matchers {
val player2 = Player("Bar")
val players = List(player1, player2)
val winner = TestUtil.simulateInput("13\n1\n") {
TextPlayerControl.determineWinnerTie(players)
PlayerControl.determineWinnerTie(players)
}
winner should be(player1)
}
@@ -149,7 +151,7 @@ class TextPlayerControllerTests extends AnyWordSpec with Matchers {
val player2 = Player("Bar")
val players = List(player1, player2)
val winner = TestUtil.simulateInput("a\n200\n1\n2\n") {
TextPlayerControl.determineWinnerTie(players)
PlayerControl.determineWinnerTie(players)
}
winner should be(player2)
}
@@ -163,7 +165,7 @@ class TextPlayerControllerTests extends AnyWordSpec with Matchers {
val player = Player("Foo")
player.provideHand(CardManager.createHand(4))
val suit = TestUtil.simulateInput("4\n") {
TextPlayerControl.pickNextTrumpsuit(player)
PlayerControl.pickNextTrumpsuit(player)
}
suit should be(Spades)
}
@@ -172,7 +174,7 @@ class TextPlayerControllerTests extends AnyWordSpec with Matchers {
val player = Player("Foo")
player.provideHand(CardManager.createHand(4))
val suit = TestUtil.simulateInput("1\n") {
TextPlayerControl.pickNextTrumpsuit(player)
PlayerControl.pickNextTrumpsuit(player)
}
suit should be(Hearts)
}
@@ -181,7 +183,7 @@ class TextPlayerControllerTests extends AnyWordSpec with Matchers {
val player = Player("Foo")
player.provideHand(CardManager.createHand(4))
val suit = TestUtil.simulateInput("2\n") {
TextPlayerControl.pickNextTrumpsuit(player)
PlayerControl.pickNextTrumpsuit(player)
}
suit should be(Diamonds)
}
@@ -190,7 +192,7 @@ class TextPlayerControllerTests extends AnyWordSpec with Matchers {
val player = Player("Foo")
player.provideHand(CardManager.createHand(4))
val suit = TestUtil.simulateInput("3\n") {
TextPlayerControl.pickNextTrumpsuit(player)
PlayerControl.pickNextTrumpsuit(player)
}
suit should be(Clubs)
}
@@ -199,37 +201,37 @@ class TextPlayerControllerTests extends AnyWordSpec with Matchers {
val player = Player("Foo")
player.provideHand(CardManager.createHand(4))
val suit = TestUtil.simulateInput("a\n10\n1\n") {
TextPlayerControl.pickNextTrumpsuit(player)
PlayerControl.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.")
}
}
}
// "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,497 @@
package de.knockoutwhist.events
import de.knockoutwhist.KnockOutWhist
import de.knockoutwhist.cards.CardValue.{Queen, Two}
import de.knockoutwhist.cards.Suit.Hearts
import de.knockoutwhist.cards.{Card, CardManager, CardValue, Hand, Suit}
import de.knockoutwhist.control.{RoundControl, TrickControl}
import de.knockoutwhist.events.GLOBAL_STATUS.*
import de.knockoutwhist.events.PLAYER_STATUS.*
import de.knockoutwhist.events.ERROR_STATUS.*
import de.knockoutwhist.events.ROUND_STATUS.*
import de.knockoutwhist.events.cards.{RenderHandEvent, ShowTieCardsEvent}
import de.knockoutwhist.events.directional.{RequestCardEvent, RequestDogPlayCardEvent, RequestNumberEvent, RequestPickTrumpsuitEvent}
import de.knockoutwhist.events.round.ShowCurrentTrickEvent
import de.knockoutwhist.player.Player
import de.knockoutwhist.rounds.{Match, Round}
import de.knockoutwhist.testutils.{TestUtil, TestUtil as shouldBe}
import de.knockoutwhist.ui.tui.TUIMain
import de.knockoutwhist.utils.events.{EventHandler, SimpleEvent}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import scala.collection.mutable.ListBuffer
import scala.compiletime.uninitialized
import scala.util.{Failure, Success}
class TestAllEvent extends AnyWordSpec with Matchers {
private val eventHandler = new EventHandler() {}
eventHandler.addListener(TUIMain)
"The render hand event" should {
TestUtil.disableDelay()
CardManager.resetOrder()
val event1 = RenderHandEvent(CardManager.createHand(1), true)
val event2 = RenderHandEvent(CardManager.createHand(1), false)
"be able to be created" in {
event1 should not be null
}
"have the correct id" in {
event1.id should be ("RenderHandEvent")
}
"have the correct parameter (showing numbers)" in {
event1.showNumbers should be (true)
}
"have the correct parameter (not showing numbers)" in {
event2.showNumbers should be (false)
}
"be able to be handled" in {
TestUtil.cancelOut() {
eventHandler.invoke(event1) should be(true)
eventHandler.invoke(event2) should be(true)
}
}
}
"The show tie cards event" should {
TestUtil.disableDelay()
val event = ShowTieCardsEvent(List((Player("Foo"), Card(Two,Suit.Hearts))))
"be able to be created" in {
event should not be null
}
"have the correct id" in {
event.id should be ("ShowTieCardsEvent")
}
"have the correct player" in {
event.card.head._1 should be (Player("Foo"))
}
"have the correct card" in {
event.card.head._2.cardValue should be (Two)
event.card.head._2.suit should be (Suit.Hearts)
}
"be able to be handled" in {
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
}
"The request card event" should {
TestUtil.disableDelay()
val hand = CardManager.createHand(1)
val event = RequestCardEvent(hand)
"be able to be created" in {
event should not be null
}
"have the correct id" in {
event.id should be("RequestCardEvent")
}
"have the correct hand" in {
event.hand should be(hand)
}
"be able to be handled" in {
TestUtil.cancelOut() {
TestUtil.simulateInput("0\n") {
eventHandler.invoke(event)
} shouldBe a [Failure[?]]
TestUtil.simulateInput("5\n") {
eventHandler.invoke(event)
} shouldBe a[Failure[?]]
val result = TestUtil.simulateInput("1\n") {
eventHandler.invoke(event)
}
result should be (Success(hand.cards.head))
}
}
}
"The request number event" should {
TestUtil.disableDelay()
val event = RequestNumberEvent(6, 12)
"be able to be created" in {
event should not be null
}
"have the correct id" in {
event.id should be("RequestNumberEvent")
}
"have the correct min & max" in {
event.min should be(6)
event.max should be(12)
}
"be able to be handled" in {
TestUtil.cancelOut() {
TestUtil.simulateInput("0\n") {
eventHandler.invoke(event)
} shouldBe a [Failure[?]]
TestUtil.simulateInput("28\n") {
eventHandler.invoke(event)
} shouldBe a[Failure[?]]
val result = TestUtil.simulateInput("8\n") {
eventHandler.invoke(event)
}
result should be (Success(8))
}
}
}
"The RequestDogPlayCardEvent" should {
TestUtil.disableDelay()
val hand = Hand(List(Card(CardValue.Ten, Suit.Spades)))
val event = RequestDogPlayCardEvent(hand, true)
"be able to get created" in {
event should not be null
}
"have the correct ID" in {
event.id should be("RequestDogPlayCardEvent")
}
"return the card played if it was played" in {
TestUtil.simulateInput("y\n") {
eventHandler.invoke(event)
} shouldBe a[Success[?]]
}
"return an exception if player doesn't want to play but has to" in {
TestUtil.simulateInput("n\n") {
eventHandler.invoke(event)
} shouldBe a [Failure[?]]
}
"return None when a player doesn't play a card and is able to do so" in {
val hand2 = Hand(List(Card(CardValue.Ten, Suit.Spades), Card(CardValue.Nine, Suit.Spades)))
val event2 = RequestDogPlayCardEvent(hand2, false)
TestUtil.simulateInput("n\n") {
eventHandler.invoke(event2)
} shouldBe a [Success[?]]
}
}
"The RequestPickTrumpsuitEvent" should {
TestUtil.disableDelay()
val event = RequestPickTrumpsuitEvent()
"be able to get created" in {
event should not be null
}
"have an ID" in {
event.id should be("RequestPickTrumpsuitEvent")
}
"be a Success if the player picks a trumpsuit" in {
TestUtil.simulateInput("1\n") {
eventHandler.invoke(event)
} shouldBe a [Success[?]]
TestUtil.simulateInput("2\n") {
eventHandler.invoke(event)
} shouldBe a[Success[?]]
TestUtil.simulateInput("3\n") {
eventHandler.invoke(event)
} shouldBe a[Success[?]]
TestUtil.simulateInput("4\n") {
eventHandler.invoke(event)
} shouldBe a[Success[?]]
}
"fail if the player doesn't pick a trumpsuit" in {
TestUtil.simulateInput("5\n") {
eventHandler.invoke(event)
} shouldBe a [Failure[?]]
}
}
"The ShowCurrentTrickEvent" should {
TestUtil.disableDelay()
val player1 = Player("Gunter")
val player2 = Player("Peter")
val listplayers = List(player1, player2)
val match1 = Match(listplayers)
val round = RoundControl.create_round(match1)
val trick = TrickControl.create_trick(round)
TrickControl.playCard(trick, round, Card(CardValue.Ten, Suit.Spades), player1)
val event = ShowCurrentTrickEvent(round, trick)
"be able to get created" in {
event should not be null
}
"have an ID" in {
event.id should be("ShowCurrentTrickEvent")
}
"should be true if a trick can be displayed" in {
eventHandler.invoke(event) shouldBe true
}
}
"The show global status event" should {
TestUtil.disableDelay()
var event: ShowGlobalStatus = null
"be able to be created" in {
event = ShowGlobalStatus(SHOW_TIE)
event should not be null
}
"have the correct id" in {
event = ShowGlobalStatus(SHOW_TIE)
event.id should be ("ShowGlobalStatus")
}
"have the correct status" in {
event = ShowGlobalStatus(SHOW_TIE)
event.status should be (SHOW_TIE)
}
"be able to be handled with status SHOW_TIE" in {
event = ShowGlobalStatus(SHOW_TIE)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to return false with status SHOW_TIE_WINNER if arguments mismatch" in {
event = ShowGlobalStatus(SHOW_TIE_WINNER)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(false)
}
}
"be able to return true with status SHOW_TIE_WINNER if arguments match" in {
event = ShowGlobalStatus(SHOW_TIE_WINNER, Player("Foo"))
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to be handled with status SHOW_TIE_TIE" in {
event = ShowGlobalStatus(SHOW_TIE_TIE)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to be handled with status SHOW_START_MATCH" in {
event = ShowGlobalStatus(SHOW_START_MATCH)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to be handled with status SHOW_TYPE_PLAYERS" in {
event = ShowGlobalStatus(SHOW_TYPE_PLAYERS)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to return false with status SHOW_FINISHED_MATCH if arguments mismatch" in {
event = ShowGlobalStatus(SHOW_FINISHED_MATCH)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(false)
}
}
"be able to return true with status SHOW_FINISHED_MATCH if arguments match" in {
event = ShowGlobalStatus(SHOW_FINISHED_MATCH, Player("Foo"))
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
}
"The show player status event" should {
TestUtil.disableDelay()
val player = Player("Foo")
var event: ShowPlayerStatus = null
"be able to be created" in {
event = ShowPlayerStatus(SHOW_TURN, player)
event should not be null
}
"have the correct id" in {
event = ShowPlayerStatus(SHOW_TURN, player)
event.id should be ("ShowPlayerStatus")
}
"have the correct status" in {
event = ShowPlayerStatus(SHOW_TURN, player)
event.status should be (SHOW_TURN)
}
"have the correct player" in {
event = ShowPlayerStatus(SHOW_TURN, player)
event.player should be (player)
}
"be able to be handled with status SHOW_TURN" in {
event = ShowPlayerStatus(SHOW_TURN, player)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to be handled with status SHOW_PLAY_CARD" in {
event = ShowPlayerStatus(SHOW_PLAY_CARD, player)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to return false with status SHOW_DOG_PLAY_CARD if arguments mismatch" in {
event = ShowPlayerStatus(SHOW_DOG_PLAY_CARD, player)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(false)
}
}
"be able to return true with status SHOW_DOG_PLAY_CARD if arguments match" in {
event = ShowPlayerStatus(SHOW_DOG_PLAY_CARD, player, true)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to return false with status SHOW_TIE_NUMBERS if arguments mismatch" in {
event = ShowPlayerStatus(SHOW_TIE_NUMBERS, player)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(false)
}
}
"be able to return true with status SHOW_TIE_NUMBERS if arguments match" in {
event = ShowPlayerStatus(SHOW_TIE_NUMBERS, player, 5)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to be handled with status SHOW_TRUMPSUIT_OPTIONS" in {
event = ShowPlayerStatus(SHOW_TRUMPSUIT_OPTIONS, player)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to be handled with status SHOW_NOT_PLAYED" in {
event = ShowPlayerStatus(SHOW_NOT_PLAYED, player)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to be handled with status SHOW_WON_PLAYER_TRICK" in {
event = ShowPlayerStatus(SHOW_WON_PLAYER_TRICK, player)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
}
"The show round status event" should {
TestUtil.disableDelay()
val round = Round(trumpSuit = Hearts, matchImpl = null, tricklist = ListBuffer(), players_in = null, firstRound = false, players_out = List(Player("Foo")))
var event: ShowRoundStatus = null
"be able to be created" in {
event = ShowRoundStatus(SHOW_START_ROUND, round)
event should not be null
}
"have the correct id" in {
event = ShowRoundStatus(SHOW_START_ROUND, round)
event.id should be ("ShowRoundStatus")
}
"have the correct status" in {
event = ShowRoundStatus(SHOW_START_ROUND, round)
event.status should be (SHOW_START_ROUND)
}
"have the correct round" in {
event = ShowRoundStatus(SHOW_START_ROUND, round)
event.currentRound should be (round)
}
"be able to be handled with status SHOW_START_ROUND" in {
event = ShowRoundStatus(SHOW_START_ROUND, round)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to return false with status WON_ROUND if arguments mismatch" in {
event = ShowRoundStatus(WON_ROUND, round)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(false)
}
}
"be able to return true with status WON_ROUND if arguments match" in {
event = ShowRoundStatus(WON_ROUND, round, Player("Foo"))
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to be handled with status PLAYERS_OUT" in {
event = ShowRoundStatus(PLAYERS_OUT, round)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
}
"The error status event" should {
TestUtil.disableDelay()
var event: ShowErrorStatus = null
"be able to be created" in {
event = ShowErrorStatus(INVALID_NUMBER)
event should not be null
}
"have the correct id" in {
event = ShowErrorStatus(INVALID_NUMBER)
event.id should be ("ShowErrorStatus")
}
"have the correct status" in {
event = ShowErrorStatus(INVALID_NUMBER)
event.status should be (INVALID_NUMBER)
}
"be able to be handled with status INVALID_NUMBER" in {
event = ShowErrorStatus(INVALID_NUMBER)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to be handled with status NOT_A_NUMBER" in {
event = ShowErrorStatus(NOT_A_NUMBER)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to be handled with status INVALID_INPUT" in {
event = ShowErrorStatus(INVALID_INPUT)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to be handled with status INVALID_NUMBER_OF_PLAYERS" in {
event = ShowErrorStatus(INVALID_NUMBER_OF_PLAYERS)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to be handled with status IDENTICAL_NAMES" in {
event = ShowErrorStatus(IDENTICAL_NAMES)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to be handled with status INVALID_NAME_FORMAT" in {
event = ShowErrorStatus(INVALID_NAME_FORMAT)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
"be able to return false with status WRONG_CARD if arguments mismatch" in {
event = ShowErrorStatus(WRONG_CARD)
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(false)
}
}
"be able to return true with status WRONG_CARD if arguments match" in {
event = ShowErrorStatus(WRONG_CARD, Card(CardValue.Queen, Suit.Hearts))
TestUtil.cancelOut() {
eventHandler.invoke(event) should be(true)
}
}
}
"An Event" should {
TestUtil.disableDelay()
"return None if it doesn't exist" in {
val event = new SimpleEvent {
override def id: String = "ShowEnte"
}
eventHandler.invoke(event) shouldBe false
}
}
/*"The pick next trumpsuit event" should {
val event = PickNextTrumpsuitEvent(Player("Foo"))
"be able to be created" in {
event should not be null
}
"have the correct id" in {
event.id should be ("PickNextTrumpsuitEvent")
}
"have the correct player" in {
event.player should be (Player("Foo"))
}
"be able to be handled" in {
TestUtil.enableDebugMode()
CardManager.resetOrder()
TestUtil.simulateInput("1\n") {
eventHandler.invoke(event)
} should be (Suit.Hearts)
}
}*/
}

View File

@@ -1,12 +1,14 @@
package de.knockoutwhist.player
import de.knockoutwhist.cards.{Card, CardValue, Hand, Suit}
import de.knockoutwhist.testutils.TestUtil
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class PlayerTests extends AnyWordSpec with Matchers {
"A player" should {
TestUtil.disableDelay()
"be able to have a hand" in {
val card = Card(CardValue.Ace, Suit.Spades)
val card2 = Card(CardValue.Ten, Suit.Spades)

View File

@@ -1,8 +1,8 @@
package de.knockoutwhist.rounds
import de.knockoutwhist.cards.CardManager
import de.knockoutwhist.control.text.TextMatchControl
import de.knockoutwhist.testutils.TestUtil
import de.knockoutwhist.ui.tui.TUIMain
import org.scalatest.matchers.must.Matchers
import org.scalatest.matchers.should.Matchers.{should, shouldBe}
import org.scalatest.wordspec.AnyWordSpec
@@ -14,11 +14,12 @@ class GameplayTests extends AnyWordSpec with Matchers {
"The Match Control" must {
"not throw an exception" in {
TestUtil.enableDebugMode()
TestUtil.disableDelay()
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()
TestUtil.simulateInput("a\n5\n1\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\ny\n1\n1\n1\n2\n") {
TUIMain.initial
}
}
}

View File

@@ -2,6 +2,7 @@ package de.knockoutwhist.rounds
import de.knockoutwhist.KnockOutWhist
import de.knockoutwhist.cards.{Card, CardManager, CardValue, Suit}
import de.knockoutwhist.control.{MatchControl, PlayerControl, RoundControl, TrickControl}
import de.knockoutwhist.player.Player
import de.knockoutwhist.testutils.TestUtil
import org.scalatest.matchers.must.Matchers
@@ -15,82 +16,74 @@ import scala.collection.mutable.ListBuffer
class MatchTests extends AnyWordSpec with Matchers{
"A Match" should {
TestUtil.disableDelay()
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 round1 = RoundControl.create_round(match1)
val trumpsuit = round1.trumpSuit
val trick1 = round1.create_trick()
val trick1 = TrickControl.create_trick(round1)
val playedcard1 = TestUtil.simulateInput("1\n") {
KnockOutWhist.matchControl.playerControl.playCard(player1)
PlayerControl.playCard(player1)
}
trick1.playCard(playedcard1, player1)
TrickControl.playCard(trick1, round1, playedcard1, player1)
val playedcard2 = TestUtil.simulateInput("1\n") {
KnockOutWhist.matchControl.playerControl.playCard(player2)
PlayerControl.playCard(player2)
}
trick1.playCard(playedcard2, player2)
TrickControl.playCard(trick1, round1, playedcard2, player2)
"return the players ingame in players_remaining" in {
round1.remainingPlayers() should be(player_list)
RoundControl.remainingPlayers(round1) should be(player_list)
}
val rtrick1 = trick1.wonTrick()
round1.finalizeRound(true)
val rtrick1 = TrickControl.wonTrick(trick1, round1)
RoundControl.finalizeRound(round1, match1, true)
val round2 = TestUtil.simulateInput("1\n") {
match1.create_round()
RoundControl.create_round(match1)
}
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 trick2: Trick = TrickControl.create_trick(round2)
val playedcard3 = TestUtil.simulateInput("1\n") {
KnockOutWhist.matchControl.playerControl.playCard(player1)
PlayerControl.playCard(player1)
}
trick1.playCard(playedcard3, player1)
TrickControl.playCard(trick1, round2, playedcard3, player1)
val playedcard4 = TestUtil.simulateInput("1\n") {
KnockOutWhist.matchControl.playerControl.playCard(player2)
PlayerControl.playCard(player2)
}
trick2.playCard(playedcard4, player2)
TrickControl.playCard(trick2, round2, playedcard4, player2)
"be able to return the current trick of the round" in {
round2.get_current_trick() should be(trick2)
round2.get_current_trick().get should be(trick2)
}
val rtrick2 = trick2.wonTrick()
val rtrick2 = TrickControl.wonTrick(trick2, round2)
"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))
RoundControl.isOver(round2) shouldBe false
}
"be able to tell if a dog needs to play" in {
round2.dogNeedsToPlay shouldBe false
RoundControl.dogNeedsToPlay(round2) shouldBe false
}
"error out if a round is finalized without all tricks played" in {
assertThrows[IllegalStateException] {
round2.finalizeRound()
RoundControl.finalizeRound(round2, match1)
}
}
round2.finalizeRound(true)
RoundControl.finalizeRound(round2, match1, true)
val round3 = TestUtil.simulateInput("1\n") {
match1.create_round()
RoundControl.create_round(match1)
}
val trick3 = round3.create_trick()
val trick3 = TrickControl.create_trick(round3)
val playedcard5 = TestUtil.simulateInput("1\n") {
KnockOutWhist.matchControl.playerControl.playCard(player1)
PlayerControl.playCard(player1)
}
trick1.playCard(playedcard5, player1)
TrickControl.playCard(trick1, round3, playedcard5, player1) //stand trick1
val playedcard6 = TestUtil.simulateInput("1\n") {
KnockOutWhist.matchControl.playerControl.playCard(player2)
PlayerControl.playCard(player2)
}
trick3.playCard(playedcard6, player2)
trick3.wonTrick()
TrickControl.playCard(trick3, round3, playedcard6, player2)
TrickControl.wonTrick(trick3, round3)
"throw an exception if a match gets finalized before it is finished" in {
assertThrows[IllegalStateException] { //If exception is thrown, assertThrows returns succeeded
match1.finalizeMatch()
MatchControl.finalizeMatch(match1)
}
}
"be able to create a random trumpsuit for first round" in {
@@ -99,38 +92,38 @@ class MatchTests extends AnyWordSpec with Matchers{
"return false when no round has been completed" in {
CardManager.shuffleAndReset()
val match3 = Match(List(Player("Gunter")))
match3.create_round()
match3.isOver shouldBe false
RoundControl.create_round(match3)
MatchControl.isOver(match3) shouldBe false
}
"return true if one player is remaining after a round has been played" in {
round3.finalizeRound(true)
match1.isOver shouldBe true
RoundControl.finalizeRound(round3, match1, true)
MatchControl.isOver(match1) shouldBe true
}
val round4 = TestUtil.simulateInput("1\n") {
match1.create_round()
RoundControl.create_round(match1)
}
val trick4 = round4.create_trick()
val trick4 = TrickControl.create_trick(round4)
val playedcard7 = Card(CardValue.Ace, Suit.Hearts)
val playedcard8 = Card(CardValue.Two, Suit.Hearts)
trick4.playCard(playedcard7, player1)
trick4.playCard(playedcard8, player2)
trick4.wonTrick()
TrickControl.playCard(trick4, round4, playedcard7, player1)
TrickControl.playCard(trick4, round4, playedcard8, player2)
TrickControl.wonTrick(trick4, round4)
val trick5 = round4.create_trick()
trick5.playCard(playedcard8, player1)
trick5.playCard(playedcard7, player2)
trick5.wonTrick()
val trick5 = TrickControl.create_trick(round4)
TrickControl.playCard(trick5, round4, playedcard8, player1)
TrickControl.playCard(trick5, round4, playedcard7, player2)
TrickControl.wonTrick(trick5, round4)
CardManager.shuffleAndReset()
val roundResult = TestUtil.simulateInput("1\n13\n") {
round4.finalizeRound(true)
RoundControl.finalizeRound(round4, match1, true)
}
val round5 = TestUtil.simulateInput("1\n") {
match1.create_round()
RoundControl.create_round(match1)
}
"error out if a round is finalized without any tricks played" in {
assertThrows[IllegalStateException] {
round5.finalizeRound()
RoundControl.finalizeRound(round5, match1)
}
}
"be able to finalize a round" in {
@@ -141,7 +134,7 @@ class MatchTests extends AnyWordSpec with Matchers{
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")
MatchControl.finalizeMatch(match1) shouldBe Player("Peter")
}
"have a working toString Method" in {
match1.toString shouldBe "List(Gunter, Peter), 2"

View File

@@ -1,13 +1,16 @@
package de.knockoutwhist.rounds
import de.knockoutwhist.cards.{Card, CardValue, Suit}
import de.knockoutwhist.control.TrickControl
import de.knockoutwhist.player.Player
import de.knockoutwhist.testutils.TestUtil
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class TrickTests extends AnyWordSpec with Matchers {
"A trick" should {
TestUtil.disableDelay()
"be able to return the first card after it was played" in {
val player = Player("Gunter")
val player2 = Player("Peter")
@@ -16,7 +19,7 @@ class TrickTests extends AnyWordSpec with Matchers {
val card = Card(CardValue.Ace, Suit.Spades)
val card2 = Card(CardValue.Two, Suit.Clubs)
val trick = new Trick(round)
trick.playCard(card, player)
TrickControl.playCard(trick, round, card, player)
trick.get_first_card().isEmpty shouldBe false
trick.get_first_card().get shouldBe card
}
@@ -35,9 +38,9 @@ class TrickTests extends AnyWordSpec with Matchers {
val card = Card(CardValue.Ace, Suit.Spades)
val trick = new Trick(round)
trick.playCard(card, player)
TrickControl.playCard(trick, round, card, player)
val won = trick.wonTrick()
val won = TrickControl.wonTrick(trick, round)
won(0) shouldBe player
won(1).cards should equal(trick.cards)
@@ -50,11 +53,11 @@ class TrickTests extends AnyWordSpec with Matchers {
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)
TrickControl.playCard(trick, round, card, player)
val won = trick.wonTrick()
val won = TrickControl.wonTrick(trick, round)
assertThrows[IllegalStateException] { //If exception is thrown, assertThrows returns succeeded
won(1).playCard(card, player)
TrickControl.playCard(won(1), round, card, player)
}
}
"filter the cards by suit correctly if no trump was played" in {
@@ -65,9 +68,9 @@ class TrickTests extends AnyWordSpec with Matchers {
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()
TrickControl.playCard(trick, round, card, player)
TrickControl.playCard(trick, round, card2, player2)
val won = TrickControl.wonTrick(trick, round)
won(0) shouldBe player
}
@@ -77,7 +80,7 @@ class TrickTests extends AnyWordSpec with Matchers {
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
TrickControl.playCard(trick, round, card, player) shouldBe true
}
"return true if the suit matches the first card played" in {
val player = Player("Gunter")
@@ -86,9 +89,9 @@ class TrickTests extends AnyWordSpec with Matchers {
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
val trick = TrickControl.create_trick(round)
TrickControl.playCard(trick, round, card, player)
TrickControl.playCard(trick, round, card2, player2) shouldBe true
}
"return true if the card matches the trump-card" in {
val player = Player("Gunter")
@@ -98,8 +101,8 @@ class TrickTests extends AnyWordSpec with Matchers {
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
TrickControl.playCard(trick, round, card, player)
TrickControl.playCard(trick, round, 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")
@@ -109,8 +112,8 @@ class TrickTests extends AnyWordSpec with Matchers {
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
TrickControl.playCard(trick, round, card, player)
TrickControl.playCard(trick, round, card2, player2) shouldBe false
}
"have a working to string" in {
val player = Player("Gunter")
@@ -120,10 +123,23 @@ class TrickTests extends AnyWordSpec with Matchers {
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)
TrickControl.playCard(trick, round, card, player)
TrickControl.playCard(trick, round, card2, player2)
trick.toString() shouldBe s"${trick.cards}, ${null}, ${false}"
}
"can't set a first card twice" 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.set_first_card(card)
assertThrows[IllegalStateException] {
trick.set_first_card(card2)
}
}
}
}

View File

@@ -1,6 +1,7 @@
package de.knockoutwhist.testutils
import de.knockoutwhist.KnockOutWhist
import de.knockoutwhist.utils.DelayHandler
import java.io.{ByteArrayInputStream, OutputStream}
@@ -26,5 +27,9 @@ object TestUtil {
def disableDebugMode(): Unit = {
KnockOutWhist.DEBUG_MODE_VAR = false
}
def disableDelay(): Unit = {
DelayHandler.activateDelay = false
}
}

View File

@@ -0,0 +1,51 @@
package de.knockoutwhist.utils.events
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class EventTests extends AnyWordSpec with Matchers {
"An EventListener Priority" should {
"have a working compare too" in {
val event1 = Priority.High
val event2 = Priority.Normal
val event3 = Priority.Low
event1 should be > event2
event2 should be < event1
event2 should be > event3
event3 should be < event2
event1 should be > event3
event3 should be < event1
}
}
"The event handler" should {
"add a listener" in {
val eventHandler = new EventHandler() {}
val listener = new EventListener {
override def listen[R](event: ReturnableEvent[R]): Option[R] = None
}
eventHandler.addListener(listener)
eventHandler.listeners should contain(listener)
}
"remove a listener" in {
val eventHandler = new EventHandler() {}
val listener = new EventListener {
override def listen[R](event: ReturnableEvent[R]): Option[R] = None
}
eventHandler.addListener(listener)
eventHandler.removeListener(listener)
eventHandler.listeners should not contain listener
}
"throw an exception if an event is sent without anyone to listen" in {
val eventHandler = new EventHandler() {}
assertThrows[IllegalStateException] {
eventHandler.invoke(new ReturnableEvent[String] {
override def id: String = "testEvent"
})
}
}
}
}