diff --git a/app/[gameID]/page.tsx b/app/[gameID]/page.tsx index 4710db2..10daa07 100644 --- a/app/[gameID]/page.tsx +++ b/app/[gameID]/page.tsx @@ -10,16 +10,18 @@ import CopyButton from '@/components/CopyButton'; import Notes from '@/components/Notes'; import NotFound from '@/components/NotFound'; import Settings from '@/components/Settings'; +import CardSelect from '@/components/CardSelect'; import { cardMap, layout } from '@/constants/tarokka'; -import type { GameUpdate, ClientUpdate } from '@/types'; +import type { Deck, GameUpdate } from '@/types'; export default function GamePage() { const { gameID: gameIDParam } = useParams(); const [gameID, setGameID] = useState(''); const [noGame, setNoGame] = useState(false); + const [selectCard, setSelectCard] = useState(-1); const [gameData, setGameData] = useState({ dmID: '', spectatorID: '', @@ -35,6 +37,7 @@ export default function GamePage() { const { dmID, cards, settings } = gameData; const isDM = !!dmID; + const selectDeck: Deck | null = selectCard >= 0 ? cards[selectCard].deck : null; useEffect(() => { if (gameIDParam) { @@ -70,12 +73,27 @@ export default function GamePage() { }, [gameID]); const flipCard = (cardIndex: number) => { - const flip: ClientUpdate = { + socket.emit('flip-card', { gameID, cardIndex, - }; + }); + }; - socket.emit('flip-card', flip); + const redraw = (cardIndex: number) => { + socket.emit('redraw', { + gameID, + cardIndex, + }); + }; + + const select = (cardIndex: number, cardID: string) => { + setSelectCard(-1); + + socket.emit('select', { + gameID, + cardIndex, + cardID, + }); }; const handleSettings = (gameData: GameUpdate) => { @@ -114,12 +132,21 @@ export default function GamePage() { position={layout[cardMap[index]]} settings={settings} flipAction={() => flipCard(cardMap[index])} + redrawAction={() => redraw(cardMap[index])} + selectAction={() => setSelectCard(cardMap[index])} /> )} ))} flipped)} /> + setSelectCard(-1)} + selectAction={(cardID) => select(selectCard, cardID)} + /> ) : null; } diff --git a/app/globals.css b/app/globals.css index 369c76c..27b91b9 100644 --- a/app/globals.css +++ b/app/globals.css @@ -40,3 +40,21 @@ body { .rotate-y-180 { transform: rotateY(180deg); } + +.see-through { + mask-image: radial-gradient(circle at center, rgba(0, 0, 0, 0) 25%, rgba(0, 0, 0, 1) 75%); + mask-size: cover; + mask-repeat: no-repeat; + -webkit-mask-image: radial-gradient(circle at center, rgba(0, 0, 0, 0) 25%, rgba(0, 0, 0, 1) 75%); + -webkit-mask-size: cover; + -webkit-mask-repeat: no-repeat; +} + +.scrollbar-hide { + scrollbar-width: none; + -ms-overflow-style: none; +} + +.scrollbar-hide::-webkit-scrollbar { + display: none; +} diff --git a/components/Card.tsx b/components/Card.tsx index c72c868..e6978d0 100644 --- a/components/Card.tsx +++ b/components/Card.tsx @@ -1,6 +1,8 @@ 'use client'; +import { useState } from 'react'; import ToolTip from '@/components/ToolTip'; +import StackTheDeck from '@/components/StackTheDeck'; import tarokkaCards from '@/constants/tarokkaCards'; import getCardInfo from '@/tools/getCardInfo'; import getURL from '@/tools/getURL'; @@ -15,9 +17,21 @@ type CardProps = { position: Layout; settings: Settings; flipAction: () => void; + redrawAction: () => void; + selectAction: () => void; }; -export default function Card({ dm, card, position, settings, flipAction }: CardProps) { +export default function Card({ + dm, + card, + position, + settings, + flipAction, + redrawAction, + selectAction, +}: CardProps) { + const [tooltip, setTooltip] = useState(null); + const { aria, flipped } = card; const handleClick = () => { @@ -42,22 +56,39 @@ export default function Card({ dm, card, position, settings, flipAction }: CardP }; return ( - +
-
+
+ {dm && ( + <> + {aria} + + + )} Card Back + {dm && !flipped && ( + selectAction()} + onHover={setTooltip} + /> + )}
-
+
{aria} void; + selectAction: (cardID: string) => void; + hand: TarokkaGameCard[]; + settings: Settings; + show: Deck | null; + className?: string; +}; + +export default function CardSelect({ + closeAction, + selectAction, + hand, + settings, + show, + className = '', +}: CardSelectProps) { + const handIDs = hand.map(({ id }) => id); + + const handleClose = (event: React.MouseEvent) => { + if (event.target === event.currentTarget) { + closeAction(); + } + }; + + if (!show) return null; + + const cards = show === 'high' ? tarokkaDeck.getHigh() : tarokkaDeck.getLow(); + + return ( +
+ +
+ {cards + .filter(({ id }) => !handIDs.includes(id)) + .map((card) => ( +
selectAction(card.id)} + > + {card.aria} +
+ ))} +
+
+ ); +} diff --git a/components/Scrim.tsx b/components/Scrim.tsx index 5a92b52..fc8127d 100644 --- a/components/Scrim.tsx +++ b/components/Scrim.tsx @@ -13,6 +13,7 @@ export default function Scrim({ children, clickAction, show = true, className = clickAction(event); } }; + if (!show) return null; return ( diff --git a/components/StackTheDeck.tsx b/components/StackTheDeck.tsx new file mode 100644 index 0000000..35a7773 --- /dev/null +++ b/components/StackTheDeck.tsx @@ -0,0 +1,48 @@ +import { GalleryHorizontalEnd, RefreshCw } from 'lucide-react'; + +interface StackTheDeckProps { + onRedraw: () => void; + onSelect: () => void; + onHover: (state: React.ReactNode) => void; + className?: string; +} + +export default function StackTheDeck({ + onRedraw, + onSelect, + onHover, + className = '', +}: StackTheDeckProps) { + const curryHandleClick = (action: () => void) => (e: React.MouseEvent) => { + e.stopPropagation(); + action(); + }; + + return ( +
+ + + +
+ ); +} diff --git a/components/ToolTip.tsx b/components/ToolTip.tsx index 7facd69..d2cf262 100644 --- a/components/ToolTip.tsx +++ b/components/ToolTip.tsx @@ -15,8 +15,8 @@ type TooltipProps = { export default function Tooltip({ children, content, - delay = 500, - mobileDelay = 500, + delay = 250, + mobileDelay = 250, offsetX = 20, offsetY = 20, edgeBuffer = 10, diff --git a/constants/tarokkaCards.ts b/constants/tarokkaCards.ts index eb48d5f..4383002 100644 --- a/constants/tarokkaCards.ts +++ b/constants/tarokkaCards.ts @@ -5,6 +5,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'back', name: 'Card Back', card: 'Back of card', + deck: 'back', suit: null, aria: 'Back of card', description: 'Back of card', @@ -15,6 +16,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'swashbuckler', name: 'Swashbuckler', card: 'One of Coins', + deck: 'common', suit: 'Coins', aria: 'Coins 01 Swashbuckler', description: 'Those who like money yet give it up freely; likable rogues and rapscallions', @@ -31,6 +33,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'philanthropist', name: 'Philanthropist', card: 'Two of Coins', + deck: 'common', suit: 'Coins', aria: 'Coins 02 Philanthropist', description: @@ -48,6 +51,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'trader', name: 'Trader', card: 'Three of Coins', + deck: 'common', suit: 'Coins', aria: 'Coins 03 Trader', description: 'Commerce; smuggling and black markets; fair and equitable trades', @@ -64,6 +68,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'merchant', name: 'Merchant', card: 'Four of Coins', + deck: 'common', suit: 'Coins', aria: 'Coins 04 Merchant', description: @@ -80,6 +85,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'guild-member', name: 'Guild Member', card: 'Five of Coins', + deck: 'common', suit: 'Coins', aria: 'Coins 05 Guild Member', description: "Like-minded individuals joined together in a common goal; pride in one's work", @@ -95,6 +101,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'beggar', name: 'Beggar', card: 'Six of Coins', + deck: 'common', suit: 'Coins', aria: 'Coins 06 Beggar', description: 'Sudden change in economic status or fortune', @@ -111,6 +118,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'thief', name: 'Thief', card: 'Seven of Coins', + deck: 'common', suit: 'Coins', aria: 'Coins 07 Thief', description: @@ -128,6 +136,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'tax-collector', name: 'Tax Collector', card: 'Eight of Coins', + deck: 'common', suit: 'Coins', aria: 'Coins 08 Tax Collector', description: 'Corruption; honesty in an otherwise corrupt government or organization', @@ -145,6 +154,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'miser', name: 'Miser', card: 'Nine of Coins', + deck: 'common', suit: 'Coins', aria: 'Coins 09 Miser', description: @@ -161,6 +171,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'rogue', name: 'Rogue', card: 'Master of Coins', + deck: 'common', suit: 'Coins', aria: 'Coins 10 Rogue', description: @@ -177,6 +188,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'monk', name: 'Monk', card: 'One of Glyphs', + deck: 'common', suit: 'Glyphs', aria: 'Glyphs 01 Monk', description: @@ -194,6 +206,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'missionary', name: 'Missionary', card: 'Two of Glyphs', + deck: 'common', suit: 'Glyphs', aria: 'Glyphs 02 Missionary', description: @@ -212,6 +225,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'healer', name: 'Healer', card: 'Three of Glyphs', + deck: 'common', suit: 'Glyphs', aria: 'Glyphs 03 Healer', description: @@ -229,6 +243,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'shepherd', name: 'Shepherd', card: 'Four of Glyphs', + deck: 'common', suit: 'Glyphs', aria: 'Glyphs 04 Shepherd', description: @@ -246,6 +261,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'druid', name: 'Druid', card: 'Five of Glyphs', + deck: 'common', suit: 'Glyphs', aria: 'Glyphs 05 Druid', description: @@ -264,6 +280,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'anarchist', name: 'Anarchist', card: 'Six of Glyphs', + deck: 'common', suit: 'Glyphs', aria: 'Glyphs 06 Anarchist', description: 'A fundamental change brought on by one whose beliefs are being put to the test', @@ -280,6 +297,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'charlatan', name: 'Charlatan', card: 'Seven of Glyphs', + deck: 'common', suit: 'Glyphs', aria: 'Glyphs 07 Charlatan', description: 'Liars; those who profess to believe one thing but actually believe another', @@ -295,6 +313,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'bishop', name: 'Bishop', card: 'Eight of Glyphs', + deck: 'common', suit: 'Glyphs', aria: 'Glyphs 08 Bishop', description: 'Strict adherence to a code or a belief; those who plot, plan, and scheme', @@ -311,6 +330,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'traitor', name: 'Traitor', card: 'Nine of Glyphs', + deck: 'common', suit: 'Glyphs', aria: 'Glyphs 09 Traitor', description: 'Betrayal by someone close and trusted; a weakening or loss of faith', @@ -328,6 +348,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'priest', name: 'Priest', card: 'Master of Glyphs', + deck: 'common', suit: 'Glyphs', aria: 'Glyphs 10 Priest', description: 'Enlightenment; those who follow a deity, a system of values, or a higher purpose', @@ -344,6 +365,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'transmuter', name: 'Transmuter', card: 'One of Stars', + deck: 'common', suit: 'Stars', aria: 'Stars 01 Transmuter', description: @@ -360,6 +382,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'diviner', name: 'Diviner', card: 'Two of Stars', + deck: 'common', suit: 'Stars', aria: 'Stars 02 Diviner', description: @@ -377,6 +400,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'enchanter', name: 'Enchanter', card: 'Three of Stars', + deck: 'common', suit: 'Stars', aria: 'Stars 03 Enchanter', description: 'Inner turmoil that comes from confusion, fear of failure, or false information', @@ -394,6 +418,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'abjurer', name: 'Abjurer', card: 'Four of Stars', + deck: 'common', suit: 'Stars', aria: 'Stars 04 Abjurer', description: @@ -411,6 +436,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'elementalist', name: 'Elementalist', card: 'Five of Stars', + deck: 'common', suit: 'Stars', aria: 'Stars 05 Elementalist', description: @@ -429,6 +455,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'evoker', name: 'Evoker', card: 'Six of Stars', + deck: 'common', suit: 'Stars', aria: 'Stars 06 Evoker', description: @@ -446,6 +473,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'illusionist', name: 'Illusionist', card: 'Seven of Stars', + deck: 'common', suit: 'Stars', aria: 'Stars 07 Illusionist', description: @@ -463,6 +491,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'necromancer', name: 'Necromancer', card: 'Eight of Stars', + deck: 'common', suit: 'Stars', aria: 'Stars 08 Necromancer', description: 'Unnatural events and unhealthy obsessions; those who follow a destructive path', @@ -478,6 +507,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'conjurer', name: 'Conjurer', card: 'Nine of Stars', + deck: 'common', suit: 'Stars', aria: 'Stars 09 Conjurer', description: @@ -495,6 +525,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'wizard', name: 'Wizard', card: 'Master of Stars', + deck: 'common', suit: 'Stars', aria: 'Stars 10 Wizard', description: @@ -512,6 +543,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'avenger', name: 'Avenger', card: 'One of Swords', + deck: 'common', suit: 'Swords', aria: 'Swords 01 Avenger', description: @@ -529,6 +561,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'paladin', name: 'Paladin', card: 'Two of Swords', + deck: 'common', suit: 'Swords', aria: 'Swords 02 Paladin', description: 'Just and noble warriors; those who live by a code of honor and integrity', @@ -545,6 +578,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'soldier', name: 'Soldier', card: 'Three of Swords', + deck: 'common', suit: 'Swords', aria: 'Swords 03 Soldier', description: 'War and sacrifice; the stamina to endure great hardship', @@ -561,6 +595,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'mercenary', name: 'Mercenary', card: 'Four of Swords', + deck: 'common', suit: 'Swords', aria: 'Swords 04 Mercenary', description: 'Inner strength and fortitude; those who fight for power or wealth', @@ -576,6 +611,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'myrmidon', name: 'Myrmidon', card: 'Five of Swords', + deck: 'common', suit: 'Swords', aria: 'Swords 05 Myrmidon', description: @@ -594,6 +630,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'berserker', name: 'Berserker', card: 'Six of Swords', + deck: 'common', suit: 'Swords', aria: 'Swords 06 Berserker', description: 'The brutal and barbaric side of warfare; bloodlust; those with a bestial nature', @@ -611,6 +648,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'hooded-one', name: 'Hooded One', card: 'Seven of Swords', + deck: 'common', suit: 'Swords', aria: 'Swords 07 Hooded One', description: 'Bigotry, intolerance, and xenophobia; a mysterious presence or newcomer', @@ -628,6 +666,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'dictator', name: 'Dictator', card: 'Eight of Swords', + deck: 'common', suit: 'Swords', aria: 'Swords 08 Dictator', description: @@ -644,6 +683,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'torturer', name: 'Torturer', card: 'Nine of Swords', + deck: 'common', suit: 'Swords', aria: 'Swords 09 Torturer', description: @@ -662,6 +702,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'warrior', name: 'Warrior', card: 'Master of Swords', + deck: 'common', suit: 'Swords', aria: 'Swords 10 Warrior', description: @@ -679,6 +720,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'artifact', name: 'Artifact', card: 'The Artifact', + deck: 'high', suit: 'High Deck', aria: 'High Deck Artifact', description: @@ -703,6 +745,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'beast', name: 'Beast', card: 'The Beast', + deck: 'high', suit: 'High Deck', aria: 'High Deck Beast', description: @@ -728,6 +771,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'broken-one', name: 'Broken One', card: 'The Broken One', + deck: 'high', suit: 'High Deck', aria: 'High Deck Broken One', description: @@ -759,6 +803,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'darklord', name: 'Darklord', card: 'The Darklord', + deck: 'high', suit: 'High Deck', aria: 'High Deck Darklord', description: @@ -782,6 +827,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'donjon', name: 'Donjon', card: 'The Donjon', + deck: 'high', suit: 'High Deck', aria: 'High Deck Donjon', description: @@ -814,6 +860,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'executioner', name: 'Executioner', card: 'The Executioner', + deck: 'high', suit: 'High Deck', aria: 'High Deck Executioner', description: @@ -840,6 +887,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'ghost', name: 'Ghost', card: 'The Ghost', + deck: 'high', suit: 'High Deck', aria: 'High Deck Ghost', description: @@ -873,6 +921,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'horseman', name: 'Horseman', card: 'The Horseman', + deck: 'high', suit: 'High Deck', aria: 'High Deck Horseman', description: @@ -905,6 +954,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'innocent', name: 'Innocent', card: 'The Innocent', + deck: 'high', suit: 'High Deck', aria: 'High Deck Innocent', description: @@ -937,6 +987,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'marionette', name: 'Marionette', card: 'The Marionette', + deck: 'high', suit: 'High Deck', aria: 'High Deck Marionette', description: @@ -968,6 +1019,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'mists', name: 'Mists', card: 'The Mists', + deck: 'high', suit: 'High Deck', aria: 'High Deck Mists', description: @@ -994,6 +1046,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'raven', name: 'Raven', card: 'The Raven', + deck: 'high', suit: 'High Deck', aria: 'High Deck Raven', description: @@ -1020,6 +1073,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'seer', name: 'Seer', card: 'The Seer', + deck: 'high', suit: 'High Deck', aria: 'High Deck Seer', description: @@ -1046,6 +1100,7 @@ const tarokkaCards: TarokkaCard[] = [ id: 'tempter', name: 'Tempter', card: 'The Tempter', + deck: 'high', suit: 'High Deck', aria: 'High Deck Tempter', description: diff --git a/lib/GameStore.ts b/lib/GameStore.ts index 635020e..de08e2a 100644 --- a/lib/GameStore.ts +++ b/lib/GameStore.ts @@ -130,6 +130,33 @@ export default class GameStore { return this.gameUpdate(game); } + redraw(gameID: string, cardIndex: number): GameUpdate { + const game = this.getGame(gameID); + const card = game.cards[cardIndex]; + + if (!card) throw new Error(`Card ${cardIndex} not found`); + + game.cards[cardIndex] = + card.suit === 'High Deck' ? deck.drawHigh(game.cards) : deck.drawLow(game.cards); + game.lastUpdated = Date.now(); + + return this.gameUpdate(game); + } + + select(gameID: string, cardIndex: number, cardID: string): GameUpdate { + const game = this.getGame(gameID); + const card = game.cards[cardIndex]; + const replacement = deck.select(cardID); + + if (!card) throw new Error(`Card ${cardIndex} not found`); + if (!replacement) throw new Error(`Card ${cardID} not found`); + + game.cards[cardIndex] = replacement; + game.lastUpdated = Date.now(); + + return this.gameUpdate(game); + } + updateSettings(gameID: string, settings: Settings) { const game = this.getGame(gameID); diff --git a/lib/TarokkaDeck.ts b/lib/TarokkaDeck.ts index e45851e..7820a22 100644 --- a/lib/TarokkaDeck.ts +++ b/lib/TarokkaDeck.ts @@ -8,8 +8,8 @@ export default class TarokkaDeck { private backs: TarokkaCard[] = []; constructor() { - this.highDeck = cards.filter((card) => !card.back && card.suit === 'High Deck'); - this.commonDeck = cards.filter((card) => !card.back && card.suit !== 'High Deck'); + this.highDeck = cards.filter((card) => card.deck === 'high'); + this.commonDeck = cards.filter((card) => card.deck === 'common'); this.backs = cards.filter((card) => card.back); } @@ -19,6 +19,49 @@ export default class TarokkaDeck { ); } + getLow(): TarokkaGameCard[] { + return this.commonDeck.map((card) => ({ ...card, flipped: false })); + } + + getHigh(): TarokkaGameCard[] { + return this.highDeck.map((card) => ({ ...card, flipped: false })); + } + + drawLow(exclude: TarokkaGameCard[] = []): TarokkaGameCard { + const excludeIDs = exclude.map(({ id }) => id); + + return { + ...getRandomItems( + this.commonDeck.filter(({ id }) => !excludeIDs.includes(id)), + 1, + )[0], + flipped: false, + }; + } + + drawHigh(exclude: TarokkaGameCard[] = []): TarokkaGameCard { + const excludeIDs = exclude.map(({ id }) => id); + + return { + ...getRandomItems( + this.highDeck.filter(({ id }) => !excludeIDs.includes(id)), + 1, + )[0], + flipped: false, + }; + } + + select(id: string): TarokkaGameCard | null { + const card = cards.find((card) => card.id === id); + + if (!card) return null; + + return { + ...card, + flipped: false, + }; + } + getBack(): TarokkaCard { return this.backs[0]; } diff --git a/server.ts b/server.ts index 904b65d..26890bc 100644 --- a/server.ts +++ b/server.ts @@ -79,6 +79,36 @@ app.prepare().then(() => { } }); + socket.on('redraw', ({ gameID, cardIndex }: ClientUpdate) => { + try { + //console.log(Date.now(), 'Redraw', { gameID, cardIndex }); + + const gameUpdate = gameStore.redraw(gameID, cardIndex); + + broadcast('game-update', gameUpdate); + } catch (e) { + const error = e instanceof Error ? e.message : e; + + console.error(Date.now(), 'Error[redraw]', error); + socket.emit('redraw-error', error); + } + }); + + socket.on('select', ({ gameID, cardIndex, cardID = '' }: ClientUpdate) => { + try { + //console.log(Date.now(), 'select', { gameID, cardIndex }); + + const gameUpdate = gameStore.select(gameID, cardIndex, cardID); + + broadcast('game-update', gameUpdate); + } catch (e) { + const error = e instanceof Error ? e.message : e; + + console.error(Date.now(), 'Error[select]', error); + socket.emit('select-error', error); + } + }); + socket.on('settings', ({ gameID, gameData }: { gameID: string; gameData: GameUpdate }) => { try { const gameUpdate = gameStore.updateSettings(gameID, gameData.settings); diff --git a/tools/getCardInfo.ts b/tools/getCardInfo.ts index 72a21ee..47e6d78 100644 --- a/tools/getCardInfo.ts +++ b/tools/getCardInfo.ts @@ -11,7 +11,7 @@ export default function getTooltip( let text: string[] = []; - if (flipped) { + if (dm || flipped) { if (dm || settings.positionFront) text.push(position.text); if (dm) text.push(`${cardName}: ${description}`); @@ -33,11 +33,9 @@ export default function getTooltip( // Low deck: Tome, Ravenkind, or Sunsword if (isLowCard(card)) { - if (dm) text.push(card.prophecy.dmText); if (dm || settings.prophecy) text.push(card.prophecy.playerText); + if (dm) text.push(card.prophecy.dmText); } - } else { - if (dm || settings.positionBack) text.push(position.text); } return text; diff --git a/tools/getURL.ts b/tools/getURL.ts index 1d0bddc..5539f20 100644 --- a/tools/getURL.ts +++ b/tools/getURL.ts @@ -1,7 +1,7 @@ import { cardStyles, standardMap } from '@/constants/tarokka'; -import { Settings, TarokkaGameCard } from '@/types'; +import { Settings, TarokkaCard, TarokkaGameCard } from '@/types'; -export default function getURL(card: TarokkaGameCard, settings: Settings) { +export default function getURL(card: TarokkaCard | TarokkaGameCard, settings: Settings) { const styleConfig = cardStyles[settings.cardStyle]; const fileBase = settings.cardStyle === 'standard' ? standardMap[card.id] : card.id; return `${styleConfig.baseURL}${fileBase}${card.extension || styleConfig.extension}`; diff --git a/types/index.ts b/types/index.ts index 9e8dbbd..cdfd8a8 100644 --- a/types/index.ts +++ b/types/index.ts @@ -1,5 +1,8 @@ export type CardStyle = 'standard' | 'color' | 'grayscale'; +// all = both + back +export type Deck = 'high' | 'common' | 'both' | 'back' | 'all'; + export interface Settings { positionBack: boolean; positionFront: boolean; @@ -28,6 +31,7 @@ export interface TarokkaBase { description: string; aria: string; back: boolean; + deck: Deck; suit: 'Coins' | 'Glyphs' | 'High Deck' | 'Stars' | 'Swords' | null; extension?: string; } @@ -90,6 +94,7 @@ export interface GameUpdate { export interface ClientUpdate { gameID: string; cardIndex: number; + cardID?: string; } export interface Layout {