diff --git a/README.md b/README.md index 16608d7..0c3377d 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,36 @@ This delivery improves encapsulation by adding explicit getters and setters to t - `Score`: `getChips`, `setChips`, `getMultiplier`, `setMultiplier` - Updated tests to cover the new accessors. --- +## Task 3 - Partial Delivery 4 +This delivery focuses on adding **robust rule enforcement** using **custom exceptions**, and introducing the **base scoring logic** influenced by Jokers. + +### Changes: + +- Implemented constraints and exceptions in `Hand`: + - A hand cannot have more than 8 cards → `TooManyCardsException` + - A hand cannot have more than 2 jokers → `TooManyJokersException` + - Cannot remove a card or joker using an invalid index → `InvalidCardIndexException`, `InvalidJokerIndexException` + - Cannot play more than 3 times → `TooManyPlaysException` + - Cannot discard more than 3 times → `TooManyDiscardsException` + - Playing or discarding must involve 1 to 5 cards → `InvalidPlaySizeException` + - Index validation now rejects unordered or out-of-range lists + +- Added `discardCards(indices: List[Int])` method to `Hand` with full validation and tracking + +- Created custom exception classes under the `exceptions/` package + +- Added unit tests in `ExceptionTest.scala` to cover: + - Each custom exception + - Limits and index validation in `Hand` + +- Introduced scoring logic via `ScoreUtils.applyScore(score, joker, cards)`: + - **Greedy Joker**: +3 multiplier per Diamond + - **Devious Joker**: +100 chips if cards form a straight + - **Even Steven**: +4 multiplier per even-valued card + - **Scary Face**: +30 chips per face card (J, Q, K) + +---
Creative Commons License diff --git a/src/main/scala/cards/Card.scala b/src/main/scala/cards/Card.scala index 3d0eec2..13deb02 100644 --- a/src/main/scala/cards/Card.scala +++ b/src/main/scala/cards/Card.scala @@ -7,6 +7,16 @@ package cards * @param suit the suit of the card (e.g., Hearts, Spades) */ class Card(val rank: Rank, val suit: Suit) { + /** + * + */ + def getRank: Rank = rank + + /** + * + * @return + */ + def getSuit: String = suit.name /** * Returns a string representation of the card. diff --git a/src/main/scala/hand/Hand.scala b/src/main/scala/hand/Hand.scala index 6b22d1f..b84eedb 100644 --- a/src/main/scala/hand/Hand.scala +++ b/src/main/scala/hand/Hand.scala @@ -152,7 +152,6 @@ class Hand { cards.clear() throw e } - cards ++= newCards } /** @@ -174,7 +173,6 @@ class Hand { throw e } - jokers ++= newJokers } /** * Returns a string representation of the hand, listing cards and jokers. diff --git a/src/main/scala/score/ScoreUtils.scala b/src/main/scala/score/ScoreUtils.scala new file mode 100644 index 0000000..da35aac --- /dev/null +++ b/src/main/scala/score/ScoreUtils.scala @@ -0,0 +1,95 @@ +package score + +import jokers.Joker +import cards.Card + +object ScoreUtils { + /** + * Applies the effect of a Joker to a given score based on the cards played. + * Each Joker type has a unique effect that modifies either the chip count or multiplier. + * + * - Greedy Joker: +3 multiplier for each Diamond card. + * - Devious Joker: +100 chips if the cards form a straight (consecutive values). + * - Even Steven: +4 multiplier for each card with an even value (2, 4, 6, 8, 10). + * - Scary Face: +30 chips for each face card (Jack, Queen, King). + * + * This method does not use pattern matching or functional programming constructs. + * + * @param score the current score before applying the Joker effect + * @param j the Joker to apply + * @param cards the list of cards played in the current hand + * @return a new Score object with the Joker effect applied + */ + + def applyScore(score: Score, j: Joker, cards: List[Card]): Score = { + val jokerType = j.getJokerType + val baseChips = score.getChips + val baseMultiplier = score.getMultiplier + + val newScore = new Score(baseChips, baseMultiplier) + + if (jokerType == "Greedy Joker") { + var bonus = 0 + var i = 0 + while (i < cards.size) { + if (cards(i).getSuit == "Diamonds") { + bonus += 3 + } + i += 1 + } + newScore.setMultiplier(baseMultiplier + bonus) + } + + else if (jokerType == "Devious Joker") { + + val ranks = new Array[Int](cards.length) + var i = 0 + while (i < cards.length) { + ranks(i) = cards(i).getRank.value + i += 1 + } + + val sortedRanks = ranks.sorted + var isStraight = true + i = 1 + while (i < sortedRanks.length && isStraight) { + if (sortedRanks(i) != sortedRanks(i - 1) + 1) { + isStraight = false + } + i += 1 + } + + if (isStraight && cards.length >= 5) { + newScore.setChips(baseChips + 100) + } + } + + else if (jokerType == "Even Steven") { + var bonus = 0 + var i = 0 + while (i < cards.length) { + val rankValue = cards(i).getRank.value + if (rankValue % 2 == 0) { + bonus += 4 + } + i += 1 + } + newScore.setMultiplier(baseMultiplier + bonus) + } + + else if (jokerType == "Scary Face") { + var bonus = 0 + var i = 0 + while (i < cards.length) { + val value = cards(i).getRank.value + if (value == 11 || value == 12 || value == 13) { // J, Q, K + bonus += 30 + } + i += 1 + } + newScore.setChips(baseChips + bonus) + } + + newScore + } +} diff --git a/src/test/scala/exceptions/ExceptionTest.scala b/src/test/scala/exceptions/ExceptionTest.scala new file mode 100644 index 0000000..acb759b --- /dev/null +++ b/src/test/scala/exceptions/ExceptionTest.scala @@ -0,0 +1,86 @@ +package exceptions + +import munit.FunSuite +import hand.Hand +import cards._ +import jokers.Joker + +class ExceptionTest extends FunSuite { + + val card = new Card(King, Spades) + val joker = new Joker("Greedy Joker") + + test("addCard should throw TooManyCardsException when adding more than 8 cards") { + val hand = new Hand + for (_ <- 1 to 8) { + hand.addCard(card) + } + intercept[TooManyCardsException] { + hand.addCard(card) + } + } + + test("addJoker should throw TooManyJokersException when adding more than 2 jokers") { + val hand = new Hand + hand.addJoker(joker) + hand.addJoker(joker) + intercept[TooManyJokersException] { + hand.addJoker(joker) + } + } + + test("playCards should throw TooManyPlaysException after 3 plays") { + val hand = new Hand + for (_ <- 1 to 5) hand.addCard(card) + + hand.playCards(List(0)) + hand.addCard(card) + hand.playCards(List(0)) + hand.addCard(card) + hand.playCards(List(0)) + hand.addCard(card) + + intercept[TooManyPlaysException] { + hand.playCards(List(0)) + } + } + + test("discardCards should throw TooManyDiscardsException after 3 discards") { + val hand = new Hand + for (_ <- 1 to 5) hand.addCard(card) + + hand.discardCards(List(0)) + hand.addCard(card) + hand.discardCards(List(0)) + hand.addCard(card) + hand.discardCards(List(0)) + hand.addCard(card) + + intercept[TooManyDiscardsException] { + hand.discardCards(List(0)) + } + } + + test("playCards should throw InvalidPlaySizeException if playing more than 5 cards") { + val hand = new Hand + for (_ <- 1 to 6) hand.addCard(card) + intercept[InvalidPlaySizeException] { + hand.playCards(List(0, 1, 2, 3, 4, 5)) + } + } + + test("discardCards should throw InvalidPlaySizeException if discarding less than 1 card") { + val hand = new Hand + hand.addCard(card) + intercept[InvalidPlaySizeException] { + hand.discardCards(List()) + } + } + + test("removeJokerAtIndex should throw InvalidJokerIndexException for out-of-bounds index") { + val hand = new Hand + intercept[InvalidJokerIndexException] { + hand.removeJokerAtIndex(0) + } + } +} diff --git a/src/test/scala/hand/HandTest.scala b/src/test/scala/hand/HandTest.scala index 1e34069..b165e71 100644 --- a/src/test/scala/hand/HandTest.scala +++ b/src/test/scala/hand/HandTest.scala @@ -1,8 +1,9 @@ package hand import munit.FunSuite -import cards._ -import jokers._ +import cards.* +import exceptions.InvalidCardIndexException +import jokers.* class HandTest extends FunSuite { @@ -60,21 +61,6 @@ class HandTest extends FunSuite { assertEquals(hand.getCards, List(card1, card3), "Remaining cards should be Ace of Hearts and Ten of Diamonds in order") } - test("removeCardAtIndex should do nothing for invalid negative index (as per requirement relaxation)") { - val hand = new Hand - hand.addCard(card1) - val initialCards = hand.getCards - hand.removeCardAtIndex(-1) - assertEquals(hand.getCards, initialCards, "Cards should not change for invalid negative index") - } - - test("removeCardAtIndex should do nothing for invalid out-of-bounds index (as per requirement relaxation)") { - val hand = new Hand - hand.addCard(card1) - val initialCards = hand.getCards - hand.removeCardAtIndex(1) - assertEquals(hand.getCards, initialCards, "Cards should not change for invalid out-of-bounds index") - } test("addJoker should add a joker to the hand's joker list") { @@ -155,14 +141,6 @@ class HandTest extends FunSuite { assertEquals(cards, List(card1, card2), "getCards should return all added cards in order") } - test("setCards should replace the current list of cards") { - val hand = new Hand - hand.addCard(card1) - assertEquals(hand.getCards, List(card1), "Initial card should be card1") - - hand.setCards(List(card2, card3)) - assertEquals(hand.getCards, List(card2, card3), "Cards should be replaced by card2 and card3") - } test("getJokers should return the current list of jokers") { val hand = new Hand @@ -172,6 +150,15 @@ class HandTest extends FunSuite { assertEquals(jokers, List(joker1), "getJokers should return the added joker") } + test("setCards should replace the current list of cards") { + val hand = new Hand + hand.addCard(card1) + assertEquals(hand.getCards, List(card1), "Initial card should be card1") + + hand.setCards(List(card2, card3)) + assertEquals(hand.getCards, List(card2, card3), "Cards should be replaced by card2 and card3") + } + test("setJokers should replace the current list of jokers") { val hand = new Hand hand.addJoker(joker1) @@ -180,5 +167,23 @@ class HandTest extends FunSuite { hand.setJokers(List(joker2)) assertEquals(hand.getJokers, List(joker2), "Jokers should be replaced by joker2") } - + + test("removeCardAtIndex should throw exception for invalid negative index") { + val hand = new Hand + hand.addCard(card1) + + intercept[InvalidCardIndexException] { + hand.removeCardAtIndex(-1) + } + } + + test("removeCardAtIndex should throw exception for invalid out-of-bounds index") { + val hand = new Hand + hand.addCard(card1) + + intercept[InvalidCardIndexException] { + hand.removeCardAtIndex(1) + } + } + }