diff --git a/src/App.tsx b/src/App.tsx index 9cac747..6534161 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,51 +1,21 @@ -import { BrowserRouter, Routes, Route } from 'react-router-dom' import { useState } from 'react' +import { BrowserRouter, Routes, Route } from 'react-router-dom' import Gameboard from './components/Gameboard/Gameboard' -import GameConstructor from './util/GameConstructor'; -import { PlayerData, NobleData, CardData, AppState } from './util/types'; -import CardDeck from './data/cards.json'; +import GameConstructor from './components/GameConstructor'; +import { initialState } from './util/stateSetters'; import './App.css' function App() { - const [state, setState] = useState({ - gameboard: { - nobles: new Array, - cardRows: { - tierOne: new Array, - tierTwo: new Array, - tierThree: new Array, - }, - tradingResources: { - ruby: 7, - sapphire: 7, - emerald: 7, - diamond: 7, - onyx: 7, - gold: 5 - }, - deck: CardDeck, - }, - round: 1, - players: new Array, - actions: { - getChips: { - active: false - }, - buyCard: { - active: false - }, - reserveCard: { - active: false - } - } - }) + const [state, setState] = useState(initialState); return (

SPLENDOR

+ {/* @ts-ignore */} } /> + {/* @ts-ignore */} } /> diff --git a/src/components/Gameboard/CardRow.css b/src/components/Card/CardRow.css similarity index 100% rename from src/components/Gameboard/CardRow.css rename to src/components/Card/CardRow.css diff --git a/src/components/Gameboard/CardRow.tsx b/src/components/Card/CardRow.tsx similarity index 100% rename from src/components/Gameboard/CardRow.tsx rename to src/components/Card/CardRow.tsx diff --git a/src/util/GameConstructor.tsx b/src/components/GameConstructor.tsx similarity index 98% rename from src/util/GameConstructor.tsx rename to src/components/GameConstructor.tsx index 2946eb3..1b4a3eb 100644 --- a/src/util/GameConstructor.tsx +++ b/src/components/GameConstructor.tsx @@ -1,7 +1,6 @@ import { useEffect, useState } from "react" import { useNavigate } from "react-router-dom" -import { v4 } from "uuid"; -import { CardData, NobleData, PlayerData, StateProps } from "./types"; +import { CardData, NobleData, PlayerData, StateProps } from "../util/types"; interface InputState { playerOne: PlayerInput diff --git a/src/components/Gameboard/Gameboard.tsx b/src/components/Gameboard/Gameboard.tsx index 50afc36..c942fe8 100644 --- a/src/components/Gameboard/Gameboard.tsx +++ b/src/components/Gameboard/Gameboard.tsx @@ -1,21 +1,71 @@ +// types, data, utils +import { AppState, PlayerData, ResourceCost, SetActionType, StateProps } from '../../util/types'; +import { setStateBuyCard, setStateGetChips, setStateReserveCard } from '../../util/stateSetters'; import { useCallback, useEffect, useState } from 'react'; -import { AppState, FullDeck, NobleData, StateProps } from '../../util/types'; -import AllPlayers from '../Player/AllPlayers'; -import AvailableChips from '../Resources/AvailableChips'; -import CardRow from './CardRow'; import Nobles from './Nobles'; -import NobleStore from '../../data/nobles.json'; -import useActionStatus from '../../util/useActionStatus'; + +// components +import initializeBoard from '../../util/initializeBoard'; +import AvailableChips from '../Resources/AvailableChips'; +import AllPlayers from '../Player/AllPlayers'; +import CardRow from '../Card/CardRow'; +import { validateChips } from '../Player/ActionMethods'; +import SelectionView from '../Resources/SelectionView'; export default function Gameboard({ state, setState }: StateProps) { - const [view, setView] = useState(

Loading...

) - const [selection, setSelection] = useState>([]); - const chipSelection = { selection, setSelection }; + const [view, setView] = useState(

Loading...

); + // callbacks for lifting state + const liftSelection = useCallback((value: keyof ResourceCost) => { + if (!state.actions.getChips.active) return; + + setState((prev: AppState) => { + let newSelection = prev.actions.getChips.selection; + newSelection?.push(value); + + let newState = { + ...prev, + actions: { + ...state.actions, + getChips: { + active: true, + selection: newSelection, + valid: false + } + } + } + + const result = validateChips(newState); + newState.actions.getChips.valid = result; + + return newState; + }) + }, [state]); + + const setActionState = useCallback((value: SetActionType, player: PlayerData) => { + if (!player?.turnActive) return; + + switch (value) { + case 0: + if (!state.actions.getChips.active) setState((prev) => setStateGetChips(prev)); + break; + case 1: + if (!state.actions.buyCard.active) setState((prev) => setStateBuyCard(prev)); + break; + case 2: + if (!state.actions.reserveCard.active) setState((prev) => setStateReserveCard(prev)); + break; + default: + break; + } + }, []); + + // util functions, setup on mount useEffect(() => { - initializeBoard(); + initializeBoard(state, setState); }, []) + // displays state of board if data is populated useEffect(() => { if (!state.players.length) { setView( @@ -32,61 +82,15 @@ export default function Gameboard({ state, setState }: StateProps) { - - + + + {/* @ts-ignore */} +
) } }, [state]); - const shuffleDeck = () => { - if (!state.gameboard.deck) return; - let newDeck: FullDeck = state.gameboard.deck; - - for (const [key, value] of Object.entries(newDeck)) { - for (let i = value.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)) - const temp = value[i]; - value[i] = value[j]; - value[j] = temp; - } - } - - setState({ ...state, gameboard: { ...state.gameboard, deck: newDeck }}) - } - - const setNobles = () => { - let newNobles = NobleStore.nobles; - let shuffledNobles = new Array; - - while (shuffledNobles.length < 4) { - const rand = Math.floor(Math.random() * newNobles.length); - const randNoble = newNobles.splice(rand,1)[0]; - shuffledNobles.push(randNoble); - } - - setState({ ...state, gameboard: { ...state.gameboard, nobles: shuffledNobles }}) - } - - const initializeBoard = () => { - shuffleDeck(); - - let newDeck = state.gameboard.cardRows; - for (const [key, value] of Object.entries(state.gameboard.deck)) { - while (newDeck[key as keyof FullDeck].length < 4) { - // @ts-ignore - const nextCard = value.shift(); - newDeck[key as keyof FullDeck].push(nextCard); - } - } - - setState({ ...state, gameboard: { ...state.gameboard, cardRows: newDeck } }) - setNobles(); - } - - const liftFromChildren = useCallback((childState: AppState) => { - setState(childState); - }, [state]); - + // render return view } \ No newline at end of file diff --git a/src/components/Player/ActionMethods.ts b/src/components/Player/ActionMethods.ts new file mode 100644 index 0000000..f6e46d2 --- /dev/null +++ b/src/components/Player/ActionMethods.ts @@ -0,0 +1,103 @@ +import { AppState, PlayerData, ResourceCost, setStateType } from "../../util/types"; +import { turnOrderUtil } from "../../util/TurnOrderUtil"; +import { initialActions } from "../../util/stateSetters"; + +export const _getChips = (resource: string | Array, dynamic: PlayerData | undefined, setState: setStateType) => { + if (!dynamic || !dynamic?.turnActive) return; + + setState((prev: AppState) => { + const { newPlayers, roundIncrement } = turnOrderUtil(prev, dynamic); + + return { + ...prev, + round: (roundIncrement ? prev.round + 1 : prev.round), + gameboard: { + ...prev.gameboard, + tradingResources: { + ...prev.gameboard.tradingResources, + [resource as keyof ResourceCost]: prev.gameboard.tradingResources[resource as keyof ResourceCost] -= 1 + } + }, + players: newPlayers + } + }) +} + +export const validateChips = (state: AppState): boolean => { + if (!state.actions.getChips.active || !state.actions.getChips.selection) return false; + + const selection = state.actions.getChips.selection; + + if (selection.length === 0 || selection.length > 3) return false; + const unique = new Set(selection); + + if (selection.length === 3 && selection.length > unique.size) return false; + return true; +} + +export const getChips = (state: AppState, setState: setStateType): boolean => { + /** + * features: + * update their inventory state + * update the total available resources + */ + + let targetPlayer: PlayerData; + + for (let each in state.players) { + if (state.players[each].turnActive) { + targetPlayer = state.players[each]; + } + } + + setState((prev) => { + const { newPlayers, roundIncrement } = turnOrderUtil(state, targetPlayer); + const idx = newPlayers.indexOf(targetPlayer); + const resources = state.actions.getChips.selection; + let newResources = prev.gameboard.tradingResources; + + if (resources) { + for (let value of resources) { + + // update player inventory + for (let each in newPlayers[idx].inventory) { + if (value === each) { + newPlayers[idx].inventory[value] += 1; + } + } + + // update globally available resources + for (let each in newResources) { + if (value === each) { + newResources[value] -= 1; + } + } + } + + } + + + return { + ...prev, + round: (roundIncrement ? prev.round + 1 : prev.round), + gameboard: { + ...prev.gameboard, + tradingResources: newResources + }, + players: newPlayers, + actions: initialActions + } + }) + + console.log(state.players); + + return true; +} + +export const buyCard = () => { + return; +} + +export const reserveCard = () => { + return; +} \ No newline at end of file diff --git a/src/components/Player/AllPlayers.tsx b/src/components/Player/AllPlayers.tsx index becd964..4e90b62 100644 --- a/src/components/Player/AllPlayers.tsx +++ b/src/components/Player/AllPlayers.tsx @@ -1,26 +1,17 @@ import Player from "./Player"; import { v4 } from "uuid"; -import { PlayerData, StateProps } from "../../util/types"; -import { Dispatch, SetStateAction, useEffect } from "react"; import "./AllPlayers.css" +import { PlayerData, SetActionType, StateProps } from "../../util/types"; interface AllPlayersProps extends StateProps { - liftFromChildren?: any, - chipSelection: { - selection: String[], - setSelection: Dispatch>> - } + setActionState: (value: SetActionType, player?: PlayerData) => void } -export default function AllPlayers({ state, setState, chipSelection, liftFromChildren }: AllPlayersProps) { - useEffect(() => { - return; - }, [state]) - +export default function AllPlayers({ state, setState, setActionState }: AllPlayersProps) { return (
{ - state.players?.map((player: PlayerData) => ) + state.players?.map((player: PlayerData) => ) }
) diff --git a/src/components/Player/Player.tsx b/src/components/Player/Player.tsx index 3575728..56f2aad 100644 --- a/src/components/Player/Player.tsx +++ b/src/components/Player/Player.tsx @@ -1,85 +1,32 @@ -import { AppState, ActionPrompts, GameActions, PlayerData, ResourceCost, StateProps } from "../../util/types"; -import { Dispatch, SetStateAction, useEffect, useState } from "react"; -import { TurnOrderUtil } from "../../util/TurnOrderUtil"; -import useActionType from "../../util/useActionType"; +import { PlayerData, SetActionType, StateProps } from "../../util/types" +import { useEffect, useState } from "react"; import { v4 } from "uuid"; interface PlayerProps extends StateProps { - player: PlayerData - chipSelection: { - selection: String[], - setSelection: Dispatch>> - }, - liftFromChildren: any + player: PlayerData, + setActionState: (value: SetActionType, player?: PlayerData) => void } -export default function Player({ player, state, setState, chipSelection, liftFromChildren }: PlayerProps) { - const [actionPrompt, setActionPrompt] = useState(ActionPrompts[0]); - const [actionType, setActionType] = useState(GameActions.AWAIT); - const [dynamic, setDynamic] = useState(); - const { selection, setSelection } = chipSelection; +export default function Player({ player, state, setState, setActionState }: PlayerProps) { + const [dynamic, setDynamic] = useState(); + const [prompt, setPrompt] = useState("Your turn! Select an action type below."); + const [actionSelection, setActionSelection] = useState(-1); + + useEffect(() => setDynamic(state.players.find((element: PlayerData) => element.id === player.id)), [state]); useEffect(() => { - return; - }, [selection, setSelection]) + setActionState(actionSelection, dynamic); - useEffect(() => { - setDynamic(state.players.find((element: PlayerData) => element.id === player.id)); - }, [state, setState]); - - useEffect(() => { - const newState = useActionType(state, actionType); - - switch (actionType) { - case GameActions.GETCHIPS: - setActionPrompt(ActionPrompts[1]); - getChips(newState); - setSelection([]); - break; - case GameActions.BUYCARD: - setActionPrompt(ActionPrompts[2]); - break; - case GameActions.RESERVECARD: - setActionPrompt(ActionPrompts[3]); - break; - default: - break; + if (state.actions.getChips.active) { + setPrompt('Make your selection of up to three chips.'); + } else if (state.actions.buyCard.active) { + setPrompt('Choose a card above to purchase.'); + } else if (state.actions.reserveCard.active) { + setPrompt('Choose a card above to reserve.'); + } else { + setPrompt("Your turn! Select an action type below."); } - }, [actionType]); - - const getChips = (newState: AppState) => { - if (!dynamic?.turnActive) return; - setActionPrompt(ActionPrompts[1]); - - if (selection.length < 3) return; - - setState(() => { - const { newPlayers, roundIncrement } = TurnOrderUtil(newState, dynamic); - console.log(newPlayers) - let newResources = newState.gameboard.tradingResources; - - for (let item of selection) { - for (let [key, value] of Object.entries(newResources)) { - if (key === item) { - newResources[key as keyof ResourceCost] = value - 1; - break; - } - } - } - - return { - ...newState, - round: (roundIncrement ? newState.round + 1 : newState.round), - players: newPlayers, - gameboard: { - ...newState.gameboard, - tradingResources: newResources - }, - } - }) - - liftFromChildren(state); - } + }, [actionSelection]) return (
@@ -89,11 +36,10 @@ export default function Player({ player, state, setState, chipSelection, liftFro

Is {player.starter || "not"} round starter

{/* Dynamic data from state */} -

{dynamic?.turnActive ? actionPrompt : "..."}

- - - - +

{dynamic?.turnActive ? prompt : '...'}

+ + +
diff --git a/src/components/Resources/AvailableChips.tsx b/src/components/Resources/AvailableChips.tsx index 5f4bc11..98639c9 100644 --- a/src/components/Resources/AvailableChips.tsx +++ b/src/components/Resources/AvailableChips.tsx @@ -1,59 +1,31 @@ -import { GameActions, ResourceCost, StateProps } from "../../util/types"; +import { ResourceCost, StateProps } from "../../util/types"; +import { useEffect } from "react"; import { v4 } from "uuid"; import "./AvailableChips.css" -import { Dispatch, SetStateAction, useEffect, useState } from "react"; -import useActionStatus from "../../util/useActionStatus"; +import { setStateGetChips } from "../../util/stateSetters"; -interface AvailableChipsProps extends StateProps { - liftFromChildren: any, - chipSelection: { - selection: String[], - setSelection: Dispatch>> - } +interface ResourceProps extends StateProps { + liftSelection: (value: keyof ResourceCost) => void } -export default function AvailableChips({ state, chipSelection, liftFromChildren }: AvailableChipsProps) { - const { selection, setSelection } = chipSelection; - - const handleSelection = (key: string) => { - console.log(key) - console.log(state); - - if (selection.length > 3) return; - - setSelection((prev) => { - let newValue = prev; - newValue.push(key); - return newValue; - }) - } - +export default function AvailableChips({ state, setState, liftSelection }: ResourceProps) { useEffect(() => { - useActionStatus(state); + return; }, [state]) return (
- -
- Selection: - { selection.map((each) =>

{each}

) } -
- { - Object.keys(state.gameboard.tradingResources).map((key: string) => { + Object.keys(state.gameboard.tradingResources).map((key: string | keyof ResourceCost) => { return (
-
) }) } -
) } \ No newline at end of file diff --git a/src/components/Resources/SelectionView.tsx b/src/components/Resources/SelectionView.tsx new file mode 100644 index 0000000..c359a35 --- /dev/null +++ b/src/components/Resources/SelectionView.tsx @@ -0,0 +1,38 @@ +import { v4 } from "uuid"; +import { useEffect, useState } from "react"; +import { ResourceCost, StateProps } from "../../util/types"; +import { setStateGetChips } from "../../util/stateSetters"; +import { GetChipsHTML } from "./ViewHTML"; + +export default function SelectionView({ state, setState }: StateProps) { + const actionTypes = [ + state.actions.getChips, + state.actions.buyCard, + state.actions.reserveCard + ] + const [view, setView] = useState(<>); + + useEffect(() => { + setView(() => { + switch (true) { + case (actionTypes[0].active): + return + case (actionTypes[1].active): + return ( + <> + {actionTypes[1].active && Your selection is {actionTypes[1].valid || "not"} valid} +

Card will display here

+ + ) + default: + return <>; + } + }) + }, [state]) + + return ( +
+ { view } +
+ ) +} \ No newline at end of file diff --git a/src/components/Resources/ViewHTML.tsx b/src/components/Resources/ViewHTML.tsx new file mode 100644 index 0000000..d42a3f4 --- /dev/null +++ b/src/components/Resources/ViewHTML.tsx @@ -0,0 +1,36 @@ +import { v4 } from "uuid"; +import { useEffect, useState } from "react"; +import { setStateGetChips } from "../../util/stateSetters"; +import { ResourceCost, StateProps } from "../../util/types"; +import { getChips } from "../Player/ActionMethods"; + +export const GetChipsHTML = ({ state, setState }: StateProps) => { + const [prompt, setPrompt] = useState(""); + + useEffect(() => { + if (!state.actions.getChips.active) setPrompt(""); + if (state.actions.getChips.selection?.length === 0) { + setPrompt("Please make a selection."); + } else { + setPrompt(`Your selection is ${state.actions.getChips.valid ? '' : "not"} valid`); + } + }, [state]) + + return ( +
+ {prompt} +
+ { + state.actions.getChips.active && + state.actions.getChips.selection?.map((each: keyof ResourceCost) =>

{each}

) + } +
+ { + state.actions.getChips.valid ? + + : + + } +
+ ) +} \ No newline at end of file diff --git a/src/util/TurnOrderUtil.ts b/src/util/TurnOrderUtil.ts index d28439f..64af43f 100644 --- a/src/util/TurnOrderUtil.ts +++ b/src/util/TurnOrderUtil.ts @@ -1,10 +1,11 @@ import { AppState, PlayerData } from "./types"; -export const TurnOrderUtil = (prev: AppState, dynamic: PlayerData) => { +export const turnOrderUtil = (prev: AppState, dynamic: PlayerData) => { let roundIncrement = false; const newPlayers = prev.players; for (let each of newPlayers) { + console.log(each); if (each.id === dynamic.id) { each.turnActive = false; } else if (each.id === dynamic.id + 1) { diff --git a/src/util/initializeBoard.ts b/src/util/initializeBoard.ts new file mode 100644 index 0000000..21699c9 --- /dev/null +++ b/src/util/initializeBoard.ts @@ -0,0 +1,47 @@ +import { AppState, FullDeck, NobleData, setStateType } from "./types"; +import NobleStore from '../data/nobles.json'; + +const shuffleDeck = (state: AppState, setState: setStateType) => { + if (!state.gameboard.deck) return; + let newDeck: FullDeck = state.gameboard.deck; + + for (const [key, value] of Object.entries(newDeck)) { + for (let i = value.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + const temp = value[i]; + value[i] = value[j]; + value[j] = temp; + } + } + + setState({ ...state, gameboard: { ...state.gameboard, deck: newDeck }}) +} + +const setNobles = (state: AppState, setState: setStateType) => { + let newNobles = NobleStore.nobles; + let shuffledNobles = new Array; + + while (shuffledNobles.length < 4) { + const rand = Math.floor(Math.random() * newNobles.length); + const randNoble = newNobles.splice(rand,1)[0]; + shuffledNobles.push(randNoble); + } + + setState({ ...state, gameboard: { ...state.gameboard, nobles: shuffledNobles }}) +} + +export default function initializeBoard(state: AppState, setState: setStateType) { + shuffleDeck(state, setState); + + let newDeck = state.gameboard.cardRows; + for (const [key, value] of Object.entries(state.gameboard.deck)) { + while (newDeck[key as keyof FullDeck].length < 4) { + // @ts-ignore + const nextCard = value.shift(); + newDeck[key as keyof FullDeck].push(nextCard); + } + } + + setState({ ...state, gameboard: { ...state.gameboard, cardRows: newDeck } }) + setNobles(state, setState); +} \ No newline at end of file diff --git a/src/util/stateSetters.ts b/src/util/stateSetters.ts new file mode 100644 index 0000000..c3f8852 --- /dev/null +++ b/src/util/stateSetters.ts @@ -0,0 +1,79 @@ +import { AppState, CardData, NobleData, PlayerData, ResourceCost } from "./types"; +import CardDeck from '../data/cards.json'; + +export const initialActions = { + buyCard: { active: false }, + getChips: { active: false }, + reserveCard: { active: false } +} + +export const initialState = { + gameboard: { + nobles: new Array, + cardRows: { + tierOne: new Array, + tierTwo: new Array, + tierThree: new Array, + }, + tradingResources: { + ruby: 7, + sapphire: 7, + emerald: 7, + diamond: 7, + onyx: 7, + gold: 5 + }, + deck: CardDeck, + }, + round: 1, + players: new Array, + actions: initialActions +} + +export const setStateAwaitAction = (prev: AppState) => { + return { + ...prev, + actions: initialActions + } +} + +export const setStateGetChips = (prev: AppState) => { + return { + ...prev, + actions: { + ...initialState.actions, + getChips: { + active: true, + selection: new Array, + valid: false + } + } + } +} + +export const setStateBuyCard = (prev: AppState) => { + return { + ...prev, + actions: { + ...initialState.actions, + buyCard: { + active: true, + valid: false + } + } + } +} + +export const setStateReserveCard = (prev: AppState) => { + return { + ...prev, + actions: { + ...initialState.actions, + reserveCard: { + active: true, + includeGold: false, + valid: false + } + } + } +} \ No newline at end of file diff --git a/src/util/types.ts b/src/util/types.ts index 52af9ad..0237090 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -13,45 +13,44 @@ export interface AppState { }, round: number, players: Array, - actions: ActionTypes + actions: StateActions } -export interface StateProps { - state: AppState, - setState: Dispatch> +interface StateActions { + setAction?: (arg: SetActionType) => void + getChips: { + active: boolean + selection?: Array + valid?: boolean + confirm?: () => void + } + buyCard: { + active: boolean + selection?: CardData + valid?: boolean + confirm?: () => void + } + reserveCard: { + active: boolean + selection?: CardData + includeGold?: boolean + valid?: boolean + confirm?: () => void + } } -export enum GameActions { +export enum SetActionType { GETCHIPS, BUYCARD, RESERVECARD, AWAIT } -export interface ActionTypes { - getChips: { - active: boolean - chips?: Array - valid?: boolean - } - buyCard: { - active: boolean - card?: CardData | null - } - reserveCard: { - active: boolean - card?: CardData | null - includeGold?: boolean - } -} +export type setStateType = Dispatch> -export enum ActionPrompts { - "Choose your action type below:", - "Make a selection of three different available resources, or two of the same.", - "Choose a card to purchase above.", - "Select any card above to reserve. You will also automatically take a gold chip.", - "Select any card above to reserve. You have the maximum allowed number of chips, so you cannnot take a gold chip.", - "It is not your turn." +export interface StateProps { + state: AppState, + setState: setStateType } export interface GameInformation {