feat(hand): implement index-based removal and playCards method, update tests

This commit is contained in:
2025-05-05 16:59:18 -04:00
parent b911b1e648
commit cc59d4aaff
11 changed files with 412 additions and 56 deletions

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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])"
}
}

View File

@@ -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")
}
}