diff --git a/src/main/scala/combinations/Flush.scala b/src/main/scala/combinations/Flush.scala new file mode 100644 index 0000000..6ac5ff4 --- /dev/null +++ b/src/main/scala/combinations/Flush.scala @@ -0,0 +1,18 @@ +package combinations + +import cards._ +import score._ + +case class Flush() extends PokerCombination { + val name: String = "Flush" + val score: Score = Score(35, 4) + def verify(cards: List[Card]): Boolean = { + var sameSuit = true + for (card <- cards) { + if (card.suit != cards.head.suit) { + sameSuit = false + } + } + cards.length == 5 && sameSuit + } +} diff --git a/src/main/scala/combinations/FourOfAKind.scala b/src/main/scala/combinations/FourOfAKind.scala new file mode 100644 index 0000000..dc06298 --- /dev/null +++ b/src/main/scala/combinations/FourOfAKind.scala @@ -0,0 +1,25 @@ +package combinations + +import cards._ +import score._ + +case class FourOfAKind() extends PokerCombination { + val name: String = "FourOfAKind" + val score: Score = Score(60, 7) + + def verify(cards: List[Card]): Boolean = { + var bool: Boolean = false + for (pokerCard <- cards){ + var i:Int= 0 + for(card <- cards){ + if(card.rank == pokerCard.rank){ + i=i+1 + } + } + if(i == 4){ + bool = true + } + } + bool + } +} diff --git a/src/main/scala/combinations/FullHouse.scala b/src/main/scala/combinations/FullHouse.scala new file mode 100644 index 0000000..5621e09 --- /dev/null +++ b/src/main/scala/combinations/FullHouse.scala @@ -0,0 +1,39 @@ +package combinations + +import cards._ +import score._ + +class FullHouse extends PokerCombination { + val name: String = "FullHouse" + val score: Score = new Score(40, 4) + + def verify(cards: List[Card]): Boolean = { + var hasThreeOfAKind = false + var hasPair = false + + for (threeCandidate <- cards) { + var threeCount = 0 + for (card <- cards) { + if (card.rank == threeCandidate.rank) { + threeCount += 1 + } + } + if (threeCount == 3) { + hasThreeOfAKind = true + for (pairCandidate <- cards if pairCandidate.rank != threeCandidate.rank) { + var pairCount = 0 + for (other <- cards) { + if (other.rank == pairCandidate.rank) { + pairCount += 1 + } + } + if (pairCount == 2) { + hasPair = true + } + } + } + } + + hasThreeOfAKind && hasPair + } +} diff --git a/src/main/scala/combinations/HighCard.scala b/src/main/scala/combinations/HighCard.scala new file mode 100644 index 0000000..36d1cb9 --- /dev/null +++ b/src/main/scala/combinations/HighCard.scala @@ -0,0 +1,31 @@ +package combinations + +import cards._ +import score._ + +case class HighCard() extends PokerCombination { + val name: String = "HighCard" + val score: Score = Score(5, 1) + + def verify(cards: List[Card]): Boolean = { + val straightFlush = new StraightFlush() + val fourOfAKind = new FourOfAKind() + val fullHouse = new FullHouse() + val flush = new Flush() + val straight = new Straight() + val threeOfAKind = new ThreeOfAKind() + val twoPair = new TwoPair() + val pair = new Pair() + + if (straightFlush.verify(cards)) return false + if (fourOfAKind.verify(cards)) return false + if (fullHouse.verify(cards)) return false + if (flush.verify(cards)) return false + if (straight.verify(cards)) return false + if (threeOfAKind.verify(cards)) return false + if (twoPair.verify(cards)) return false + if (pair.verify(cards)) return false + + true + } +} diff --git a/src/main/scala/combinations/Pair.scala b/src/main/scala/combinations/Pair.scala new file mode 100644 index 0000000..4349849 --- /dev/null +++ b/src/main/scala/combinations/Pair.scala @@ -0,0 +1,25 @@ +package combinations + +import cards._ +import score._ + +case class Pair() extends PokerCombination { + val name: String = "Pair" + val score: Score = Score(10, 2) + + def verify(cards: List[Card]): Boolean = { + var bool: Boolean = false + for (pokerCard <- cards){ + var i:Int= 0 + for(card <- cards){ + if(card.rank == pokerCard.rank){ + i=i+1 + } + } + if(i == 2){ + bool = true + } + } + bool + } +} diff --git a/src/main/scala/combinations/Straight.scala b/src/main/scala/combinations/Straight.scala new file mode 100644 index 0000000..8922749 --- /dev/null +++ b/src/main/scala/combinations/Straight.scala @@ -0,0 +1,16 @@ +package combinations + +import cards._ +import score._ + +class Straight extends PokerCombination { + val name: String = "Straight" + val score: Score = new Score(30, 4) + + def verify(cards: List[Card]): Boolean = { + if (cards.length != 5) return false + + val sortedRanks = cards.map(_.rank.value).distinct.sorted + sortedRanks.length == 5 && sortedRanks.last - sortedRanks.head == 4 + } +} diff --git a/src/main/scala/combinations/StraightFlush.scala b/src/main/scala/combinations/StraightFlush.scala new file mode 100644 index 0000000..6ce9203 --- /dev/null +++ b/src/main/scala/combinations/StraightFlush.scala @@ -0,0 +1,17 @@ +package combinations + +import cards._ +import score._ + +class StraightFlush extends PokerCombination { + val name: String = "StraightFlush" + val score: Score = new Score(100, 8) + + def verify(cards: List[Card]): Boolean = { + if (cards.length != 5) return false + + val sameSuit = cards.forall(_.suit == cards.head.suit) + val sortedRanks = cards.map(_.rank.value).distinct.sorted + sameSuit && sortedRanks.length == 5 && sortedRanks.last - sortedRanks.head == 4 + } +} diff --git a/src/main/scala/combinations/ThreeOfAKind.scala b/src/main/scala/combinations/ThreeOfAKind.scala new file mode 100644 index 0000000..6c48d93 --- /dev/null +++ b/src/main/scala/combinations/ThreeOfAKind.scala @@ -0,0 +1,28 @@ +package combinations + +import cards._ +import score._ + +class ThreeOfAKind extends PokerCombination { + val name: String = "ThreeOfAKind" + val score: Score = new Score(30, 3) + + def verify(cards: List[Card]): Boolean = { + var found = false + + for (candidate <- cards) { + var count = 0 + for (card <- cards) { + if (card.rank == candidate.rank) { + count += 1 + } + } + if (count == 3) { + found = true + } + } + + found + } + +} diff --git a/src/main/scala/combinations/TwoPair.scala b/src/main/scala/combinations/TwoPair.scala new file mode 100644 index 0000000..5c6c796 --- /dev/null +++ b/src/main/scala/combinations/TwoPair.scala @@ -0,0 +1,23 @@ +package combinations + +import cards._ +import score._ + +class TwoPair extends PokerCombination { + val name: String = "TwoPair" + val score: Score = new Score(20, 2) + + def verify(cards: List[Card]): Boolean = { + var pairs = Set[Rank]() + + for (a <- cards) { + var count = 0 + for (b <- cards) { + if (a.rank == b.rank) count += 1 + } + if (count == 2) pairs += a.rank + } + + pairs.size == 2 + } +} diff --git a/src/main/scala/hand/Hand.scala b/src/main/scala/hand/Hand.scala index 8e65122..830cd75 100644 --- a/src/main/scala/hand/Hand.scala +++ b/src/main/scala/hand/Hand.scala @@ -5,62 +5,110 @@ import jokers.Joker import scala.collection.mutable.ListBuffer /** - * Represents a Hand that contains a set of Cards and a set of Jokers. - * Does not include any extra logic or scoring yet. + * Represents a Hand in Balatro, containing Cards and Jokers. + * Allows adding/removing cards and jokers by index, and playing cards. */ class Hand { - // Mutable collections to store cards and jokers internally private val cards = ListBuffer[Card]() private val jokers = ListBuffer[Joker]() /** * Adds a Card to the hand. + * Assumes there is space in the hand. * - * @param card the card to add + * @param card The Card object to add. */ - def addCard(card: Card): Unit = cards += card + def addCard(card: Card): Unit = { + cards += card + } /** - * Removes a Card from the hand. + * Removes a Card from the hand at the specified index. + * Assumes the provided index is valid as per requirements. + * Includes a basic check for safety during development. * - * @param card the card to remove + * @param index The zero-based index of the card to remove. */ - def removeCard(card: Card): Unit = cards -= card + def removeCardAtIndex(index: Int): Unit = { + // Basic check, although requirements assume valid index. + if (index >= 0 && index < cards.length) { + cards.remove(index) + } else { + // As per requirements, we don't need to strictly handle invalid indices for the final submission. + println(s"Warning: Attempted to remove card at invalid index $index. Hand size: ${cards.length}") + } + } /** * Adds a Joker to the hand. + * Assumes there is space for the joker. * - * @param joker the Joker to add + * @param joker The Joker object to add. */ - def addJoker(joker: Joker): Unit = jokers += joker + def addJoker(joker: Joker): Unit = { + jokers += joker + } /** - * Removes a Joker from the hand. + * Removes a Joker from the hand at the specified index. + * Assumes the provided index is valid as per requirements. + * Includes a basic check for safety during development. * - * @param joker the Joker to remove + * @param index The zero-based index of the joker to remove. */ - def removeJoker(joker: Joker): Unit = jokers -= joker + def removeJokerAtIndex(index: Int): Unit = { + // Basic check, although requirements assume valid index. + if (index >= 0 && index < jokers.length) { + jokers.remove(index) + } else { + // As per requirements, we don't need to strictly handle invalid indices for the final submission. + println(s"Warning: Attempted to remove joker at invalid index $index. Joker count: ${jokers.length}") + } + } /** - * Provides an immutable list of cards currently in the hand. + * "Plays" cards from the hand based on a list of indices. + * This involves returning the selected cards and removing them from the hand. + * Assumes the indices provided are valid and correspond to cards in the hand. + * Assumes the list of indices contains between 1 and 5 elements. * - * @return a list of cards contained in the hand + * @param indices A list of zero-based indices corresponding to the cards to be played. + * @return A list containing the Card objects that were played (and thus removed). + */ + def playCards(indices: List[Int]): List[Card] = { + // Assumes indices are valid and within bounds as per requirements. + val cardsToPlay = indices.map(index => cards(index)).toList + // Removes the selected cards from the hand. + cards --= cardsToPlay + // Returns the list of cards that were played. + cardsToPlay + } + + /** + * Provides an immutable view of the cards currently in the hand. + * Useful for displaying the hand or passing it to other components + * without allowing external modification of the internal state. + * + * @return An immutable List[Card] representing the cards in hand. */ def getCards: List[Card] = cards.toList /** - * Provides an immutable list of jokers currently in the hand. + * Provides an immutable view of the jokers currently in the hand. * - * @return a list of jokers contained in the hand + * @return An immutable List[Joker] representing the jokers in hand. */ def getJokers: List[Joker] = jokers.toList - /** * Returns a string representation of the hand, listing cards and jokers. + * Useful for debugging and logging purposes. * - * @return a string of cards and jokers + * @return A String describing the current state of the hand. */ - override def toString: String = - s"Hand(cards: $cards, jokers: $jokers)" + override def toString: String = { + val cardsString = cards.mkString(", ") + val jokersString = jokers.mkString(", ") + s"Hand(Cards: [$cardsString], Jokers: [$jokersString])" + } } diff --git a/src/test/scala/hand/HandTest.scala b/src/test/scala/hand/HandTest.scala index 2367d88..453e376 100644 --- a/src/test/scala/hand/HandTest.scala +++ b/src/test/scala/hand/HandTest.scala @@ -5,59 +5,145 @@ import cards._ import jokers._ class HandTest extends FunSuite { - test("Hand toString returns a string with cards and jokers") { + + val card1: Card = new Card(Ace, Hearts) + val card2: Card = new Card(King, Spades) + val card3: Card = new Card(Ten, Diamonds) + val card4: Card = new Card(Two, Clubs) + + val joker1: Joker = new Joker("Devious Joker") + val joker2: Joker = new Joker("Greedy Joker") + + test("Hand toString returns a string with current cards and jokers") { val hand = new Hand() - - hand.addCard(new Card(Five, Hearts)) - hand.addCard(new Card(King, Spades)) - - hand.addJoker(new Joker("Devious Joker")) - hand.addJoker(new Joker("Greedy Joker")) + hand.addCard(card1) + hand.addCard(card2) + hand.addJoker(joker1) val result = hand.toString - assert(result.contains("cards")) - assert(result.contains("jokers")) - assert(result.contains("Five")) - assert(result.contains("King")) - assert(result.contains("Devious Joker")) - assert(result.contains("Greedy Joker")) - } - test("addCard should add a card to the hand") { - val hand = new Hand - val card = new Card(Ace, Hearts) - - hand.addCard(card) - - assert(hand.getCards.contains(card)) + assert(result.contains("Cards:"), "toString should contain 'Cards:' label") + assert(result.contains("Jokers:"), "toString should contain 'Jokers:' label") + assert(result.contains(card1.toString), s"toString should contain ${card1.toString}") + assert(result.contains(card2.toString), s"toString should contain ${card2.toString}") + assert(result.contains(joker1.toString), s"toString should contain ${joker1.toString}") + assert(!result.contains(card3.toString), s"toString should not contain ${card3.toString}") + assert(!result.contains(joker2.toString), s"toString should not contain ${joker2.toString}") } - test("removeCard should remove a card from the hand") { + test("addCard should add a card to the hand's card list") { val hand = new Hand - val card = new Card(Ace, Hearts) + assertEquals(hand.getCards.length, 0, "Hand should start empty") - hand.addCard(card) - hand.removeCard(card) + hand.addCard(card1) + assertEquals(hand.getCards.length, 1, "Hand should have 1 card after adding one") + assert(hand.getCards.contains(card1), "Hand should contain the added card") - assert(!hand.getCards.contains(card)) + hand.addCard(card2) + assertEquals(hand.getCards.length, 2, "Hand should have 2 cards after adding another") + assert(hand.getCards.contains(card2), "Hand should contain the second added card") } - test("addJoker should add a joker to the hand") { + test("removeCardAtIndex should remove the card at the specified index") { val hand = new Hand - val joker = new Joker("Greedy Joker") + hand.addCard(card1) + hand.addCard(card2) + hand.addCard(card3) + assertEquals(hand.getCards.length, 3, "Initial hand size should be 3") - hand.addJoker(joker) + hand.removeCardAtIndex(1) - assert(hand.getJokers.contains(joker)) + assertEquals(hand.getCards.length, 2, "Hand size should be 2 after removal") + assert(!hand.getCards.contains(card2), "Removed card (King of Spades) should not be in hand") + assert(hand.getCards.contains(card1), "Card before removed index (Ace of Hearts) should still be in hand") + assert(hand.getCards.contains(card3), "Card after removed index (Ten of Diamonds) should still be in hand") + assertEquals(hand.getCards, List(card1, card3), "Remaining cards should be Ace of Hearts and Ten of Diamonds in order") } - test("removeJoker should remove a joker from the hand") { + test("removeCardAtIndex should do nothing for invalid negative index (as per requirement relaxation)") { val hand = new Hand - val joker = new Joker("Greedy Joker") + hand.addCard(card1) + val initialCards = hand.getCards + hand.removeCardAtIndex(-1) + assertEquals(hand.getCards, initialCards, "Cards should not change for invalid negative index") + } - hand.addJoker(joker) - hand.removeJoker(joker) + 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") + } - assert(!hand.getJokers.contains(joker)) + + test("addJoker should add a joker to the hand's joker list") { + val hand = new Hand + assertEquals(hand.getJokers.length, 0, "Hand should start with no jokers") + + hand.addJoker(joker1) + assertEquals(hand.getJokers.length, 1, "Hand should have 1 joker after adding one") + assert(hand.getJokers.contains(joker1), "Hand should contain the added joker") + } + + test("removeJokerAtIndex should remove the joker at the specified index") { + val hand = new Hand + hand.addJoker(joker1) + hand.addJoker(joker2) + assertEquals(hand.getJokers.length, 2, "Initial joker count should be 2") + + hand.removeJokerAtIndex(0) + + assertEquals(hand.getJokers.length, 1, "Joker count should be 1 after removal") + assert(!hand.getJokers.contains(joker1), "Removed joker (Devious Joker) should not be in hand") + assert(hand.getJokers.contains(joker2), "Remaining joker (Greedy Joker) should still be in hand") + assertEquals(hand.getJokers, List(joker2), "Remaining joker should be Greedy Joker") + } + + test("playCards should return the selected cards and remove them from the hand") { + val hand = new Hand + hand.addCard(card1) + hand.addCard(card2) + hand.addCard(card3) + hand.addCard(card4) + assertEquals(hand.getCards.length, 4, "Initial hand size should be 4") + + val playedIndices = List(1, 3) + val playedCards = hand.playCards(playedIndices) + + assertEquals(playedCards.length, 2, "Should return 2 cards") + assert(playedCards.contains(card2), "Returned list should contain King of Spades") + assert(playedCards.contains(card4), "Returned list should contain Two of Clubs") + assertEquals(playedCards, List(card2, card4), "Returned list should be King Spades, Two Clubs") + + assertEquals(hand.getCards.length, 2, "Hand size should be 2 after playing cards") + assert(!hand.getCards.contains(card2), "Played card (King Spades) should be removed from hand") + assert(!hand.getCards.contains(card4), "Played card (Two Clubs) should be removed from hand") + assert(hand.getCards.contains(card1), "Non-played card (Ace Hearts) should remain in hand") + assert(hand.getCards.contains(card3), "Non-played card (Ten Diamonds) should remain in hand") + assertEquals(hand.getCards, List(card1, card3), "Remaining cards should be Ace Hearts and Ten Diamonds") + } + + test("playCards should handle playing a single card") { + val hand = new Hand + hand.addCard(card1) + hand.addCard(card2) + val playedIndices = List(0) + val playedCards = hand.playCards(playedIndices) + + assertEquals(playedCards, List(card1), "Should return the single played card") + assertEquals(hand.getCards, List(card2), "Remaining card should be King Spades") + } + + test("playCards should handle playing multiple cards with adjacent indices") { + val hand = new Hand + hand.addCard(card1) + hand.addCard(card2) + hand.addCard(card3) + val playedIndices = List(0, 1) + val playedCards = hand.playCards(playedIndices) + + assertEquals(playedCards, List(card1, card2), "Should return the two played cards") + assertEquals(hand.getCards, List(card3), "Remaining card should be Ten Diamonds") } }