From 455eecd90881e151dd1efcf92153b6e2e400b12a Mon Sep 17 00:00:00 2001 From: Rokas Reizgys Date: Sun, 12 Nov 2023 16:03:42 +0200 Subject: [PATCH] Laboratorinis darbas 3 --- BlackJack.iml | 32 +- pom.xml | 22 +- src/main/java/module-info.java | 2 + .../Controllers/DeckViewController.java | 451 +++++++++--------- .../games/blackjack/UnitTests/Tests.java | 43 -- .../Controllers/DeckViewControllerTest.java | 130 +++++ target/classes/module-info.class | Bin 374 -> 450 bytes .../Controllers/DeckViewController.class | Bin 13324 -> 15713 bytes 8 files changed, 387 insertions(+), 293 deletions(-) delete mode 100644 src/main/java/rokas/games/blackjack/UnitTests/Tests.java create mode 100644 src/test/java/rokas/games/blackjack/Controllers/DeckViewControllerTest.java diff --git a/BlackJack.iml b/BlackJack.iml index 18b3018..3166029 100644 --- a/BlackJack.iml +++ b/BlackJack.iml @@ -1,29 +1,11 @@ - - - - - - - - + + + + - - - - - - - - - - - - - - - - - + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 90edea0..264b4cf 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,6 @@ org.junit.jupiter junit-jupiter-api ${junit.version} - test org.junit.jupiter @@ -42,6 +41,27 @@ ${junit.version} test + + org.mockito + mockito-core + 3.12.4 + test + + + + org.mockito + mockito-junit-jupiter + 3.12.4 + + + + org.testfx + testfx-junit5 + 4.0.15-alpha + test + + + diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 625badd..d169398 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -3,6 +3,8 @@ requires javafx.fxml; requires org.kordamp.bootstrapfx.core; + requires org.mockito.junit.jupiter; + requires org.junit.jupiter.api; opens rokas.games.blackjack to javafx.fxml; opens rokas.games.blackjack.Controllers to javafx.fxml; diff --git a/src/main/java/rokas/games/blackjack/Controllers/DeckViewController.java b/src/main/java/rokas/games/blackjack/Controllers/DeckViewController.java index 3e5b1d6..e4b77bb 100644 --- a/src/main/java/rokas/games/blackjack/Controllers/DeckViewController.java +++ b/src/main/java/rokas/games/blackjack/Controllers/DeckViewController.java @@ -1,37 +1,21 @@ package rokas.games.blackjack.Controllers; -import javafx.event.ActionEvent; import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; -import javafx.scene.Node; -import javafx.scene.Parent; -import javafx.scene.Scene; import javafx.scene.control.Button; -import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.text.Text; -import javafx.stage.Stage; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; import rokas.games.blackjack.Model.Card; import rokas.games.blackjack.Model.Deck; import rokas.games.blackjack.Model.Hand; -import java.io.IOException; + import java.net.URL; import java.util.Random; import java.util.ResourceBundle; - - -@Getter -@Setter -@AllArgsConstructor public class DeckViewController implements Initializable { @FXML private Text gameOverText; @FXML private Button playButtonRestart; @@ -39,7 +23,6 @@ public class DeckViewController implements Initializable { @FXML private Text betText; @FXML private Text moneyText; @FXML private ImageView mainImage; - @FXML private Button play; @FXML private Button playAgainButton; @FXML private Text dealerScoreText; @FXML private Text playerScoreText; @@ -78,6 +61,27 @@ public class DeckViewController implements Initializable { @FXML private ImageView playerCard9; @FXML private ImageView playerCard10; @FXML private ImageView playerCard11; + + public Text getWinsText() { + return winsText; + } + + public Text getGameOverText() { + return gameOverText; + } + + public void setDealerHandCount(int dealerHandCount) { + this.dealerHandCount = dealerHandCount; + } + + public void setDealerScore(int dealerScore) { + this.dealerScore = dealerScore; + } + + public void setPlayerMoneyAmount(int playerMoneyAmount) { + this.playerMoneyAmount = playerMoneyAmount; + } + private Deck deck; private Hand hand; private int playerHandCount = 0; @@ -89,32 +93,15 @@ public class DeckViewController implements Initializable { private int hiddenCardValue = 0; private int playerMoneyAmount = 0; private int playerBetAmount = 0; - private boolean playerLostFlag = false; - private static int playerStartMoneyAmount = 1000; - - public boolean getPlayerLostFlag() { - return playerLostFlag; - } - public void setPlayerScore(int playerScore) { - this.playerScore = playerScore; - } - - public int getPlayerHandCount() { - return playerHandCount; + public int getDealerHandCount() { + return dealerHandCount; } - public int getPlayerScore() { - return playerScore; - } + private boolean playerLostFlag = false; - public Button getPlayButton() { - return playButton; - } - - public Text getBetNotice() { - return betNotice; - } + private final Random randomNumber = new Random(); + private static final int PLAYERSTARTMONEYAMOUNT = 1000; public DeckViewController() { this.deck = new Deck(); @@ -147,7 +134,7 @@ public void initialize(URL url, ResourceBundle resourceBundle) { deck.shuffle(); playButton.setVisible(false); playAgainButton.setVisible(false); - playerMoneyAmount = playerStartMoneyAmount; + playerMoneyAmount = PLAYERSTARTMONEYAMOUNT; playerMoney.setText(Integer.toString(playerMoneyAmount)); hideEverything(); cardsLeftImageView.setImage(new Image("file:src/main/resources/rokas/games/blackjack/Media/icons8-ace-of-diamonds-50.png")); @@ -194,7 +181,6 @@ private void resetGameState() { playButton.setVisible(false); playAgainButton.setVisible(false); playerBetAmount = 0; - playerMoneyAmount = 1000; betAmount.setText(Integer.toString(playerBetAmount)); playerMoney.setText(Integer.toString(playerMoneyAmount)); toggleElements(false); @@ -221,39 +207,11 @@ private void resetCardVisibility() { dealerCard5.setVisible(false); } public boolean checkIfBust(int score){ - if(score > 21){ - return true; - } - else{ - return false; - } + return score > 21; } public boolean checkIfMaxScore(int score){ - if(score == 21){ - return true; - } - else{ - return false; - } - } - - public boolean compareScores(int playerCurrentScore, int dealerCurrentScore){ - if(playerCurrentScore > dealerCurrentScore){ - return true; - } - else{ - return false; - } - } - - public boolean checkIfPush(int playerCurrentScore, int dealerCurrentScore){ - if (playerCurrentScore == dealerCurrentScore){ - return true; - } - else{ - return false; - } + return score == 21; } public void hitCard() { @@ -267,8 +225,12 @@ public void hitCard() { playerHandCount++; ImageView playerCard = getPlayerCardImageView(playerHandCount); - playerCard.setImage(currentCard.getImage()); - playerCard.setVisible(true); + if (playerCard != null) { + playerCard.setImage(currentCard.getImage()); + playerCard.setVisible(true); + } else { + return; + } currentCardFaceValue = hand.extractFaceValue(currentCard.toString(), playerScore); playerScore = hand.calculateScore(playerScore, currentCardFaceValue); @@ -298,140 +260,144 @@ public void hitCard() { } private ImageView getPlayerCardImageView(int playerHandCount) { - switch (playerHandCount) { - case 1: return playerCard1; - case 2: return playerCard2; - case 3: return playerCard3; - case 4: return playerCard4; - case 5: return playerCard5; - case 6: return playerCard6; - case 7: return playerCard7; - case 8: return playerCard8; - case 9: return playerCard9; - case 10: return playerCard10; - case 11: return playerCard11; - default: return null; - } + return switch (playerHandCount) { + case 1 -> playerCard1; + case 2 -> playerCard2; + case 3 -> playerCard3; + case 4 -> playerCard4; + case 5 -> playerCard5; + case 6 -> playerCard6; + case 7 -> playerCard7; + case 8 -> playerCard8; + case 9 -> playerCard9; + case 10 -> playerCard10; + case 11 -> playerCard11; + default -> null; + }; } private ImageView getDealerCardByIndex(int index) { - switch (index) { - case 1: - return dealerCard1; - case 2: + return switch (index) { + case 1 -> dealerCard1; + case 2 -> { previousCard = currentCard; - return dealerCard2; - case 3: + yield dealerCard2; + } + case 3 -> { dealerCard2.setImage(previousCard.getImage()); - return dealerCard3; - case 4: - return dealerCard4; - case 5: - return dealerCard5; - case 6: - return dealerCard6; - case 7: - return dealerCard7; - case 8: - return dealerCard8; - case 9: - return dealerCard9; - case 10: - return dealerCard10; - case 11: - return dealerCard11; - default: - throw new IndexOutOfBoundsException("Invalid dealer card index: " + index); - } + yield dealerCard3; + } + case 4 -> dealerCard4; + case 5 -> dealerCard5; + case 6 -> dealerCard6; + case 7 -> dealerCard7; + case 8 -> dealerCard8; + case 9 -> dealerCard9; + case 10 -> dealerCard10; + case 11 -> dealerCard11; + default -> throw new IndexOutOfBoundsException("Invalid dealer card index: " + index); + }; } public void standCard() { - if (deck.getDeckSize() == 0) { - deck = new Deck(); - deck.shuffle(); - } + resetDeckIfNeeded(); - hand = new Hand(); - currentCard = deck.dealCard(); - int currentCardFaceValue; - dealerHandCount++; + dealAndDisplayCard(); - ImageView dealerCard = getDealerCardByIndex(dealerHandCount); - dealerCard.setImage(currentCard.getImage()); - dealerCard.setVisible(true); - currentCardFaceValue = hand.extractFaceValue(currentCard.toString(), dealerScore); + handleWinLoseConditions(); + } - if (dealerHandCount == 2){ - hiddenCardValue = currentCardFaceValue; + /** + * Reset the deck if it's empty. + */ + void resetDeckIfNeeded() { + if (this.deck.getDeckSize() == 0) { + this.deck = new Deck(); + this.deck.shuffle(); } - else if (dealerHandCount == 3){ - currentCardFaceValue = currentCardFaceValue + hiddenCardValue; - dealerScore = hand.calculateScore(dealerScore, currentCardFaceValue); + } + + /** + * Deal a card, display it, and update the dealer's hand count and score. + */ + void dealAndDisplayCard() { + this.hand = new Hand(); + this.currentCard = this.deck.dealCard(); + ++this.dealerHandCount; + ImageView dealerCard = this.getDealerCardByIndex(this.dealerHandCount); + dealerCard.setImage(this.currentCard.getImage()); + dealerCard.setVisible(true); + + int currentCardFaceValue = this.hand.extractFaceValue(this.currentCard.toString(), this.dealerScore); + + if (this.dealerHandCount == 2) { + this.hiddenCardValue = currentCardFaceValue; + } else if (this.dealerHandCount == 3) { + currentCardFaceValue += this.hiddenCardValue; } - else{ - dealerScore = hand.calculateScore(dealerScore, currentCardFaceValue); + + this.dealerScore = this.hand.calculateScore(this.dealerScore, currentCardFaceValue); + + this.displayDealerFaceValue.setText(String.valueOf(this.dealerScore)); + this.hand.setDealerHandCardCount(this.dealerHandCount); + this.hand.setDealerScoreCount(this.dealerScore); + this.cardsInDeck.setText(Integer.toString(this.deck.getDeckSize())); + } + + /** + * Handle win/lose conditions and display appropriate messages. + */ + void handleWinLoseConditions() { + if (this.dealerHandCount == 2 && this.checkIfMaxScore(this.dealerScore + this.hiddenCardValue)) { + displayWinCondition("Dealer wins (dealer got 21)", this.previousCard.getImage()); + checkAndShowGameOverScreen(); + } else if (this.dealerHandCount > 2) { + handleWinLoseConditionsAfterSecondCard(); } - displayDealerFaceValue.setText(String.valueOf(dealerScore)); - hand.setDealerHandCardCount(dealerHandCount); - hand.setDealerScoreCount(dealerScore); - cardsInDeck.setText(Integer.toString(deck.getDeckSize())); - if(checkIfMaxScore(dealerScore+hiddenCardValue) && dealerHandCount == 2){ - winsText.setVisible(true); - winsText.setText("Dealer wins (dealer got 21)"); - playAgainButton.setVisible(true); - toggleButtons(false); - dealerCard2.setImage(previousCard.getImage()); - if(checkIfGameOver(playerMoneyAmount)){ - showGameOverScreen(); - } + } + + /** + * Handle win/lose conditions after the second card is dealt. + */ + void handleWinLoseConditionsAfterSecondCard() { + if (this.checkIfBust(this.dealerScore)) { + displayWinCondition("Player wins (dealer busted)", null); + } else if (this.dealerScore <= 20 && this.checkIfDealerHitNext(this.dealerScore)) { + this.standCard(); + } else if (this.checkIfMaxScore(this.dealerScore)) { + displayWinCondition("Dealer wins (dealer got 21)", null); + checkAndShowGameOverScreen(); + } else { + displayWinCondition("Dealer wins (dealer score is bigger)", null); + checkAndShowGameOverScreen(); } - else if(dealerHandCount > 2){ - if(checkIfBust(dealerScore)){ - winsText.setVisible(true); - winsText.setText("Player wins (dealer busted)"); - playAgainButton.setVisible(true); - playerMoneyAmount += playerBetAmount*2; - playerMoney.setText(Integer.toString(playerMoneyAmount)); - toggleButtons(false); - } - else if(dealerScore <= 20 && checkIfDealerHitNext(dealerScore)){ - standCard(); - } - else if(checkIfMaxScore(dealerScore)){ - winsText.setVisible(true); - winsText.setText("Dealer wins (dealer got 21)"); - playAgainButton.setVisible(true); - toggleButtons(false); - if(checkIfGameOver(playerMoneyAmount)){ - showGameOverScreen(); - } - } - else if (compareScores(playerScore, dealerScore)){ - winsText.setVisible(true); - winsText.setText("Player wins (player score is bigger)"); - playAgainButton.setVisible(true); - playerMoneyAmount += playerBetAmount*2; - playerMoney.setText(Integer.toString(playerMoneyAmount)); - toggleButtons(false); - } - else if (checkIfPush(playerScore, dealerScore)){ - winsText.setVisible(true); - winsText.setText("Push!"); - playAgainButton.setVisible(true); - playerMoneyAmount += playerBetAmount; - playerMoney.setText(Integer.toString(playerMoneyAmount)); - toggleButtons(false); - } - else{ - winsText.setVisible(true); - winsText.setText("Dealer wins (dealer score is bigger)"); - playAgainButton.setVisible(true); - toggleButtons(false); - if(checkIfGameOver(playerMoneyAmount)){ - showGameOverScreen(); - } - } + } + + /** + * Display the win condition and update UI accordingly. + * + * @param message The win condition message to display. + * @param image The image to set for the dealer's second card (can be null). + */ + void displayWinCondition(String message, Image image) { + this.winsText.setVisible(true); + this.winsText.setText(message); + this.playAgainButton.setVisible(true); + this.toggleButtons(false); + + if (image != null) { + this.dealerCard2.setImage(image); } + checkAndShowGameOverScreen(); + } + + /** + * Check if the game is over based on the player's money amount. + */ + void checkAndShowGameOverScreen() { + if (this.checkIfGameOver(this.playerMoneyAmount)) { + this.showGameOverScreen(); + } } public void doubleCard() { @@ -445,57 +411,102 @@ public void doubleCard() { } } - public void betTen(ActionEvent actionEvent) { - addBet(10); + public void betTen() { + adjustBetAmount(10); } - public void betTwentyFive(ActionEvent actionEvent) { - addBet(25); + public void betTwentyFive() { + adjustBetAmount(25); } public void betFifty() { - addBet(50); + adjustBetAmount(50); } public void betHundred() { - addBet(100); + adjustBetAmount(100); } public void betTwoHundred() { - addBet(200); + adjustBetAmount(200); } public void betAllIn() { - addBet(playerMoneyAmount); + adjustBetAmount(playerMoneyAmount); } public void restartBet() { - addBet(-1); + adjustBetAmount(-1); + } + + + /** + * Adjusts the player's bet amount based on the given input. + * If the input is valid, updates the bet amount, player money, and UI accordingly. + * If the input is not valid, displays a "Not enough money" notice. + * + * @param amount The amount to adjust the player's bet. + */ + public void adjustBetAmount(int amount) { + if (isValidBetAmount(amount)) { + updatePlayerBetAndMoney(amount); + updateUI(); + showPlayButton(); + } else { + displayNotEnoughMoneyNotice(); + } } + /** + * Checks if the given bet amount is valid. + * + * @param amount The bet amount to check. + * @return True if the amount is greater than zero and the player has enough money, otherwise false. + */ + boolean isValidBetAmount(int amount) { + return amount > 0 && playerMoneyAmount >= amount; + } - public void addBet(int amount) { - if (amount > 0 && playerMoneyAmount >= amount) { - playerBetAmount += amount; - playerMoneyAmount -= amount; - betAmount.setText(Integer.toString(playerBetAmount)); - playerMoney.setText(Integer.toString(playerMoneyAmount)); - playButton.setVisible(true); - } else if (amount <= 0) { + /** + * Updates the player's bet and money amounts based on the given input. + * + * @param amount The amount to adjust the player's bet. + */ + void updatePlayerBetAndMoney(int amount) { + if (amount <= 0) { playerMoneyAmount += playerBetAmount; playerBetAmount = 0; - betAmount.setText(Integer.toString(playerBetAmount)); - playerMoney.setText(Integer.toString(playerMoneyAmount)); - playButton.setVisible(false); } else { - betNotice.setText("Not enough money!"); + playerBetAmount += amount; + playerMoneyAmount -= amount; } } + /** + * Updates the UI to reflect the current player's bet and money amounts. + */ + void updateUI() { + betAmount.setText(Integer.toString(playerBetAmount)); + playerMoney.setText(Integer.toString(playerMoneyAmount)); + } + + /** + * Displays a "Not enough money" notice and hides the play button. + */ + void displayNotEnoughMoneyNotice() { + betNotice.setText("Not enough money!"); + playButton.setVisible(false); + } + + /** + * Sets play button visibility to true + */ + void showPlayButton() { + playButton.setVisible(true); + } public boolean checkIfDealerHitNext(int score) { int probability = 0; - Random rand = new Random(); - int randInt = rand.nextInt(100) + 1; + int randInt = randomNumber.nextInt(100) + 1; if (score > 0 && score <= 10) { return true; } else if (score > 10 && score <= 14) { @@ -515,12 +526,7 @@ public boolean checkIfDealerHitNext(int score) { } public boolean checkIfGameOver(int money){ - if (money <= 0){ - return true; - } - else{ - return false; - } + return money <= 0; } @@ -607,7 +613,4 @@ public void toggleElements(boolean toggleFlag){ dealerCard5.setVisible(false); } } - - - } diff --git a/src/main/java/rokas/games/blackjack/UnitTests/Tests.java b/src/main/java/rokas/games/blackjack/UnitTests/Tests.java deleted file mode 100644 index 2c1faed..0000000 --- a/src/main/java/rokas/games/blackjack/UnitTests/Tests.java +++ /dev/null @@ -1,43 +0,0 @@ -package rokas.games.blackjack.UnitTests; - -import org.testng.annotations.Test; -import rokas.games.blackjack.Controllers.DeckViewController; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class Tests { - DeckViewController deckViewController = new DeckViewController(); - @Test - public void testCheckIfBust(){ - int scoreMore = 22; - int scoreEqual = 21; - int scoreLess = 20; - assertTrue(deckViewController.checkIfBust(scoreMore)); - assertFalse(deckViewController.checkIfBust(scoreEqual)); - assertFalse(deckViewController.checkIfBust(scoreLess)); - } - - @Test - public void testCompareScore(){ - int playerScore = 20; - int dealerScore = 10; - assertTrue(deckViewController.compareScores(playerScore, dealerScore)); - playerScore = 10; - dealerScore = 20; - assertFalse(deckViewController.compareScores(playerScore, dealerScore)); - } - - @Test - public void testCheckIfGameOver(){ - int money = 1000; - assertFalse(deckViewController.checkIfGameOver(money)); - money = 0; - assertTrue(deckViewController.checkIfGameOver(money)); - money = -10; - assertTrue(deckViewController.checkIfGameOver(money)); - } - - - -} \ No newline at end of file diff --git a/src/test/java/rokas/games/blackjack/Controllers/DeckViewControllerTest.java b/src/test/java/rokas/games/blackjack/Controllers/DeckViewControllerTest.java new file mode 100644 index 0000000..6ec63f4 --- /dev/null +++ b/src/test/java/rokas/games/blackjack/Controllers/DeckViewControllerTest.java @@ -0,0 +1,130 @@ +package rokas.games.blackjack.Controllers; + +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.testfx.framework.junit5.ApplicationExtension; +import rokas.games.blackjack.Model.Card; +import rokas.games.blackjack.Model.Deck; +import rokas.games.blackjack.Model.Hand; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +@ExtendWith(MockitoExtension.class) +@ExtendWith(ApplicationExtension.class) +class DeckViewControllerTest { + + private DeckViewController deckViewController; + + @BeforeEach + void setUp() { + deckViewController = new DeckViewController(); + deckViewController.newGame(); + } + + @Test + void testStandCard() { + Deck mockedDeck = mock(Deck.class); + when(mockedDeck.getDeckSize()).thenReturn(10); + Hand mockedHand = mock(Hand.class); + deckViewController.setDeck(mockedDeck); + deckViewController.setHand(mockedHand); + deckViewController.setDealerHandCount(1); + deckViewController.setDealerScore(16); + + deckViewController.standCard(); + + assertEquals(2, deckViewController.getDealerHandCount()); + // Add more assertions based on the actual behavior of the standCard() method in your application + } + + @Test + void testResetDeckIfNeeded() { + Deck mockedDeck = mock(Deck.class); + when(mockedDeck.getDeckSize()).thenReturn(0); + deckViewController.setDeck(mockedDeck); + + deckViewController.resetDeckIfNeeded(); + + verify(mockedDeck, times(1)).shuffle(); + } + + @Test + void testDealAndDisplayCard() { + Deck mockedDeck = mock(Deck.class); + when(mockedDeck.dealCard()).thenReturn(new Card("Test", "Test")); + when(mockedDeck.getDeckSize()).thenReturn(10); + Hand mockedHand = mock(Hand.class); + deckViewController.setDeck(mockedDeck); + deckViewController.setHand(mockedHand); + deckViewController.setDealerHandCount(1); + deckViewController.setDealerScore(16); + + deckViewController.dealAndDisplayCard(); + + assertEquals(2, deckViewController.getDealerHandCount()); + // Add more assertions based on the actual behavior of the dealAndDisplayCard() method in your application + } + + @Test + void testHandleWinLoseConditions() { + Deck mockedDeck = mock(Deck.class); + when(mockedDeck.getDeckSize()).thenReturn(10); + Hand mockedHand = mock(Hand.class); + deckViewController.setDeck(mockedDeck); + deckViewController.setHand(mockedHand); + deckViewController.setDealerHandCount(1); + deckViewController.setDealerScore(16); + + deckViewController.handleWinLoseConditions(); + + // Add more assertions based on the actual behavior of the handleWinLoseConditions() method in your application + } + + @Test + void testHandleWinLoseConditionsAfterSecondCard() { + Deck mockedDeck = mock(Deck.class); + when(mockedDeck.getDeckSize()).thenReturn(10); + Hand mockedHand = mock(Hand.class); + deckViewController.setDeck(mockedDeck); + deckViewController.setHand(mockedHand); + deckViewController.setDealerHandCount(1); + deckViewController.setDealerScore(16); + + deckViewController.handleWinLoseConditionsAfterSecondCard(); + + // Add more assertions based on the actual behavior of the handleWinLoseConditionsAfterSecondCard() method in your application + } + + @Test + void testDisplayWinCondition() { + String message = "Test message"; + Image image = new Image("test.png"); + + deckViewController.displayWinCondition(message, image); + + assertEquals(message, deckViewController.getWinsText().getText()); + // Add more assertions based on the actual behavior of the displayWinCondition() method in your application + } + + @Test + void testCheckAndShowGameOverScreen() { + Deck mockedDeck = mock(Deck.class); + when(mockedDeck.getDeckSize()).thenReturn(10); + Hand mockedHand = mock(Hand.class); + deckViewController.setDeck(mockedDeck); + deckViewController.setHand(mockedHand); + deckViewController.setPlayerMoneyAmount(0); + + deckViewController.checkAndShowGameOverScreen(); + + assertTrue(deckViewController.getGameOverText().isVisible()); + // Add more assertions based on the actual behavior of the checkAndShowGameOverScreen() method in your application + } + + // Add more tests as needed to achieve 80% code coverage + +} diff --git a/target/classes/module-info.class b/target/classes/module-info.class index 80f69f7a3416e536c55f43d0525481fd4339c4ab..dfc65975c0678559f22ea54413eb3211f14b35d0 100644 GIT binary patch delta 213 zcmeyybcmVj)W2Q(7#JAr7-S}LrLnUaTId<*8BMH=Ob})eVPufZFG|tGEXmi) zD$UC*0g?roC86k-qtk_-%@KrGI{#2^9Y RNdZYlpehI-#Da+cB>-(3DaZf- delta 138 zcmX@a{Edn0)W2Q(7#JAr7{n)XrEzi?TId<*8R;2LERU2EVGvPs~j% z)=$bwOwP^%Vi5+h$p(z}?rschK+OycEDRhC3=CWh+(0%j10R?a0FpusObo(6nt?$S Qh#7%05I%?n69Gy908rBu{{R30 diff --git a/target/classes/rokas/games/blackjack/Controllers/DeckViewController.class b/target/classes/rokas/games/blackjack/Controllers/DeckViewController.class index f9415f5a20f0ad8333e0f8e5ccd445d0649453ab..fb7f6d00bdef2802e59e1dda30a932b623962b3d 100644 GIT binary patch literal 15713 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 literal 13324 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 T{yxf&0_MTrkMZODV=DSDvmz*r