From af4d13cc577b0a4420865bd981a7be0ba5401ad2 Mon Sep 17 00:00:00 2001 From: Daithi Hearn Date: Fri, 29 Sep 2023 15:08:49 +0200 Subject: [PATCH 1/2] fix: best and worst card selection --- src/caches/PlayCardSlice.ts | 4 +- src/components/Game/Actions/PlayCard.tsx | 10 ++-- src/utils/GameUtils.spec.ts | 50 ++++++++++++++++++- src/utils/GameUtils.ts | 61 +++++++++++++++++------- 4 files changed, 100 insertions(+), 25 deletions(-) diff --git a/src/caches/PlayCardSlice.ts b/src/caches/PlayCardSlice.ts index 7509f28..956180e 100644 --- a/src/caches/PlayCardSlice.ts +++ b/src/caches/PlayCardSlice.ts @@ -1,9 +1,9 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit" -import { Card } from "model/Cards" +import { Card, CardName } from "model/Cards" import { RootState } from "./caches" export interface AutoPlayState { - card?: string + card?: CardName } const initialState: AutoPlayState = {} diff --git a/src/components/Game/Actions/PlayCard.tsx b/src/components/Game/Actions/PlayCard.tsx index 11695aa..f14ce10 100644 --- a/src/components/Game/Actions/PlayCard.tsx +++ b/src/components/Game/Actions/PlayCard.tsx @@ -92,10 +92,12 @@ const PlayCard = () => { ) const playCard = useCallback( - (card: string) => + (card: CardName) => { + console.debug(`Playing card ${card}`) dispatch(GameService.playCard(gameId!, card)).catch(e => { console.error(parseError(e)) - }), + }) + }, [gameId], ) @@ -111,10 +113,10 @@ const PlayCard = () => { if (round?.suit && isMyGo) { if (cardToPlay) playCard(cardToPlay) else if (autoPlay === "worst" || bestCardLead(round)) { - const worstCard = getWorstCard(myCards, round.suit) + const worstCard = getWorstCard(myCards, round) if (worstCard) playCard(worstCard.name) } else if (autoPlay === "best") { - const bestCard = getBestCard(myCards, round.suit) + const bestCard = getBestCard(myCards, round) if (bestCard) playCard(bestCard.name) } } diff --git a/src/utils/GameUtils.spec.ts b/src/utils/GameUtils.spec.ts index 8f2bec0..46481fb 100644 --- a/src/utils/GameUtils.spec.ts +++ b/src/utils/GameUtils.spec.ts @@ -1,5 +1,23 @@ -import { compareCards } from "./GameUtils" +import { Suit } from "model/Suit" +import { compareCards, getBestCard, getWorstCard } from "./GameUtils" import { Card, CardName, CARDS } from "model/Cards" +import { Round, RoundStatus } from "model/Round" + +const ROUND: Round = { + suit: Suit.HEARTS, + currentHand: { + leadOut: CardName.TWO_CLUBS, + timestamp: "", + currentPlayerId: "", + playedCards: [], + }, + timestamp: "", + number: 0, + dealerId: "", + status: RoundStatus.PLAYING, + dealerSeeingCall: false, + completedHands: [], +} const HAND1: Card[] = [ CARDS[CardName.TWO_HEARTS], @@ -17,6 +35,12 @@ const HAND2: Card[] = [ CARDS[CardName.THREE_DIAMONDS], ] +const HAND3: Card[] = [ + CARDS[CardName.KING_SPADES], + CARDS[CardName.THREE_DIAMONDS], + CARDS[CardName.TWO_CLUBS], +] + describe("GameUtils", () => { describe("compareCards", () => { it("2 empty hands should return true", () => { @@ -35,4 +59,28 @@ describe("GameUtils", () => { expect(compareCards(HAND1, HAND1.reverse())).toBe(true) }) }) + + describe("getBestCard", () => { + it("empty hand", () => { + expect(getBestCard([], ROUND)).toBe(undefined) + }) + it("trump card", () => { + expect(getBestCard(HAND1, ROUND)).toBe(CARDS[CardName.FIVE_HEARTS]) + }) + it("follow cold card", () => { + expect(getBestCard(HAND3, ROUND)).toBe(CARDS[CardName.TWO_CLUBS]) + }) + }) + + describe("getWorstCard", () => { + it("empty hand", () => { + expect(getBestCard([], ROUND)).toBe(undefined) + }) + it("trump card", () => { + expect(getWorstCard(HAND1, ROUND)).toBe(CARDS[CardName.TWO_HEARTS]) + }) + it("follow cold card", () => { + expect(getWorstCard(HAND3, ROUND)).toBe(CARDS[CardName.TWO_CLUBS]) + }) + }) }) diff --git a/src/utils/GameUtils.ts b/src/utils/GameUtils.ts index c4f6463..ffa6910 100644 --- a/src/utils/GameUtils.ts +++ b/src/utils/GameUtils.ts @@ -169,38 +169,63 @@ export const bestCardLead = (round: Round) => { return round.currentHand.leadOut === trumpCards[0].name } -export const getWorstCard = (cards: SelectableCard[], suit: Suit) => { - const myTrumpCards = cards.filter( - card => card.suit === suit || card.suit === Suit.WILD, - ) +export const getWorstCard = (cards: Card[], round: Round) => { + if (cards.length === 0) return undefined - if (myTrumpCards.length > 0) { - // Sort ascending by value - myTrumpCards.sort((a, b) => a.value - b.value) - return myTrumpCards[0] - } else { - // Sort ascending by cold value - cards.sort((a, b) => a.coldValue - b.coldValue) + // Check if must follow suit + const leadOut = round.currentHand?.leadOut + let suitLead = leadOut ? CARDS[leadOut as CardName]?.suit : undefined - return cards[0] + if (suitLead === Suit.WILD) { + suitLead = round.suit } + + if (suitLead) { + const myCards = cards.filter(card => card.suit === suitLead) + if (myCards.length > 0) { + // Sort ascending by value + myCards.sort((a, b) => a.value - b.value) + return myCards[0] + } + } + + // Sort ascending by value + cards.sort((a, b) => a.value - b.value) + + return cards[0] } -export const getBestCard = (cards: SelectableCard[], suit: Suit) => { +export const getBestCard = (cards: Card[], round: Round) => { + if (cards.length === 0) return undefined + + // Check for trump cards const myTrumpCards = cards.filter( - card => card.suit === suit || card.suit === Suit.WILD, + card => card.suit === round.suit || card.suit === Suit.WILD, ) if (myTrumpCards.length > 0) { // Sort descending by value myTrumpCards.sort((a, b) => b.value - a.value) return myTrumpCards[0] - } else { - // Sort descending by cold value - cards.sort((a, b) => b.coldValue - a.coldValue) + } - return cards[0] + // Check if have any cold cards + const leadOut = round.currentHand?.leadOut + const suitLead = leadOut ? CARDS[leadOut as CardName]?.suit : undefined + + if (suitLead && suitLead !== Suit.WILD && suitLead !== round.suit) { + const myColdCards = cards.filter(card => card.suit === suitLead) + if (myColdCards.length > 0) { + // Sort descending by cold value + myColdCards.sort((a, b) => b.coldValue - a.coldValue) + return myColdCards[0] + } } + + // Sort descending by cold value + cards.sort((a, b) => b.coldValue - a.coldValue) + + return cards[0] } export const pickBestCards = ( From 869f952328a14dfd3644f550af74b28d8e15de18 Mon Sep 17 00:00:00 2001 From: Daithi Hearn Date: Fri, 29 Sep 2023 15:09:02 +0200 Subject: [PATCH 2/2] chore: adding unit test pipeline --- .github/workflows/unit-tests.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/unit-tests.yml diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..91d3295 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,25 @@ +name: CI + +on: + workflow_dispatch: + pull_request: + branches: ["main"] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: "16.19.x" + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Run tests + run: yarn test