diff --git a/src/utils/GameUtils.spec.ts b/src/utils/GameUtils.spec.ts index 703985f..47d2eb6 100644 --- a/src/utils/GameUtils.spec.ts +++ b/src/utils/GameUtils.spec.ts @@ -3,9 +3,11 @@ import { areAllTrumpCards, bestCardLead, calculateMinCardsToKeep, + canRenege, compareCards, containsATrumpCard, getBestCard, + getColdCards, getTrumpCards, getWorstCard, padMyHand, @@ -48,14 +50,44 @@ const ROUND_BEST_LEAD: Round = { dealerId: "", status: RoundStatus.PLAYING, dealerSeeingCall: false, - completedHands: [], + completedHands: [ + { + leadOut: CardName.FIVE_DIAMONDS, + timestamp: "", + currentPlayerId: "", + playedCards: [{ card: CardName.ACE_HEARTS, playerId: "blah" }], + }, + ], +} + +const ROUND_WILD_LEAD: Round = { + suit: Suit.HEARTS, + currentHand: { + leadOut: CardName.JOKER, + timestamp: "", + currentPlayerId: "", + playedCards: [], + }, + timestamp: "", + number: 0, + dealerId: "", + status: RoundStatus.PLAYING, + dealerSeeingCall: false, + completedHands: [ + { + leadOut: CardName.FIVE_DIAMONDS, + timestamp: "", + currentPlayerId: "", + playedCards: [{ card: CardName.ACE_HEARTS, playerId: "blah" }], + }, + ], } const HAND1: Card[] = [ { ...CARDS.TWO_HEARTS, selected: true }, CARDS.THREE_HEARTS, CARDS.FOUR_HEARTS, - CARDS.FIVE_HEARTS, + CARDS.JACK_HEARTS, CARDS.SIX_HEARTS, ] @@ -77,6 +109,13 @@ const HAND4: Card[] = [ CARDS.TWO_DIAMONDS, ] +const HAND_RENEG: Card[] = [ + CARDS.THREE_DIAMONDS, + CARDS.TWO_CLUBS, + CARDS.THREE_CLUBS, + CARDS.FIVE_HEARTS, +] + describe("GameUtils", () => { describe("removeEmptyCards", () => { it("empty hand", () => { @@ -140,6 +179,12 @@ describe("GameUtils", () => { expect(compareCards([], [])).toBe(true) }) + it("different length hands", () => { + expect(compareCards([...HAND1], [...HAND1, CARDS.ACE_CLUBS])).toBe( + false, + ) + }) + it("equal hands", () => { expect(compareCards([...HAND1], [...HAND1])).toBe(true) }) @@ -339,7 +384,7 @@ describe("GameUtils", () => { ), ).toStrictEqual([ CARDS.THREE_HEARTS, - CARDS.FIVE_HEARTS, + CARDS.JACK_HEARTS, CARDS.SIX_HEARTS, ]) }) @@ -352,7 +397,7 @@ describe("GameUtils", () => { ), ).toStrictEqual([ CARDS.THREE_HEARTS, - CARDS.FIVE_HEARTS, + CARDS.JACK_HEARTS, CARDS.SIX_HEARTS, ]) }) @@ -428,6 +473,34 @@ describe("GameUtils", () => { }) }) + describe("getColdCards", () => { + it("empty hand", () => { + expect(getColdCards([], Suit.HEARTS)).toStrictEqual([]) + }) + + it("all cold cards", () => { + expect(getColdCards([...HAND1], Suit.HEARTS)).toStrictEqual([]) + }) + + it("no cold cards", () => { + expect(getColdCards([...HAND3], Suit.HEARTS)).toStrictEqual(HAND3) + }) + + it("all cold cards with joker and ace of hearts", () => { + expect(getColdCards([...HAND4], Suit.HEARTS)).toStrictEqual([ + CARDS.THREE_CLUBS, + CARDS.TWO_DIAMONDS, + ]) + }) + + it("some cold cards", () => { + expect(getColdCards([...HAND3], Suit.SPADES)).toStrictEqual([ + CARDS.THREE_DIAMONDS, + CARDS.TWO_CLUBS, + ]) + }) + }) + describe("bestCardLead", () => { it("no suit", () => { expect(bestCardLead({ ...ROUND, suit: undefined })).toBe(false) @@ -446,11 +519,11 @@ describe("GameUtils", () => { describe("getBestCard", () => { it("empty hand", () => { - expect(getBestCard([], ROUND)).toBe(undefined) + expect(() => getBestCard([], ROUND)).toThrow() }) it("trump card", () => { - expect(getBestCard([...HAND1], ROUND)).toStrictEqual( - CARDS.FIVE_HEARTS, + expect(getBestCard([...HAND1], ROUND_BEST_LEAD)).toStrictEqual( + CARDS.JACK_HEARTS, ) }) it("follow cold card", () => { @@ -460,21 +533,99 @@ describe("GameUtils", () => { }) }) - describe("getWorstCard", () => { - it("empty hand", () => { - expect(getBestCard([], ROUND)).toBe(undefined) + describe("canRenege", () => { + it("not a trump card", () => { + expect(() => + canRenege(CARDS.TWO_CLUBS, CARDS.TWO_HEARTS, Suit.HEARTS), + ).toThrow() }) - it("trump card", () => { - expect(getWorstCard([...HAND1], ROUND)).toStrictEqual({ - ...CARDS.TWO_HEARTS, - selected: true, - }) + it("not a trump card", () => { + expect(() => + canRenege(CARDS.TWO_HEARTS, CARDS.TWO_CLUBS, Suit.HEARTS), + ).toThrow() }) - it("follow cold card", () => { - expect(getWorstCard([...HAND3], ROUND)).toStrictEqual( - CARDS.TWO_CLUBS, + it("not a renegable card", () => { + expect( + canRenege(CARDS.THREE_HEARTS, CARDS.TWO_HEARTS, Suit.HEARTS), + ).toBe(false) + }) + it("renegable card", () => { + expect( + canRenege(CARDS.ACE_HEARTS, CARDS.TWO_HEARTS, Suit.HEARTS), + ).toBe(true) + }) + it("renegable card", () => { + expect(canRenege(CARDS.JOKER, CARDS.TWO_HEARTS, Suit.HEARTS)).toBe( + true, + ) + }) + it("renegable card", () => { + expect( + canRenege(CARDS.JACK_HEARTS, CARDS.TWO_HEARTS, Suit.HEARTS), + ).toBe(true) + }) + it("renegable card", () => { + expect( + canRenege(CARDS.FIVE_HEARTS, CARDS.TWO_HEARTS, Suit.HEARTS), + ).toBe(true) + }) + it("renegable card lower than card lead out", () => { + expect(canRenege(CARDS.ACE_HEARTS, CARDS.JOKER, Suit.HEARTS)).toBe( + false, ) }) + it("renegable card lower than card lead out", () => { + expect( + canRenege(CARDS.ACE_HEARTS, CARDS.JACK_HEARTS, Suit.HEARTS), + ).toBe(false) + }) + it("renegable card lower than card lead out", () => { + expect( + canRenege(CARDS.ACE_HEARTS, CARDS.FIVE_HEARTS, Suit.HEARTS), + ).toBe(false) + }) + it("renegable card lower than card lead out", () => { + expect(canRenege(CARDS.JOKER, CARDS.JACK_HEARTS, Suit.HEARTS)).toBe( + false, + ) + }) + it("renegable card lower than card lead out", () => { + expect(canRenege(CARDS.JOKER, CARDS.FIVE_HEARTS, Suit.HEARTS)).toBe( + false, + ) + }) + it("renegable card lower than card lead out", () => { + expect( + canRenege(CARDS.JACK_HEARTS, CARDS.FIVE_HEARTS, Suit.HEARTS), + ).toBe(false) + }) + }) + + describe("getWorstCard", () => { + // it("empty hand", () => { + // expect(() => getBestCard([], ROUND)).toThrow() + // }) + // it("trump card", () => { + // expect(getWorstCard([...HAND1], ROUND)).toStrictEqual({ + // ...CARDS.TWO_HEARTS, + // selected: true, + // }) + // }) + // it("follow cold card", () => { + // expect(getWorstCard([...HAND3], ROUND)).toStrictEqual( + // CARDS.TWO_CLUBS, + // ) + // }) + // it("wild cards lead", () => { + // expect(getWorstCard([...HAND4], ROUND_WILD_LEAD)).toStrictEqual( + // CARDS.TWO_HEARTS, + // ) + // }) + it("Can reneg five", () => { + expect( + getWorstCard([...HAND_RENEG], ROUND_WILD_LEAD), + ).toStrictEqual(CARDS.THREE_DIAMONDS) + }) }) describe("calculateMinCardsToKeep", () => { @@ -516,13 +667,13 @@ describe("GameUtils", () => { }) it("Must keep 2", () => { expect(pickBestCards([...HAND1], Suit.DIAMONDS, 6)).toStrictEqual([ + CARDS.JACK_HEARTS, CARDS.SIX_HEARTS, - CARDS.FIVE_HEARTS, ]) }) it("Must keep 1", () => { expect(pickBestCards([...HAND1], Suit.DIAMONDS, 5)).toStrictEqual([ - CARDS.SIX_HEARTS, + CARDS.JACK_HEARTS, ]) }) it("Must keep 0", () => { diff --git a/src/utils/GameUtils.ts b/src/utils/GameUtils.ts index 7f83cfb..79ea9fb 100644 --- a/src/utils/GameUtils.ts +++ b/src/utils/GameUtils.ts @@ -5,14 +5,7 @@ import { Suit } from "model/Suit" export const removeEmptyCards = (cards: Card[]): Card[] => [...cards].filter(c => c.name !== CardName.EMPTY) -export const compareCards = ( - hand1: Card[] | undefined, - hand2: Card[] | undefined, -) => { - if (!Array.isArray(hand1) || !Array.isArray(hand2)) { - return false - } - +export const compareCards = (hand1: Card[], hand2: Card[]) => { let h1 = removeEmptyCards(hand1) let h2 = removeEmptyCards(hand2) @@ -149,12 +142,10 @@ export const riskOfMistakeBuyingCards = ( } export const getTrumpCards = (cards: Card[], suit: Suit): Card[] => - cards.filter( - card => - card.suit === suit || - card.name === CardName.JOKER || - card.name === CardName.ACE_HEARTS, - ) + cards.filter(card => card.suit === suit || card.suit === Suit.WILD) + +export const getColdCards = (cards: Card[], suit: Suit): Card[] => + cards.filter(card => card.suit !== suit && card.suit !== Suit.WILD) export const bestCardLead = (round: Round) => { if (!round.suit) return false @@ -179,15 +170,63 @@ export const bestCardLead = (round: Round) => { return round.currentHand.leadOut === trumpCards[0].name } +export const canRenege = (myCard: Card, cardLead: Card, suit: Suit) => { + // If the card lead isn't a trump card then throw an error + if (cardLead.suit !== suit && cardLead.suit !== Suit.WILD) { + throw new Error("Card lead must be a trump card") + } + + // If my card isn't a trump card then throw an error + if (myCard.suit !== suit && myCard.suit !== Suit.WILD) { + throw new Error("My card must be a trump card") + } + + // If my card has a value greater than 112 and greater than the card lead then you can renege + return myCard.value >= 112 && myCard.value > cardLead.value +} + export const getWorstCard = (cards: Card[], round: Round) => { - if (cards.length === 0) return undefined + if (cards.length === 0) throw new Error("No cards to choose from") // Check if must follow suit + const roundSuit = round.suit + if (!roundSuit) throw new Error("Round suit cannot be undefined") const leadOut = round.currentHand?.leadOut let suitLead = leadOut ? CARDS[leadOut]?.suit : undefined + if (suitLead === Suit.WILD) suitLead = roundSuit + + const myTrumpCards = getTrumpCards(cards, roundSuit).sort( + (a, b) => a.value - b.value, + ) + const myColdCards = getColdCards(cards, roundSuit).sort( + (a, b) => a.coldValue - b.coldValue, + ) + + // If no card lead play the worst card + if (!leadOut) { + // If we have cold cards then play the worst one + if (myColdCards.length > 0) { + return myColdCards[0] + } + // Otherwise play the worst trump card + else if (myTrumpCards.length > 0) { + return myTrumpCards[0] + } else throw new Error("No cards to choose from") + } - if (suitLead === Suit.WILD) { - suitLead = round.suit + // Handle when suit lead is the trump suit + if (suitLead === roundSuit) { + // Get trump cards that aren't renegable + const notRenegableTrumpCards = myTrumpCards.filter( + c => !canRenege(c, CARDS[leadOut], roundSuit), + ) + if (notRenegableTrumpCards.length > 0) { + return notRenegableTrumpCards[0] + } else if (myColdCards.length > 0) { + return myColdCards[0] + } else if (myTrumpCards.length > 0) { + return myTrumpCards[0] + } else throw new Error("No cards to choose from") } if (suitLead) { @@ -206,7 +245,7 @@ export const getWorstCard = (cards: Card[], round: Round) => { } export const getBestCard = (cards: Card[], round: Round) => { - if (cards.length === 0) return undefined + if (cards.length === 0) throw new Error("No cards to choose from") // Check for trump cards const myTrumpCards = cards.filter(