Skip to content

Commit

Permalink
Merge pull request #205 from daithihearn/autoplay-2
Browse files Browse the repository at this point in the history
feat: auto play
  • Loading branch information
daithihearn authored Feb 1, 2024
2 parents d1abde5 + 3483dda commit f06de75
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 24 deletions.
56 changes: 34 additions & 22 deletions src/components/Game/Actions/PlayCard.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { useCallback, useMemo, useState } from "react"
import { useCallback, useEffect, useMemo, useState } from "react"

import { useAppSelector } from "caches/hooks"
import {
getCardsWithoutBlanks,
getGameId,
getIsMyGo,
getRound,
getSelectedCards,
} from "caches/GameSlice"
import { getGame, getSelectedCards } from "caches/GameSlice"
import { RoundStatus } from "model/Round"
import {
Button,
Expand All @@ -21,16 +15,18 @@ import {
} from "@mui/material"
import { CardName } from "model/Cards"
import { useGameActions } from "components/Hooks/useGameActions"
import { bestCardLead, getBestCard, getWorstCard } from "utils/GameUtils"

type AutoPlayState = "off" | "best" | "worst"

const PlayCard = () => {
const theme = useTheme()
const { playCard } = useGameActions()
const round = useAppSelector(getRound)
const gameId = useAppSelector(getGameId)
const myCards = useAppSelector(getCardsWithoutBlanks)
const isMyGo = useAppSelector(getIsMyGo)
const game = useAppSelector(getGame)
const cardsWithoutEmpty = useMemo(
() => game.cardsFull.filter(c => c.name !== CardName.EMPTY),
[game],
)

const [autoPlay, setAutoPlay] = useState<AutoPlayState>("off")
const selectedCards = useAppSelector(getSelectedCards)
Expand Down Expand Up @@ -72,14 +68,11 @@ const PlayCard = () => {

const playButtonEnabled = useMemo(
() =>
isMyGo &&
round &&
round.status === RoundStatus.PLAYING &&
round.completedHands.length +
myCards.filter(c => c.name !== CardName.EMPTY).length ===
5,
game.isMyGo &&
game.round?.status === RoundStatus.PLAYING &&
game.round.completedHands.length + cardsWithoutEmpty.length === 5,

[isMyGo, round, myCards],
[game, cardsWithoutEmpty],
)

const PlayCardButton = () => (
Expand All @@ -94,9 +87,28 @@ const PlayCard = () => {
)

const selectCardToPlay = useCallback(() => {
if (selectedCards.length === 1 && gameId)
playCard({ gameId, card: selectedCards[0].name })
}, [gameId, selectedCards])
if (selectedCards.length === 1 && game.id)
playCard({ gameId: game.id, card: selectedCards[0].name })
}, [game.id, selectedCards])

// Auto play logic
// 1. If only one card in hand, play it
// 2. If best card lead or lead from bottom enabled, play worst card
// 3. If lead from the top enabled, play best card
useEffect(() => {
if (game.id && game.round?.suit && game.isMyGo) {
if (cardsWithoutEmpty.length === 1) {
playCard({ gameId: game.id, card: cardsWithoutEmpty[0].name })
} else if (autoPlay === "worst" || bestCardLead(game.round)) {
const worstCard = getWorstCard(cardsWithoutEmpty, game.round)
if (worstCard)
playCard({ gameId: game.id, card: worstCard.name })
} else if (autoPlay === "best") {
const bestCard = getBestCard(cardsWithoutEmpty, game.round)
if (bestCard) playCard({ gameId: game.id, card: bestCard.name })
}
}
}, [playCard, autoPlay, game, cardsWithoutEmpty])

return (
<ButtonGroup disableElevation variant="contained" size="large">
Expand Down
8 changes: 6 additions & 2 deletions src/components/Game/PlayersAndCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Player, PlayerProfile } from "model/Player"
import PlayerCard from "./PlayerCard"
import { useProfiles } from "components/Hooks/useProfiles"
import { useAppSelector } from "caches/hooks"
import { getGamePlayers } from "caches/GameSlice"
import { getGameId, getGamePlayers } from "caches/GameSlice"

const compareSeat = (a: Player, b: Player) => {
if (a.seatNumber > b.seatNumber) return 1
Expand All @@ -18,6 +18,7 @@ interface PP {
}

const PlayersAndCards = () => {
const gameId = useAppSelector(getGameId)
const players = useAppSelector(getGamePlayers)
const { allProfiles } = useProfiles()

Expand Down Expand Up @@ -60,7 +61,10 @@ const PlayersAndCards = () => {
<CardContent className="card-root">
<Grid container justifyContent="space-between">
{playerProfiles.map((pp, index) => (
<Grid item key={`playerCard_${pp.player.id}`} xs={xs}>
<Grid
item
key={`playerCard_${gameId}_${pp.player.id}`}
xs={xs}>
<PlayerCard
player={pp.player}
profile={pp.profile}
Expand Down
5 changes: 5 additions & 0 deletions src/utils/GameUtils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,11 @@ describe("GameUtils", () => {
it("only one card left", () => {
expect(getBestCard([CARDS.JOKER], ROUND)).toStrictEqual(CARDS.JOKER)
})
it("Hand with EMPTY cards", () => {
expect(getBestCard([EMPTY, ...HAND3], ROUND)).toStrictEqual(
CARDS.TWO_CLUBS,
)
})
})

describe("canRenege", () => {
Expand Down
6 changes: 6 additions & 0 deletions src/utils/GameUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ export const canRenege = (myCard: Card, cardLead: Card, suit: Suit) => {
}

export const getWorstCard = (cards: Card[], round: Round) => {
// Clear empty cards
cards = cards.filter(c => c.name !== CardName.EMPTY)

if (cards.length === 0) throw new Error("No cards to choose from")
if (cards.length === 1) return cards[0]

Expand Down Expand Up @@ -252,6 +255,9 @@ export const getWorstCard = (cards: Card[], round: Round) => {
}

export const getBestCard = (cards: Card[], round: Round) => {
// Clear empty cards
cards = cards.filter(c => c.name !== CardName.EMPTY)

if (cards.length === 0) throw new Error("No cards to choose from")
if (cards.length === 1) return cards[0]

Expand Down

0 comments on commit f06de75

Please sign in to comment.