Skip to content

Commit

Permalink
fix: renege logic
Browse files Browse the repository at this point in the history
  • Loading branch information
daithihearn committed Oct 3, 2023
1 parent 87d4213 commit 442a5ae
Show file tree
Hide file tree
Showing 2 changed files with 228 additions and 38 deletions.
191 changes: 171 additions & 20 deletions src/utils/GameUtils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import {
areAllTrumpCards,
bestCardLead,
calculateMinCardsToKeep,
canRenege,
compareCards,
containsATrumpCard,
getBestCard,
getColdCards,
getTrumpCards,
getWorstCard,
padMyHand,
Expand Down Expand Up @@ -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,
]

Expand All @@ -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", () => {
Expand Down Expand Up @@ -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)
})
Expand Down Expand Up @@ -339,7 +384,7 @@ describe("GameUtils", () => {
),
).toStrictEqual([
CARDS.THREE_HEARTS,
CARDS.FIVE_HEARTS,
CARDS.JACK_HEARTS,
CARDS.SIX_HEARTS,
])
})
Expand All @@ -352,7 +397,7 @@ describe("GameUtils", () => {
),
).toStrictEqual([
CARDS.THREE_HEARTS,
CARDS.FIVE_HEARTS,
CARDS.JACK_HEARTS,
CARDS.SIX_HEARTS,
])
})
Expand Down Expand Up @@ -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)
Expand All @@ -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", () => {
Expand All @@ -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", () => {
Expand Down Expand Up @@ -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", () => {
Expand Down
75 changes: 57 additions & 18 deletions src/utils/GameUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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(
Expand Down

0 comments on commit 442a5ae

Please sign in to comment.