diff --git a/client/game/BoardSlot.tsx b/client/game/BoardSlot.tsx index eff63e9..dcea71c 100644 --- a/client/game/BoardSlot.tsx +++ b/client/game/BoardSlot.tsx @@ -8,19 +8,31 @@ const EMPTY_SPACE = '-' interface CardTokenProps { card: Card | null onSelect: () => void + isSelected: boolean + disabled: boolean } export default function BoardSlot(props: CardTokenProps): JSX.Element { - const {onSelect, card} = props + const {onSelect, card, isSelected, disabled} = props - if (card == null) { + if (card === null || card.type === -1) { return ( - ) } return ( - + ) } diff --git a/client/game/Game.tsx b/client/game/Game.tsx index ad3db88..54e9a7c 100644 --- a/client/game/Game.tsx +++ b/client/game/Game.tsx @@ -3,34 +3,44 @@ import React from 'react' import type { GameHandle, Selection } from './types.ts' import BoardSlot from './BoardSlot.tsx' import CardToken from './cards/Card.tsx' -import {isAttackSelection, isMoveSelection, isPlayCardSelection} from './selection.tsx' +import { + isAttackSelection, + isMoveSelection, + isPlayCardSelection, + isAlly, + isBoard, + isHand, +} from './selection.tsx' type GameProps = GameHandle -export default function Game({ - team, - board, - player, - deck, - enemyLife, - enemyDeckSize, - enemyHandSize, - currentTurn, - canDraw, - hasDrawn, - gameStatus, - startTurn, - endTurn, - startDraw, - commitDraw, - attackCard, - moveCard, - playCard, -}: GameProps): JSX.Element { +export default function Game(props: GameProps): JSX.Element { + const { + team, + board, + player, + deck, + enemyLife, + enemyDeckSize, + enemyHandSize, + currentTurn, + canDraw, + hasDrawn, + gameStatus, + drawChoices, + startTurn, + endTurn, + startDraw, + commitDraw, + attackCard, + moveCard, + playCard, + getView, + } = props const [selection, setSelection] = React.useState(null) function selectCard(nextSelection: Selection): void { - if (!selection) { + if (selection === null) { setSelection(nextSelection) } else { if (isAttackSelection(selection, nextSelection)) { @@ -44,11 +54,27 @@ export default function Game({ } } - function selectTurnButton(): void { - } + React.useEffect(() => { + if (currentTurn === team) { + startTurn() + } + }, [currentTurn, startTurn]) + + React.useEffect(() => { + const intervalId = setInterval( + () => { + getView() + }, + 1000, + ) + return () => { + clearInterval(intervalId) + } + }, [getView]) const enemyBoard = team === 1 ? board.scourge : board.sentinal const allyBoard = team === 1 ? board.sentinal : board.scourge + const isMyTurn = currentTurn === team return (
@@ -58,19 +84,20 @@ export default function Game({

Life: {enemyLife}

Deck: {enemyDeckSize}

-

Turn: {currentTurn}

- {canDraw &&

Drawing phase...

} - {hasDrawn &&

Action phase...

} - +
+

{isMyTurn ? 'My' : 'Enemy'} Turn

+ {isMyTurn && canDraw && } + {isMyTurn && hasDrawn && } +

Life: {player.life}

-

Deck: {deck.length}

+

Deck: {deck.cards.length}

{Array(enemyHandSize).fill(null).map(() => ( - + ))}
@@ -78,6 +105,12 @@ export default function Game({ selectCard({target: 'opponent', type: 'board', index})} + isSelected={ + selection + ? !isAlly(selection) && isBoard(selection) && selection.index === index + : false + } + disabled={!(currentTurn === team && hasDrawn)} /> ))}
@@ -86,14 +119,55 @@ export default function Game({ selectCard({target: 'ally', type: 'board', index})} + isSelected={ + selection + ? isAlly(selection) && isBoard(selection) && selection.index === index + : false + } + disabled={!(currentTurn === team && hasDrawn)} /> ))}
+ {drawChoices.length > 0 && ( + +
+ {drawChoices.map((card, index) => ( + selectCard({target: 'ally', type: 'draws', index})} + isSelected={ + selection + ? selection.type === 'draws' && selection.index === index + : false + } + disabled={false} + /> + ))} +
+
+ +
+
+ )}
{player.hand.map((card, index) => ( selectCard({target: 'ally', type: 'hand', index})} + isSelected={ + selection + ? isAlly(selection) && isHand(selection) && selection.index === index + : false + } + disabled={!(isMyTurn && hasDrawn)} /> ))}
diff --git a/client/game/GameClient.tsx b/client/game/GameClient.tsx index 4d38b4b..cb72ff5 100644 --- a/client/game/GameClient.tsx +++ b/client/game/GameClient.tsx @@ -6,6 +6,7 @@ import Loading from '../components/Loading.tsx' import { AsyncHandle, + Card, FighterArea, GameState, GameAction, @@ -14,19 +15,69 @@ import { import useServerSocket from './useServerSocket.ts' import Game from './Game.tsx' -function reducer(state: GameState, action: GameAction): GameState { +interface GameClientState extends GameState { + isDrawing: boolean +} + +type GameClientAction = GameAction + +function reducer(state: GameClientState, action: GameClientAction): GameClientState { switch (action.type) { case 'update-state': { - return {...action.state, team: state.team} + return {...state, ...action.state} } - case 'set-player': { + case 'set-player-team': { return {...state, team: action.team} } + case 'play-card': { + const nextHand = [ + ...state.player.hand.slice(0, action.handIndex), + ...state.player.hand.slice(action.handIndex + 1), + ] + return { + ...state, + player: { ...state.player, hand: nextHand }, + } + } + case 'receive-cards': { + // first, i can draw and i am not yet drawing + // scry N cards from the deck + if (state.canDraw && !state.isDrawing) { + return { + ...state, + isDrawing: true, + drawChoices: action.cards, + } + } + // then, i am drawing, i can still draw, and I haven't yet drawn. + // i draw a card into my hand + if (state.canDraw && state.isDrawing && !state.hasDrawn) { + return { + ...state, + drawChoices: [], + player: { + ...state.player, + hand: action.cards + }, + isDrawing: false, + hasDrawn: true, + } + } + // then, i am no longer drawing and cannot draw. this is the board. + const team = state.team === 1 ? 'sentinal' : 'scourge' as const + return { + ...state, + board: { + ...state.board, + [team]: action.cards, + }, + } + } default: return state } } -const initialState: GameState = { +const initialState: GameClientState = { board: { sentinal: Array(4).fill(undefined) as FighterArea, scourge: Array(4).fill(undefined) as FighterArea, @@ -38,7 +89,9 @@ const initialState: GameState = { life: 0, ready: false, }, - deck: [], + deck: { + cards: [] + }, team: 1, enemyLife: 0, enemyDeckSize: 0, @@ -47,17 +100,20 @@ const initialState: GameState = { canDraw: false, hasDrawn: false, gameStatus: 0, + isDrawing: false, + drawChoices: [], } export default function GameClient(): JSX.Element { const [state, dispatch] = React.useReducer(reducer, initialState) - const handleGameUpdate = React.useCallback((data: Record) => { - console.log(data) + const handleGameUpdate = React.useCallback((action: GameAction) => { + dispatch(action) }, []) const socketHandle = useServerSocket(handleGameUpdate) + const {team} = state const gameHandle = React.useMemo>( () => { if (socketHandle.status !== 'connected') return socketHandle @@ -69,6 +125,7 @@ export default function GameClient(): JSX.Element { socketHandle.handle.sendGameCommand({ type: 's', cmd: 'b', + player_id: team, }) }, startTurn: () => { @@ -76,6 +133,7 @@ export default function GameClient(): JSX.Element { socketHandle.handle.sendGameCommand({ type: 's', cmd: 's', + player_id: team, }) }, endTurn: () => { @@ -83,6 +141,7 @@ export default function GameClient(): JSX.Element { socketHandle.handle.sendGameCommand({ type: 's', cmd: 'e', + player_id: team, }) }, getView: () => { @@ -90,6 +149,7 @@ export default function GameClient(): JSX.Element { socketHandle.handle.sendGameCommand({ type: 's', cmd: 'g', + player_id: team, }) }, startDraw: () => { @@ -97,20 +157,25 @@ export default function GameClient(): JSX.Element { socketHandle.handle.sendGameCommand({ type: 'a', cmd: 's', + player_id: team, }) }, commitDraw: (cardIndex: number) => { if (socketHandle.status !== 'connected') return + // dispatch({type}) socketHandle.handle.sendGameCommand({ type: 'a', cmd: `d ${cardIndex}`, + player_id: team, }) }, playCard: (handIndex: number, positionIndex: number) => { if (socketHandle.status !== 'connected') return + dispatch({ type: 'play-card', handIndex }) socketHandle.handle.sendGameCommand({ type: 'a', cmd: `p ${handIndex} ${positionIndex}`, + player_id: team, }) }, moveCard: (positionFrom: number, positionTo: number) => { @@ -118,6 +183,7 @@ export default function GameClient(): JSX.Element { socketHandle.handle.sendGameCommand({ type: 'a', cmd: `m ${positionFrom} ${positionTo}`, + player_id: team, }) }, attackCard: (positionFrom: number, positionTo: number) => { @@ -125,23 +191,22 @@ export default function GameClient(): JSX.Element { socketHandle.handle.sendGameCommand({ type: 'a', cmd: `a ${positionFrom} ${positionTo}`, + player_id: team, }) }, }, } }, - [socketHandle, state], + [socketHandle, team], ) React.useEffect(() => { if (gameHandle.status !== 'connected') return gameHandle.handle.readyPlayer() - if (state?.team === 1) gameHandle.handle.startTurn() - }, [gameHandle, state?.team]) + }, [gameHandle, team]) switch (gameHandle.status) { case 'connected': { - if (!state) return return } case 'not-connected': diff --git a/client/game/cards/Card.tsx b/client/game/cards/Card.tsx index 2906339..30dec1d 100644 --- a/client/game/cards/Card.tsx +++ b/client/game/cards/Card.tsx @@ -9,21 +9,30 @@ const EMPTY_SPACE = '-' interface CardTokenProps { cardKey: CardKey | null onSelect?: () => void + isSelected: boolean + disabled: boolean } export default function CardToken(props: CardTokenProps): JSX.Element { - const {onSelect, cardKey} = props - + const {onSelect, cardKey, isSelected, disabled} = props if (cardKey == null) { return ( -
- {EMPTY_SPACE} +
+
+ {EMPTY_SPACE} +
) } return ( -