diff --git a/src/caches/GameSlice.ts b/src/caches/GameSlice.ts
index 15d96ba..41fccbd 100644
--- a/src/caches/GameSlice.ts
+++ b/src/caches/GameSlice.ts
@@ -90,8 +90,10 @@ export const getIsDoublesGame = createSelector(
players => players.length === 6,
)
+export const getMaxCall = createSelector(getGame, game => game.maxCall || 0)
export const getIsMyGo = createSelector(getGame, game => game.isMyGo)
export const getIamGoer = createSelector(getGame, game => game.iamGoer)
+export const getIamDealer = createSelector(getGame, game => game.iamDealer)
export const getIHavePlayed = createSelector(getGame, game => {
const myPosition = game.players.findIndex(p => p.id === game.me?.id)
const currentPlayerPosition = game.players.findIndex(
diff --git a/src/components/Game/Actions/ActionsWrapper.tsx b/src/components/Game/Actions/ActionsWrapper.tsx
new file mode 100644
index 0000000..a97da1a
--- /dev/null
+++ b/src/components/Game/Actions/ActionsWrapper.tsx
@@ -0,0 +1,32 @@
+import { ButtonGroup, CardBody } from "reactstrap"
+import {
+ getIsRoundBuying,
+ getIsRoundCalled,
+ getIsRoundCalling,
+ getIsRoundPlaying,
+} from "../../../caches/GameSlice"
+import { useAppSelector } from "../../../caches/hooks"
+import Buying from "./Buying"
+import Calling from "./Calling"
+import PlayCard from "./PlayCard"
+import SelectSuit from "./SelectSuit"
+
+const ActionsWrapper = () => {
+ const isBuying = useAppSelector(getIsRoundBuying)
+ const isCalling = useAppSelector(getIsRoundCalling)
+ const isPlaying = useAppSelector(getIsRoundPlaying)
+ const isCalled = useAppSelector(getIsRoundCalled)
+
+ return (
+
+
+ {isCalling && }
+ {isBuying && }
+ {isCalled && }
+ {isPlaying && }
+
+
+ )
+}
+
+export default ActionsWrapper
diff --git a/src/components/Game/Buying.tsx b/src/components/Game/Actions/Buying.tsx
similarity index 58%
rename from src/components/Game/Buying.tsx
rename to src/components/Game/Actions/Buying.tsx
index 47b4cc8..0bb94d5 100644
--- a/src/components/Game/Buying.tsx
+++ b/src/components/Game/Actions/Buying.tsx
@@ -1,26 +1,32 @@
-import { Button, ButtonGroup, CardBody } from "reactstrap"
+import { Button } from "reactstrap"
import { useCallback, useEffect, useState } from "react"
-import GameService from "../../services/GameService"
-import { useAppDispatch, useAppSelector } from "../../caches/hooks"
+import GameService from "../../..//services/GameService"
+import { useAppDispatch, useAppSelector } from "../../..//caches/hooks"
import { useSnackbar } from "notistack"
import {
getMyCardsWithoutBlanks,
getSelectedCards,
selectAll,
-} from "../../caches/MyCardsSlice"
+} from "../../..//caches/MyCardsSlice"
import {
getGameId,
getIamGoer,
getIHavePlayed,
getIsMyGo,
getSuit,
-} from "../../caches/GameSlice"
-import { riskOfMistakeBuyingCards } from "../../utils/GameUtils"
+} from "../../..//caches/GameSlice"
+import { riskOfMistakeBuyingCards } from "../../../utils/GameUtils"
import ThrowCardsWarningModal from "./ThrowCardsWarningModal"
-import { SelectableCard } from "../../model/Cards"
-import parseError from "../../utils/ErrorUtils"
+import { SelectableCard } from "../../../model/Cards"
+import parseError from "../../../utils/ErrorUtils"
+
+const WaitingForRoundToStart = () => (
+
+)
const Buying = () => {
const dispatch = useAppDispatch()
@@ -69,32 +75,27 @@ const Buying = () => {
}
}, [gameId, suit, selectedCards, myCards, isMyGo, readyToBuy])
+ if (iHavePlayed) return
return (
-
-
-
- {!iHavePlayed ? (
-
- ) : null}
-
+ <>
+
-
-
-
+
+ >
)
}
diff --git a/src/components/Game/Actions/Calling.tsx b/src/components/Game/Actions/Calling.tsx
new file mode 100644
index 0000000..5cae98f
--- /dev/null
+++ b/src/components/Game/Actions/Calling.tsx
@@ -0,0 +1,125 @@
+import { Button } from "reactstrap"
+
+import GameService from "../../../services/GameService"
+import { useAppDispatch, useAppSelector } from "../../../caches/hooks"
+import {
+ getCards,
+ getGameId,
+ getGamePlayers,
+ getIamDealer,
+ getIsMyGo,
+ getMaxCall,
+ getRound,
+} from "../../../caches/GameSlice"
+import { useCallback, useMemo } from "react"
+import { useSnackbar } from "notistack"
+import parseError from "../../../utils/ErrorUtils"
+
+const Calling = () => {
+ const dispatch = useAppDispatch()
+ const { enqueueSnackbar } = useSnackbar()
+ const gameId = useAppSelector(getGameId)
+ const cards = useAppSelector(getCards)
+ const round = useAppSelector(getRound)
+ const players = useAppSelector(getGamePlayers)
+ const isMyGo = useAppSelector(getIsMyGo)
+ const iamDealer = useAppSelector(getIamDealer)
+ const maxCall = useAppSelector(getMaxCall)
+
+ const call = useCallback(
+ (callAmount: number) => {
+ if (gameId)
+ dispatch(GameService.call(gameId, callAmount)).catch(
+ (e: Error) =>
+ enqueueSnackbar(parseError(e), { variant: "error" }),
+ )
+ },
+ [gameId],
+ )
+
+ const buttonsEnabled = useMemo(
+ () => round && round.currentHand && cards.length > 0 && isMyGo,
+ [round, cards, isMyGo],
+ )
+
+ const canCall10 = useMemo(
+ () =>
+ players.length === 6 &&
+ ((iamDealer && maxCall <= 10) || maxCall < 10),
+ [players, iamDealer, maxCall],
+ )
+
+ const canCall15 = useMemo(
+ () => (iamDealer && maxCall <= 15) || maxCall < 15,
+ [iamDealer, maxCall],
+ )
+
+ const canCall20 = useMemo(
+ () => (iamDealer && maxCall <= 20) || maxCall < 20,
+ [iamDealer, maxCall],
+ )
+
+ const canCall25 = useMemo(
+ () => (iamDealer && maxCall <= 25) || maxCall < 25,
+ [iamDealer, maxCall],
+ )
+
+ const canCallJink = useMemo(() => players.length > 2, [players])
+
+ return (
+ <>
+
+ {canCall10 ? (
+
+ ) : null}
+ {canCall15 ? (
+
+ ) : null}
+ {canCall20 ? (
+
+ ) : null}
+ {canCall25 ? (
+
+ ) : null}
+
+ >
+ )
+}
+
+export default Calling
diff --git a/src/components/Game/Actions/PlayCard.tsx b/src/components/Game/Actions/PlayCard.tsx
new file mode 100644
index 0000000..300f481
--- /dev/null
+++ b/src/components/Game/Actions/PlayCard.tsx
@@ -0,0 +1,68 @@
+import { Button } from "reactstrap"
+
+import { useCallback, useMemo } from "react"
+
+import GameService from "../../../services/GameService"
+import { useAppDispatch, useAppSelector } from "../../../caches/hooks"
+import { useSnackbar } from "notistack"
+import {
+ getMyCardsWithoutBlanks,
+ getSelectedCards,
+} from "../../../caches/MyCardsSlice"
+import { getGameId, getIsMyGo, getRound } from "../../../caches/GameSlice"
+import { BLANK_CARD } from "../../../model/Cards"
+import parseError from "../../../utils/ErrorUtils"
+import { RoundStatus } from "../../../model/Round"
+
+const WaitingForYourGo = () => (
+
+)
+
+const PlayCard = () => {
+ const dispatch = useAppDispatch()
+ const round = useAppSelector(getRound)
+ const { enqueueSnackbar } = useSnackbar()
+ const gameId = useAppSelector(getGameId)
+ const myCards = useAppSelector(getMyCardsWithoutBlanks)
+ const isMyGo = useAppSelector(getIsMyGo)
+ const selectedCards = useAppSelector(getSelectedCards)
+
+ const playButtonEnabled = useMemo(
+ () =>
+ isMyGo &&
+ round &&
+ round.status === RoundStatus.PLAYING &&
+ round.completedHands.length +
+ myCards.filter(c => c.name !== BLANK_CARD.name).length ===
+ 5,
+
+ [isMyGo, round, myCards],
+ )
+
+ const playCard = useCallback(() => {
+ if (selectedCards.length !== 1) {
+ enqueueSnackbar("Please select exactly one card to play", {
+ variant: "warning",
+ })
+ } else {
+ dispatch(
+ GameService.playCard(gameId!, selectedCards[0].name),
+ ).catch(e => enqueueSnackbar(parseError(e), { variant: "error" }))
+ }
+ }, [gameId, selectedCards])
+
+ if (!playButtonEnabled) return
+ return (
+
+ )
+}
+
+export default PlayCard
diff --git a/src/components/Game/Actions/SelectSuit.tsx b/src/components/Game/Actions/SelectSuit.tsx
new file mode 100644
index 0000000..c7ee0be
--- /dev/null
+++ b/src/components/Game/Actions/SelectSuit.tsx
@@ -0,0 +1,156 @@
+import { Button } from "reactstrap"
+
+import { useCallback, useState } from "react"
+
+import GameService from "../../../services/GameService"
+import { useAppDispatch, useAppSelector } from "../../../caches/hooks"
+import { getGameId, getIamGoer } from "../../../caches/GameSlice"
+import { Suit } from "../../../model/Suit"
+import { useSnackbar } from "notistack"
+import {
+ getMyCardsWithoutBlanks,
+ getSelectedCards,
+} from "../../../caches/MyCardsSlice"
+import { removeAllFromHand } from "../../../utils/GameUtils"
+import ThrowCardsWarningModal from "./ThrowCardsWarningModal"
+import { SelectableCard } from "../../../model/Cards"
+import parseError from "../../../utils/ErrorUtils"
+
+const WaitingForSuit = () => (
+
+)
+
+const SelectSuit = () => {
+ const dispatch = useAppDispatch()
+ const { enqueueSnackbar } = useSnackbar()
+ const gameId = useAppSelector(getGameId)
+ const myCards = useAppSelector(getMyCardsWithoutBlanks)
+ const iamGoer = useAppSelector(getIamGoer)
+
+ const [selectedSuit, setSelectedSuit] = useState()
+ const [possibleIssue, setPossibleIssues] = useState(false)
+
+ const selectedCards = useAppSelector(getSelectedCards)
+
+ const selectFromDummy = useCallback(
+ (suit: Suit) => {
+ // Make sure a suit was selected
+ if (
+ suit !== Suit.HEARTS &&
+ suit !== Suit.DIAMONDS &&
+ suit !== Suit.CLUBS &&
+ suit !== Suit.SPADES
+ ) {
+ enqueueSnackbar("Please select a suit!")
+ return
+ }
+
+ // Check if there is a risk that they made a mistake when selecting the cards
+ if (riskOfMistakeBuyingCards(suit)) {
+ setSelectedSuit(suit)
+ setPossibleIssues(true)
+ } else {
+ dispatch(
+ GameService.chooseFromDummy(gameId!, selectedCards, suit),
+ ).catch((e: Error) =>
+ enqueueSnackbar(parseError(e), { variant: "error" }),
+ )
+ }
+ },
+ [gameId, selectedCards],
+ )
+
+ const selectFromDummyCallback = (
+ id: string,
+ sel: SelectableCard[],
+ suit?: Suit,
+ ) => {
+ if (!suit) throw Error("Must provide a suit")
+ dispatch(GameService.chooseFromDummy(id, sel, suit)).catch((e: Error) =>
+ enqueueSnackbar(parseError(e), { variant: "error" }),
+ )
+ }
+
+ const hideCancelSelectFromDummyDialog = useCallback(() => {
+ setSelectedSuit(undefined)
+ setPossibleIssues(false)
+ }, [])
+
+ const riskOfMistakeBuyingCards = useCallback(
+ (suit: Suit) => {
+ let deletingCards = removeAllFromHand(selectedCards, myCards)
+
+ for (const element of deletingCards) {
+ if (
+ element.name === "JOKER" ||
+ element.name === "ACE_HEARTS" ||
+ element.suit === suit
+ ) {
+ return true
+ }
+ }
+
+ return false
+ },
+ [myCards, selectedCards],
+ )
+
+ if (!iamGoer) return
+ return (
+ <>
+
+
+
+
+
+ {selectedSuit && (
+
+ )}
+ >
+ )
+}
+
+export default SelectSuit
diff --git a/src/components/Game/ThrowCardsWarningModal.tsx b/src/components/Game/Actions/ThrowCardsWarningModal.tsx
similarity index 88%
rename from src/components/Game/ThrowCardsWarningModal.tsx
rename to src/components/Game/Actions/ThrowCardsWarningModal.tsx
index da92040..790063a 100644
--- a/src/components/Game/ThrowCardsWarningModal.tsx
+++ b/src/components/Game/Actions/ThrowCardsWarningModal.tsx
@@ -1,4 +1,3 @@
-import { useSnackbar } from "notistack"
import React, { useCallback, useMemo } from "react"
import {
Modal,
@@ -11,16 +10,15 @@ import {
CardGroup,
Card as CardComponent,
} from "reactstrap"
-import { getGameId } from "../../caches/GameSlice"
-import { useAppDispatch, useAppSelector } from "../../caches/hooks"
+import { getGameId } from "../../../caches/GameSlice"
+import { useAppSelector } from "../../../caches/hooks"
import {
getMyCardsWithoutBlanks,
getSelectedCards,
-} from "../../caches/MyCardsSlice"
-import { Card, SelectableCard } from "../../model/Cards"
-import { Suit } from "../../model/Suit"
-import GameService from "../../services/GameService"
-import { removeAllFromHand } from "../../utils/GameUtils"
+} from "../../../caches/MyCardsSlice"
+import { SelectableCard } from "../../../model/Cards"
+import { Suit } from "../../../model/Suit"
+import { removeAllFromHand } from "../../../utils/GameUtils"
interface ModalOpts {
modalVisible: boolean
diff --git a/src/components/Game/Calling.tsx b/src/components/Game/Calling.tsx
deleted file mode 100644
index bec11f2..0000000
--- a/src/components/Game/Calling.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import { Button, ButtonGroup, CardBody } from "reactstrap"
-
-import GameService from "../../services/GameService"
-import { useAppDispatch, useAppSelector } from "../../caches/hooks"
-import { getGame, getGameId } from "../../caches/GameSlice"
-import { useCallback } from "react"
-import { useSnackbar } from "notistack"
-import parseError from "../../utils/ErrorUtils"
-
-const Calling = () => {
- const dispatch = useAppDispatch()
- const { enqueueSnackbar } = useSnackbar()
- const gameId = useAppSelector(getGameId)
- const game = useAppSelector(getGame)
-
- const buttonsEnabled =
- game.round &&
- game.round.currentHand &&
- game.cards.length > 0 &&
- game.isMyGo
-
- const call = useCallback(
- (callAmount: number) => {
- if (gameId)
- dispatch(GameService.call(gameId, callAmount)).catch(
- (e: Error) =>
- enqueueSnackbar(parseError(e), { variant: "error" }),
- )
- },
- [gameId],
- )
-
- if (!game) {
- return null
- }
- return (
-
-
-
-
- {game.players.length === 6 &&
- ((game.iamDealer && game.maxCall! <= 10) ||
- game.maxCall! < 10) ? (
-
- ) : null}
- {(game.iamDealer && game.maxCall! <= 15) ||
- game.maxCall! < 15 ? (
-
- ) : null}
- {(game.iamDealer && game.maxCall! <= 20) ||
- game.maxCall! < 20 ? (
-
- ) : null}
- {(game.iamDealer && game.maxCall! <= 25) ||
- game.maxCall! < 25 ? (
-
- ) : null}
-
-
-
-
- )
-}
-
-export default Calling
diff --git a/src/components/Game/GameWrapper.tsx b/src/components/Game/GameWrapper.tsx
index 1a99822..65925ce 100644
--- a/src/components/Game/GameWrapper.tsx
+++ b/src/components/Game/GameWrapper.tsx
@@ -2,22 +2,14 @@ import { Card, CardGroup } from "reactstrap"
import MyCards from "./MyCards"
import PlayersAndCards from "./PlayersAndCards"
-import Calling from "./Calling"
-import Buying from "./Buying"
-import SelectSuit from "./SelectSuit"
import WebsocketManager from "./WebsocketManager"
import { useAppSelector } from "../../caches/hooks"
-import {
- getIamSpectator,
- getIsRoundBuying,
- getIsRoundCalling,
-} from "../../caches/GameSlice"
+import { getIamSpectator } from "../../caches/GameSlice"
+import ActionsWrapper from "./Actions/ActionsWrapper"
const GameWrapper = () => {
const iamSpectator = useAppSelector(getIamSpectator)
- const isBuying = useAppSelector(getIsRoundBuying)
- const isCalling = useAppSelector(getIsRoundCalling)
return (
@@ -26,9 +18,7 @@ const GameWrapper = () => {
- {!iamSpectator && isCalling ? : null}
- {!iamSpectator && isBuying ? : null}
- {!iamSpectator ? : null}
+ {!iamSpectator ? : null}
{!iamSpectator ? : null}
diff --git a/src/components/Game/MyCards.tsx b/src/components/Game/MyCards.tsx
index 3da7a5b..364fe4c 100644
--- a/src/components/Game/MyCards.tsx
+++ b/src/components/Game/MyCards.tsx
@@ -1,5 +1,5 @@
-import React, { useCallback, useMemo, useState } from "react"
-import { Button, ButtonGroup, CardImg, CardBody } from "reactstrap"
+import React, { useCallback, useMemo } from "react"
+import { CardImg, CardBody } from "reactstrap"
import {
DragDropContext,
Draggable,
@@ -8,21 +8,12 @@ import {
} from "react-beautiful-dnd"
import { BLANK_CARD, SelectableCard } from "../../model/Cards"
-import GameService from "../../services/GameService"
import { RoundStatus } from "../../model/Round"
-import {
- getGameId,
- getIamGoer,
- getIsMyGo,
- getIsRoundCalled,
- getRound,
-} from "../../caches/GameSlice"
+import { getIamGoer, getIsRoundCalled, getRound } from "../../caches/GameSlice"
import { useAppDispatch, useAppSelector } from "../../caches/hooks"
-import { useSnackbar } from "notistack"
import {
clearSelectedCards,
getMyCards,
- getSelectedCards,
replaceMyCards,
toggleSelect,
toggleUniqueSelect,
@@ -32,7 +23,6 @@ import {
toggleAutoPlay,
clearAutoPlay,
} from "../../caches/AutoPlaySlice"
-import parseError from "../../utils/ErrorUtils"
const EMPTY_HAND = [
{ ...BLANK_CARD, selected: false },
@@ -44,29 +34,11 @@ const EMPTY_HAND = [
const MyCards: React.FC = () => {
const dispatch = useAppDispatch()
- const gameId = useAppSelector(getGameId)
const round = useAppSelector(getRound)
const isRoundCalled = useAppSelector(getIsRoundCalled)
const myCards = useAppSelector(getMyCards)
const autoPlayCard = useAppSelector(getAutoPlayCard)
const iamGoer = useAppSelector(getIamGoer)
- const isMyGo = useAppSelector(getIsMyGo)
-
- const { enqueueSnackbar } = useSnackbar()
-
- const selectedCards = useAppSelector(getSelectedCards)
-
- const playButtonEnabled = useMemo(
- () =>
- isMyGo &&
- round &&
- round.status === RoundStatus.PLAYING &&
- round.completedHands.length +
- myCards.filter(c => c.name !== BLANK_CARD.name).length ===
- 5,
-
- [isMyGo, round, myCards],
- )
const cardsSelectable = useMemo(
() =>
@@ -161,18 +133,6 @@ const MyCards: React.FC = () => {
[cardsSelectable, autoPlayCard],
)
- const playCard = useCallback(() => {
- if (selectedCards.length !== 1) {
- enqueueSnackbar("Please select exactly one card to play", {
- variant: "warning",
- })
- } else {
- dispatch(
- GameService.playCard(gameId!, selectedCards[0].name),
- ).catch(e => enqueueSnackbar(parseError(e), { variant: "error" }))
- }
- }, [gameId, selectedCards])
-
return (
@@ -283,21 +243,6 @@ const MyCards: React.FC = () => {
-
- {round?.status === RoundStatus.PLAYING ? (
-
-
-
-
-
- ) : null}
)
}
diff --git a/src/components/Game/SelectSuit.tsx b/src/components/Game/SelectSuit.tsx
deleted file mode 100644
index 9c19958..0000000
--- a/src/components/Game/SelectSuit.tsx
+++ /dev/null
@@ -1,176 +0,0 @@
-import { Button, ButtonGroup, CardBody } from "reactstrap"
-
-import { useCallback, useState } from "react"
-
-import GameService from "../../services/GameService"
-import { useAppDispatch, useAppSelector } from "../../caches/hooks"
-import { getGameId, getIamGoer, getIsRoundCalled } from "../../caches/GameSlice"
-import { Suit } from "../../model/Suit"
-import { useSnackbar } from "notistack"
-import {
- getMyCardsWithoutBlanks,
- getSelectedCards,
-} from "../../caches/MyCardsSlice"
-import { removeAllFromHand } from "../../utils/GameUtils"
-import ThrowCardsWarningModal from "./ThrowCardsWarningModal"
-import { SelectableCard } from "../../model/Cards"
-import parseError from "../../utils/ErrorUtils"
-
-const SelectSuit = () => {
- const dispatch = useAppDispatch()
- const { enqueueSnackbar } = useSnackbar()
- const gameId = useAppSelector(getGameId)
- const myCards = useAppSelector(getMyCardsWithoutBlanks)
- const iamGoer = useAppSelector(getIamGoer)
- const isCalled = useAppSelector(getIsRoundCalled)
-
- const [selectedSuit, setSelectedSuit] = useState()
- const [possibleIssue, setPossibleIssues] = useState(false)
-
- const selectedCards = useAppSelector(getSelectedCards)
-
- const selectFromDummy = useCallback(
- (suit: Suit) => {
- // Make sure a suit was selected
- if (
- suit !== Suit.HEARTS &&
- suit !== Suit.DIAMONDS &&
- suit !== Suit.CLUBS &&
- suit !== Suit.SPADES
- ) {
- enqueueSnackbar("Please select a suit!")
- return
- }
-
- // Check if there is a risk that they made a mistake when selecting the cards
- if (riskOfMistakeBuyingCards(suit)) {
- setSelectedSuit(suit)
- setPossibleIssues(true)
- } else {
- dispatch(
- GameService.chooseFromDummy(gameId!, selectedCards, suit),
- ).catch((e: Error) =>
- enqueueSnackbar(parseError(e), { variant: "error" }),
- )
- }
- },
- [gameId, selectedCards],
- )
-
- const selectFromDummyCallback = (
- id: string,
- sel: SelectableCard[],
- suit?: Suit,
- ) => {
- if (!suit) throw Error("Must provide a suit")
- dispatch(GameService.chooseFromDummy(id, sel, suit)).catch((e: Error) =>
- enqueueSnackbar(parseError(e), { variant: "error" }),
- )
- }
-
- const hideCancelSelectFromDummyDialog = useCallback(() => {
- setSelectedSuit(undefined)
- setPossibleIssues(false)
- }, [])
-
- const riskOfMistakeBuyingCards = useCallback(
- (suit: Suit) => {
- let deletingCards = removeAllFromHand(selectedCards, myCards)
-
- for (const element of deletingCards) {
- if (
- element.name === "JOKER" ||
- element.name === "ACE_HEARTS" ||
- element.suit === suit
- ) {
- return true
- }
- }
-
- return false
- },
- [myCards, selectedCards],
- )
-
- return (
-
- {isCalled ? (
-
- {iamGoer ? (
-
-
-
-
-
-
-
-
- {selectedSuit && (
-
- )}
-
- ) : (
-
-
-
- )}
-
- ) : null}
-
- )
-}
-
-export default SelectSuit
diff --git a/tsconfig.json b/tsconfig.json
index e49c3a8..58ff4dd 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -14,7 +14,8 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
- "jsx": "react-jsx"
+ "jsx": "react-jsx",
+ "baseUrl": "src"
},
"include": ["src"],
"exclude": [