From cc351594e663540dc70d393b44c3bb4b9ef71295 Mon Sep 17 00:00:00 2001 From: Ignas Date: Sun, 12 Nov 2023 16:01:13 +0200 Subject: [PATCH] 3lab --- pom.xml | 10 + src/main/java/module-info.java | 4 +- .../Controllers/DeckViewController.java | 356 ++++++++---------- .../games/blackjack/UnitTests/Tests.java | 43 --- .../Controllers/DeckViewControllerTest.java | 59 +++ target/classes/module-info.class | Bin 374 -> 446 bytes .../Controllers/DeckViewController.class | Bin 13324 -> 14423 bytes 7 files changed, 238 insertions(+), 234 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/pom.xml b/pom.xml index 90edea0..3222690 100644 --- a/pom.xml +++ b/pom.xml @@ -42,6 +42,16 @@ ${junit.version} test + + junit + junit + 4.13.2 + + + org.mockito + mockito-core + 4.8.1 + diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 625badd..44f4889 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -3,9 +3,11 @@ requires javafx.fxml; requires org.kordamp.bootstrapfx.core; + requires junit; + requires org.mockito; opens rokas.games.blackjack to javafx.fxml; opens rokas.games.blackjack.Controllers to javafx.fxml; exports rokas.games.blackjack; - exports rokas.games.blackjack.Controllers to javafx.fxml; + exports rokas.games.blackjack.Controllers to javafx.fxml, org.junit.jupiter.engine; } \ No newline at end of file diff --git a/src/main/java/rokas/games/blackjack/Controllers/DeckViewController.java b/src/main/java/rokas/games/blackjack/Controllers/DeckViewController.java index 3e5b1d6..aa69b80 100644 --- a/src/main/java/rokas/games/blackjack/Controllers/DeckViewController.java +++ b/src/main/java/rokas/games/blackjack/Controllers/DeckViewController.java @@ -1,37 +1,20 @@ 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 +22,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; @@ -91,30 +73,7 @@ public class DeckViewController implements Initializable { 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 getPlayerScore() { - return playerScore; - } - - public Button getPlayButton() { - return playButton; - } - - public Text getBetNotice() { - return betNotice; - } + private static final int playerStartMoneyAmount = 1000; public DeckViewController() { this.deck = new Deck(); @@ -221,127 +180,148 @@ 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; - } + return score == 21; } public boolean compareScores(int playerCurrentScore, int dealerCurrentScore){ - if(playerCurrentScore > dealerCurrentScore){ - return true; - } - else{ - return false; - } + return playerCurrentScore > dealerCurrentScore; } public boolean checkIfPush(int playerCurrentScore, int dealerCurrentScore){ - if (playerCurrentScore == dealerCurrentScore){ - return true; - } - else{ - return false; - } + return playerCurrentScore == dealerCurrentScore; } public void hitCard() { - if (deck.getDeckSize() == 0) { - deck = new Deck(); - deck.shuffle(); + if (shouldResetDeck()) { + resetDeck(); } - hand = new Hand(); + currentCard = deck.dealCard(); - int currentCardFaceValue; playerHandCount++; + displayPlayerCard(currentCard); + + int currentCardFaceValue = calculateCardFaceValue(); + playerScore = hand.calculateScore(playerScore, currentCardFaceValue); + updatePlayerUI(); + + if (shouldHandleBust()) { + handleBust(); + } else if (shouldHandleMaxScore()) { + handleMaxScore(); + } + + updateHandInfo(); + } + + private void updateHandInfo() { + hand.setPlayerHandCardCount(playerHandCount); + hand.setPlayerScoreCount(playerScore); + } + + boolean shouldResetDeck() { + return deck.getDeckSize() == 0; + } + + private void resetDeck() { + deck = new Deck(); + deck.shuffle(); + } + + private void displayPlayerCard(Card card) { ImageView playerCard = getPlayerCardImageView(playerHandCount); - playerCard.setImage(currentCard.getImage()); + assert playerCard != null; + playerCard.setImage(card.getImage()); playerCard.setVisible(true); + } - currentCardFaceValue = hand.extractFaceValue(currentCard.toString(), playerScore); - playerScore = hand.calculateScore(playerScore, currentCardFaceValue); + private int calculateCardFaceValue() { + return hand.extractFaceValue(currentCard.toString(), playerScore); + } + + private void updatePlayerUI() { displayPlayerFaceValue.setText(String.valueOf(playerScore)); cardsInDeck.setText(Integer.toString(deck.getDeckSize())); + } - if (checkIfBust(playerScore) && playerHandCount > 2) { - winsText.setVisible(true); - winsText.setText("Dealer wins (player busted)"); - playAgainButton.setVisible(true); - playerLostFlag = true; - toggleButtons(false); - if (checkIfGameOver(playerMoneyAmount)) { - showGameOverScreen(); - } - } else if (checkIfMaxScore(playerScore) && playerHandCount >= 2) { - winsText.setVisible(true); - winsText.setText("Player wins (player got 21)"); - playAgainButton.setVisible(true); - playerMoneyAmount = playerMoneyAmount + playerBetAmount * 2; - playerMoney.setText(Integer.toString(playerMoneyAmount)); - playerLostFlag = false; - toggleButtons(false); + private boolean shouldHandleBust() { + return playerHandCount > 2 && checkIfBust(playerScore); + } + + private void handleBust() { + displayBustMessage(); + playerLostFlag = true; + toggleButtons(false); + if (checkIfGameOver(playerMoneyAmount)) { + showGameOverScreen(); } - hand.setPlayerHandCardCount(playerHandCount); - hand.setPlayerScoreCount(playerScore); + } + + private boolean shouldHandleMaxScore() { + return playerHandCount >= 2 && checkIfMaxScore(playerScore); + } + + private void handleMaxScore() { + displayMaxScoreMessage(); + playerMoneyAmount = playerMoneyAmount + playerBetAmount * 2; + playerMoney.setText(Integer.toString(playerMoneyAmount)); + playerLostFlag = false; + toggleButtons(false); + } + + private void displayBustMessage() { + winsText.setVisible(true); + winsText.setText("Dealer wins (player busted)"); + playAgainButton.setVisible(true); + } + + private void displayMaxScoreMessage() { + winsText.setVisible(true); + winsText.setText("Player wins (player got 21)"); + playAgainButton.setVisible(true); } 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() { @@ -445,10 +425,10 @@ public void doubleCard() { } } - public void betTen(ActionEvent actionEvent) { + public void betTen() { addBet(10); } - public void betTwentyFive(ActionEvent actionEvent) { + public void betTwentyFive() { addBet(25); } @@ -493,7 +473,7 @@ public void addBet(int amount) { public boolean checkIfDealerHitNext(int score) { - int probability = 0; + int probability; Random rand = new Random(); int randInt = rand.nextInt(100) + 1; if (score > 0 && score <= 10) { @@ -515,12 +495,7 @@ public boolean checkIfDealerHitNext(int score) { } public boolean checkIfGameOver(int money){ - if (money <= 0){ - return true; - } - else{ - return false; - } + return money <= 0; } @@ -549,12 +524,61 @@ public void hideEverything(){ playerScoreText.setVisible(false); playerMoney.setVisible(false); + hidePlayerCards(); + hideDealerCards(); + } + + public void toggleElements(boolean toggleFlag) { + if (toggleFlag) { + showGameElements(); + } else { + hideGameElements(); + } + } + + private void showGameElements() { + betPane.setVisible(false); + hitButton.setVisible(true); + standButton.setVisible(true); + playButton.setVisible(false); + playAgainButton.setVisible(false); + cardsInDeck.setVisible(true); + playerScoreText.setVisible(true); + dealerScoreText.setVisible(true); + cardsLeftImageView.setVisible(true); + displayPlayerFaceValue.setVisible(true); + displayDealerFaceValue.setVisible(true); + } + + private void hideGameElements() { + betPane.setVisible(true); + hitButton.setVisible(false); + standButton.setVisible(false); + doubleButton.setVisible(false); + winsText.setVisible(false); + playerScoreText.setVisible(false); + dealerScoreText.setVisible(false); + cardsLeftImageView.setVisible(false); + displayPlayerFaceValue.setVisible(false); + displayDealerFaceValue.setVisible(false); + betAmount.setVisible(true); + betText.setVisible(true); + moneyText.setVisible(true); + playerMoney.setVisible(true); + + hidePlayerCards(); + hideDealerCards(); + } + + private void hidePlayerCards() { playerCard1.setVisible(false); playerCard2.setVisible(false); playerCard3.setVisible(false); playerCard4.setVisible(false); playerCard5.setVisible(false); + } + private void hideDealerCards() { dealerCard1.setVisible(false); dealerCard2.setVisible(false); dealerCard3.setVisible(false); @@ -562,52 +586,4 @@ public void hideEverything(){ dealerCard5.setVisible(false); } - public void toggleElements(boolean toggleFlag){ - if (toggleFlag){ - betPane.setVisible(false); - hitButton.setVisible(true); - standButton.setVisible(true); - playButton.setVisible(false); - playAgainButton.setVisible(false); - cardsInDeck.setVisible(true); - playerScoreText.setVisible(true); - dealerScoreText.setVisible(true); - cardsInDeck.setVisible(true); - cardsLeftImageView.setVisible(true); - displayPlayerFaceValue.setVisible(true); - displayDealerFaceValue.setVisible(true); - } - else{ - betPane.setVisible(true); - hitButton.setVisible(false); - standButton.setVisible(false); - doubleButton.setVisible(false); - winsText.setVisible(false); - playerScoreText.setVisible(false); - dealerScoreText.setVisible(false); - cardsInDeck.setVisible(false); - cardsLeftImageView.setVisible(false); - displayPlayerFaceValue.setVisible(false); - displayDealerFaceValue.setVisible(false); - betAmount.setVisible(true); - betText.setVisible(true); - moneyText.setVisible(true); - playerMoney.setVisible(true); - - playerCard1.setVisible(false); - playerCard2.setVisible(false); - playerCard3.setVisible(false); - playerCard4.setVisible(false); - playerCard5.setVisible(false); - - dealerCard1.setVisible(false); - dealerCard2.setVisible(false); - dealerCard3.setVisible(false); - dealerCard4.setVisible(false); - 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..0d5b879 --- /dev/null +++ b/src/test/java/rokas/games/blackjack/Controllers/DeckViewControllerTest.java @@ -0,0 +1,59 @@ +package rokas.games.blackjack.Controllers; + +import org.junit.Before; +import org.junit.Test; +import static org.mockito.Mockito.*; +import rokas.games.blackjack.Model.Deck; +import rokas.games.blackjack.Model.Hand; +public class DeckViewControllerTest { + + + private DeckViewController deckViewController; + private Deck deck; + private Hand hand; + + @Before + public void setUp() { + deck = mock(Deck.class); + hand = mock(Hand.class); + deckViewController = new DeckViewController(); + } + + @Test + public void testHitCardShouldResetDeck() { + when(deck.getDeckSize()).thenReturn(0); + deckViewController.hitCard(); + verify(deck).shuffle(); + } + + @Test + public void testHitCardShouldHandleBust() { + when(deck.getDeckSize()).thenReturn(5); // Assuming deck is not empty + when(hand.extractFaceValue(anyString(), anyInt())).thenReturn(10); // Assuming current card value + when(hand.calculateScore(anyInt(), anyInt())).thenReturn(25); // Assuming player's score + when(deckViewController.checkIfBust(anyInt())).thenReturn(true); + deckViewController.hitCard(); + // Write assertions for the expected behavior when bust is handled + } + + @Test + public void testHitCardShouldHandleMaxScore() { + when(deck.getDeckSize()).thenReturn(5); // Assuming deck is not empty + when(hand.extractFaceValue(anyString(), anyInt())).thenReturn(10); // Assuming current card value + when(hand.calculateScore(anyInt(), anyInt())).thenReturn(21); // Assuming player's score + when(deckViewController.checkIfMaxScore(anyInt())).thenReturn(true); + deckViewController.hitCard(); + // Write assertions for the expected behavior when max score is handled + } + + @Test + public void testHitCardDefaultCase() { + when(deck.getDeckSize()).thenReturn(5); // Assuming deck is not empty + when(hand.extractFaceValue(anyString(), anyInt())).thenReturn(10); // Assuming current card value + when(hand.calculateScore(anyInt(), anyInt())).thenReturn(15); // Assuming player's score + deckViewController.hitCard(); + // Write assertions for the default case + } + } + + diff --git a/target/classes/module-info.class b/target/classes/module-info.class index 80f69f7a3416e536c55f43d0525481fd4339c4ab..9e8fff9e7ece94d33bccf95cad776905c4041f9a 100644 GIT binary patch delta 224 zcmXAiI}U#5n{J Xa5XuWT319)QyZdkri>b8Dy8@VBNinh delta 107 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 literal 13324 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 T{yxf&0_MTrkMZODV=DSDvmz*r