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