Merge pull request #2 from innocuous-symmetry/rework-080522

Rework 080522
This commit is contained in:
Mikayla Dobson
2022-08-06 19:28:44 -05:00
committed by GitHub
25 changed files with 1948 additions and 241 deletions

1425
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,10 +17,12 @@
"uuid": "^8.3.2"
},
"devDependencies": {
"@testing-library/react": "^13.3.0",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/uuid": "^8.3.4",
"@vitejs/plugin-react": "^2.0.0",
"jsdom": "^20.0.0",
"typescript": "^4.6.4",
"vite": "^3.0.0",
"vitest": "^0.20.2"

View File

@@ -1,10 +1,11 @@
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { initialState } from './util/stateSetters';
import { useState } from 'react'
import { useEffect, useState } from 'react'
import Gameboard from './components/Gameboard/Gameboard'
import GameConstructor from './components/GameConstructor';
import './App.css'
import ResumeGame from './components/ResumeGame';
function App() {
const [state, setState] = useState(initialState);
@@ -14,9 +15,11 @@ function App() {
<h1>SPLENDOR</h1>
<BrowserRouter>
<Routes>
{/* @ts-ignore */}a
{/* @ts-ignore */}
<Route path="/" element={<GameConstructor state={state} setState={setState} />} />
{/* @ts-ignore */}
<Route path="/resume-game" element={<ResumeGame state={state} setState={setState} /> } />
{/* @ts-ignore */}
<Route path="/game" element={<Gameboard state={state} setState={setState} />} />
</Routes>
</BrowserRouter>

View File

@@ -9,8 +9,10 @@ const { buyCard, tooExpensive } = buyCardActions;
export default function Card({ data, state, setState }: CardProps) {
const currentPlayer = useCurrentPlayer(state);
if (!data) return <div className="card"></div>;
return (
<div className={`card`}>
<div className="card">
<div className="top-row">
<p>Counts as: {data.gemValue}</p>
<p>Point value: {data.points || 0}</p>
@@ -23,7 +25,7 @@ export default function Card({ data, state, setState }: CardProps) {
}
{ state.actions.buyCard.active &&
<button
onClick={() => buyCard(data, state, setState)}
onClick={() => buyCard(state, setState, data)}
disabled={tooExpensive(data, state)}>
Buy This Card
</button>

View File

@@ -2,8 +2,11 @@ import { CardRowProps } from '../../util/propTypes';
import { CardData } from "../../util/types"
import Card from "../Card/Card"
import { v4 } from 'uuid';
import cardTierToKey from '../../util/cardTierToKey';
export default function CardRow({tier, state, setState}: CardRowProps) {
const typedTier = cardTierToKey(tier);
let cards: Array<CardData>
switch (tier) {
case 1:
@@ -24,6 +27,9 @@ export default function CardRow({tier, state, setState}: CardRowProps) {
<div className={`card-row tier-${tier}`}>
<p>Tier: {tier}</p>
<div className="card-row-cards-visible">
<div className="card card-count">
<p>Remaining: {state.gameboard.deck[typedTier].length}</p>
</div>
{ cards && cards.map((cardData: CardData) => {
return <Card key={v4()} data={cardData} state={state} setState={setState} />
})}

View File

@@ -1,10 +0,0 @@
import { CardData } from "../../util/types"
export const MiniCard = ({ data }: {data: CardData}) => {
return (
<div className="mini-card" style={{backgroundColor: 'white'}}>
<p>{data.gemValue} card</p>
<p>{data.points || null}</p>
</div>
)
}

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from "react"
import { useNavigate } from "react-router-dom"
import { Link, useNavigate } from "react-router-dom"
import { CardData, NobleData, PlayerData } from "../util/types";
import { StateProps } from '../util/propTypes';
@@ -103,7 +103,9 @@ export default function GameConstructor({ state, setState }: StateProps) {
return (
<div className="game-constructor App">
<h1>Configure a new game:</h1>
<strong>Start a new game</strong>
<h2>OR</h2>
<strong>Enter your previous game data <Link to={'/resume-game'}>here</Link> to pick up where you left off.</strong>
<div>
<label htmlFor="P1-NAME">Player 1 Name:</label>
@@ -162,7 +164,7 @@ export default function GameConstructor({ state, setState }: StateProps) {
</input>
<input
type="radio"
id="P1-START"
id="P4-START"
onChange={() => handleRadio(4)}
checked={starter === 3 && input.playerFour.name.length > 0}>
</input>

View File

@@ -3,7 +3,7 @@ import { AppState, ResourceCost } from '../../util/types';
import { useCallback, useEffect, useState } from 'react';
import { getChipsActions } from '../Player/ActionMethods';
import { StateProps } from '../../util/propTypes';
const { validateChips } = getChipsActions;
import { Link } from 'react-router-dom';
// components
import Nobles from './Nobles';
@@ -12,6 +12,7 @@ import AvailableChips from '../Resources/AvailableChips';
import AllPlayers from '../Player/AllPlayers';
import CardRow from '../Card/CardRow';
import SelectionView from '../Resources/SelectionView';
const { validateChips } = getChipsActions;
export default function Gameboard({ state, setState }: StateProps) {
const [view, setView] = useState(<p>Loading...</p>);
@@ -19,7 +20,6 @@ export default function Gameboard({ state, setState }: StateProps) {
// 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);
@@ -38,7 +38,6 @@ export default function Gameboard({ state, setState }: StateProps) {
const result = validateChips(newState);
newState.actions.getChips.valid = result;
return newState;
})
}, [state]);
@@ -50,15 +49,21 @@ export default function Gameboard({ state, setState }: StateProps) {
useEffect(() => {
setCardRows(state);
for (let player of state.players) {
if (player.points >= 15) {
console.log('trigger endgame');
}
}
}, [state])
// displays state of board if data is populated
// displays state of board if data is populated, otherwise points to game constructor
useEffect(() => {
if (!state.players.length) {
setView(
<div className="error-page">
<strong>Sorry! It appears we've lost track of your game data.</strong>
<p>Please head back to the <a href="/">home page</a> to start a fresh game.</p>
<p>Please head back to the <Link to="/">home page</Link> to start a fresh game.</p>
</div>
);
} else {

View File

@@ -0,0 +1,144 @@
import { describe, test } from "vitest";
import { CardData, NobleData, PlayerData } from "../../util/types";
export const firstNoble: NobleData = {
points: 3,
resourceCost: {
ruby: 0,
sapphire: 0,
emerald: 0,
onyx: 0,
diamond: 3
}
}
export const secondNoble: NobleData = {
points: 3,
resourceCost: {
ruby: 3,
sapphire: 0,
emerald: 0,
onyx: 0,
diamond: 0
}
}
const exampleOneCards: CardData[] = [
{
gemValue: "diamond",
tier: 3,
points: 0,
resourceCost: {
ruby: 0,
sapphire: 0,
emerald: 0,
onyx: 0,
diamond: 3
}
},
{
gemValue: "diamond",
tier: 3,
points: 1,
resourceCost: {
ruby: 0,
sapphire: 0,
emerald: 0,
onyx: 0,
diamond: 4
}
},
{
gemValue: "diamond",
tier: 2,
points: 2,
resourceCost: {
ruby: 0,
sapphire: 0,
emerald: 0,
onyx: 0,
diamond: 5
}
}
]
export const legalPlayer: PlayerData = {
name: "First example",
id: 1,
starter: true,
turnActive: true,
points: 5,
nobles: [],
cards: exampleOneCards,
reservedCards: [],
inventory: {
ruby: 0,
sapphire: 0,
emerald: 0,
onyx: 0,
diamond: 0
}
}
const exampleTwoCards: CardData[] = [
{
gemValue: "ruby",
tier: 2,
points: 2,
resourceCost: {
ruby: 5,
sapphire: 0,
emerald: 0,
onyx: 0,
diamond: 0
}
},
{
gemValue: "ruby",
tier: 3,
points: 1,
resourceCost: {
ruby: 4,
sapphire: 0,
emerald: 0,
onyx: 0,
diamond: 0
}
},
{
gemValue: "ruby",
tier: 3,
points: 0,
resourceCost: {
ruby: 3,
sapphire: 0,
emerald: 0,
onyx: 0,
diamond: 0
}
},
]
export const doesNotIncludeInventory: PlayerData = {
name: "second example",
id: 2,
starter: true,
turnActive: true,
points: 5,
nobles: [],
cards: exampleTwoCards,
reservedCards: [],
inventory: {
ruby: 3,
sapphire: 3,
emerald: 3,
onyx: 3,
diamond: 3,
}
}
describe('canPickUpNoble', () => {
test('detects noble eligibility by card count', () => {
})
})

View File

@@ -1,21 +1,16 @@
import { useEffect } from "react";
import { v4 } from "uuid";
import { NobleData, ResourceCost } from "../../util/types";
import { StateProps } from "../../util/propTypes";
import "./Nobles.css"
export default function Nobles({ state, setState }: StateProps) {
const removeNoble = (noble: NobleData) => {
console.log(noble);
setState((prev) => {
return {
...prev,
gameboard: {
...prev.gameboard,
nobles: prev.gameboard.nobles.filter((each) => each.nobleid !== noble.nobleid)
}
}
})
export default function Nobles({ state }: StateProps) {
if (!state.gameboard.nobles.length) {
return (
<div className="nobles-panel">
<strong>NOBLES</strong>
<p>All nobles have been acquired!</p>
</div>
)
}
return (

View File

@@ -1,7 +1,16 @@
import { buyCard, getTotalBuyingPower, tooExpensive, updateResources } from './buyCardActions';
import { test, expect, describe } from 'vitest';
import { expensiveCard, midGameCardOne, midGameState, mockPlayerOne, mockPlayerTwo, mockState } from '../../../util/testUtils';
import { expensiveCard, midGameCardOne, midGameCardTwo, midGameState, mockPlayerOne, mockPlayerTwo, mockState } from '../../../util/testUtils';
import { buyCard, tooExpensive } from './buyCardActions';
import getTotalBuyingPower from '../../../util/getTotalBuyingPower';
import { useCurrentPlayer } from '../../../util/useCurrentPlayer';
import { AppState, CardData, PlayerData, ResourceCost } from '../../../util/types';
import { test, expect, describe, vi, afterEach } from 'vitest';
import { renderHook } from "@testing-library/react";
import React, { useState } from 'react';
import { turnOrderUtil } from '../../../util/turnOrderUtil';
afterEach(() => {
vi.restoreAllMocks();
})
describe('buy cards', () => {
test('detects unaffordable cards', () => {
@@ -29,7 +38,7 @@ describe('buy cards', () => {
]
}
const totalBuyingPower = getTotalBuyingPower(modifiedState);
const totalBuyingPower = getTotalBuyingPower(mockPlayerOne);
const expectedValue = {
ruby: 3,
@@ -42,21 +51,7 @@ describe('buy cards', () => {
expect(totalBuyingPower).toStrictEqual(expectedValue);
})
test('updateResources', () => {
const currentPlayer = useCurrentPlayer(midGameState);
if (!currentPlayer) return;
const { newTradingResources, updatedPlayer } = updateResources(midGameState, midGameCardOne);
expect(newTradingResources).toBeDefined();
expect(updatedPlayer).toBeDefined();
})
test('renders the correct inventory', () => {
const output = 1;
expect(output).toBe(1);
})
})
describe('get chips', () => {})
describe('reserve card', () => {})
// describe('get chips', () => {})
// describe('reserve card', () => {})

View File

@@ -1,40 +1,17 @@
import { initialActions } from "../../../util/stateSetters";
import { turnOrderUtil } from "../../../util/turnOrderUtil";
import { AppState, CardData, ResourceCost, setStateType } from "../../../util/types";
import { AppState, CardData, FullDeck, ResourceCost, setStateType } from "../../../util/types";
import { useCurrentPlayer } from "../../../util/useCurrentPlayer";
export const getTotalBuyingPower = (state: AppState) => {
const currentPlayer = useCurrentPlayer(state);
let totalBuyingPower = {
ruby: 0,
sapphire: 0,
emerald: 0,
diamond: 0,
onyx: 0,
gold: 0,
}
if (!currentPlayer) return totalBuyingPower;
for (let [key,quantity] of Object.entries(currentPlayer.inventory)) {
totalBuyingPower[key as keyof ResourceCost] += quantity;
}
for (let each of currentPlayer.cards) {
totalBuyingPower[each.gemValue as keyof ResourceCost] += 1;
}
return totalBuyingPower;
}
import getTotalBuyingPower from "../../../util/getTotalBuyingPower";
import { initialActions, setStateGetNoble } from "../../../util/stateSetters";
import { canPickUpNoble } from "../../../util/canPickUpNoble";
export const tooExpensive = (card: CardData, state: AppState): boolean => {
const currentPlayer = useCurrentPlayer(state);
if (!currentPlayer) return true;
for (let [gemType, cost] of Object.entries(card.resourceCost)) {
let totalBuyingPower = getTotalBuyingPower(state);
for (let [cardGemType, cardCost] of Object.entries(card.resourceCost)) {
let totalBuyingPower = getTotalBuyingPower(currentPlayer);
for (let [heldResource, quantity] of Object.entries(totalBuyingPower)) {
if (gemType === heldResource && quantity < cost) {
if (cardGemType === heldResource && quantity < cardCost) {
return true;
}
}
@@ -43,45 +20,85 @@ export const tooExpensive = (card: CardData, state: AppState): boolean => {
return false;
}
export const updateResources = (state: AppState, card: CardData) => {
let currentPlayer = useCurrentPlayer(state);
let newTradingResources = state.gameboard.tradingResources;
let updatedPlayer = currentPlayer;
const totalBuyingPower = getTotalBuyingPower(state);
let difference = 0;
for (let [key, value] of Object.entries(card.resourceCost)) {
if (value < 1) continue;
if (value !== totalBuyingPower[key as keyof ResourceCost]) {
difference += Math.abs(totalBuyingPower[key as keyof ResourceCost] - value);
}
}
return { newTradingResources, updatedPlayer }
}
export const buyCard = (state: AppState, setState: setStateType, card: CardData) => {
let currentPlayer = useCurrentPlayer(state);
const currentPlayer = useCurrentPlayer(state);
if (!currentPlayer) return;
const { newPlayers, roundIncrement } = turnOrderUtil(state, currentPlayer);
setState((prev: AppState) => {
if (!currentPlayer) return prev;
const { newTradingResources, updatedPlayer } = updateResources(state, card);
setState((prev) => {
// shift turn order and identify current player in new player state
const { newPlayers, roundIncrement } = turnOrderUtil(prev, currentPlayer);
const idx = newPlayers.indexOf(currentPlayer);
updatedPlayer && (newPlayers[idx] = updatedPlayer);
const updatedPlayer = newPlayers[idx];
// pointers for each value to be modified
const cardCost = card.resourceCost;
const playerBuyingPower = getTotalBuyingPower(currentPlayer);
const newPlayerInventory = updatedPlayer.inventory;
const newResourcePool = prev.gameboard.tradingResources;
for (let key of Object.keys(cardCost)) {
const typedKey = key as keyof ResourceCost;
let adjustedCost = cardCost[typedKey];
let adjustedInventoryValue = newPlayerInventory[typedKey];
let adjustedResourcePoolValue = newResourcePool[typedKey] || 0;
if (!adjustedCost || !adjustedInventoryValue) continue;
// before decrementing player inventory values, account for total buying power
const buyingPowerDifference = playerBuyingPower[typedKey] - adjustedInventoryValue;
adjustedCost -= buyingPowerDifference;
while (adjustedCost > 0) {
adjustedInventoryValue--;
adjustedCost--;
adjustedResourcePoolValue++;
}
// assign modified values to player inventory and resource pool
newPlayerInventory[typedKey] = adjustedInventoryValue;
newResourcePool[typedKey] = adjustedResourcePoolValue;
}
// connect modified player state to updated list of all players
updatedPlayer.inventory = newPlayerInventory;
updatedPlayer.cards = [...updatedPlayer.cards, card];
updatedPlayer.points = updatedPlayer.points + (card.points || 0);
newPlayers[idx] = updatedPlayer;
// attempt to queue replacement card from full deck
const typedCardTier = ["tierThree", "tierTwo", "tierOne"][2 - (card.tier-1)] as keyof FullDeck;
let newFullDeckTargetTier = prev.gameboard.deck[typedCardTier];
const replacementCard = newFullDeckTargetTier.shift();
// isolate the affected row of face up cards, remove the purchased card
let newTargetCardRow = prev.gameboard.cardRows[typedCardTier];
newTargetCardRow = newTargetCardRow.filter((data: CardData) => data.resourceCost !== card.resourceCost);
// push replacement card to face up card, if exists
if (replacementCard) newTargetCardRow.push(replacementCard);
return {
...prev,
players: newPlayers,
round: (roundIncrement ? prev.round + 1 : prev.round),
actions: initialActions,
gameboard: {
...prev.gameboard,
cardRows: prev.gameboard.cardRows,
tradingResources: newTradingResources
tradingResources: newResourcePool,
cardRows: {
...prev.gameboard.cardRows,
[typedCardTier]: newTargetCardRow
},
round: (roundIncrement ? prev.round + 1 : prev.round),
players: newPlayers,
actions: initialActions
deck: {
...prev.gameboard.deck,
[typedCardTier]: newFullDeckTargetTier
}
}
}
});
for (let each of state.gameboard.nobles) {
if (canPickUpNoble(currentPlayer, each)) {
console.log(`${currentPlayer.name} can pick up noble ${state.gameboard.nobles.indexOf(each)}`);
setState((prev) => setStateGetNoble(prev, each));
}
}
})
}

View File

@@ -0,0 +1,82 @@
import { describe, expect, it, test } from "vitest";
import { initialActions } from "../../../util/stateSetters";
import { mockPlayerOne, mockState } from "../../../util/testUtils";
import { AppState, PlayerData } from "../../../util/types";
import { hasMaxChips, validateChips } from "./getChipsActions";
const getChipsState: AppState = {
...mockState,
actions: {
...initialActions,
getChips: {
active: true,
selection: []
}
}
}
describe('get chips', () => {
test('hasMaxChips', () => {
const illegalPlayer: PlayerData = {
...mockPlayerOne,
inventory: {
ruby: 2,
sapphire: 2,
emerald: 2,
diamond: 2,
onyx: 2,
gold: 2
}
}
expect(hasMaxChips(illegalPlayer)).toBeTruthy();
})
describe('validateChips', () => {
test('is falsy when chips action is not active', () => {
expect(validateChips(mockState)).toBeFalsy();
})
test('is falsy when more than three chips selected', () => {
const illegalState = getChipsState;
illegalState.actions.getChips.selection = ['ruby', 'diamond', 'onyx', 'sapphire']
expect(validateChips(illegalState)).toBeFalsy();
})
test('proper handling of duplicates', () => {
const illegalState = getChipsState;
illegalState.actions.getChips.selection = ['ruby', 'ruby', 'ruby']
const legalState = getChipsState;
legalState.actions.getChips.selection = ['ruby', 'ruby']
expect(validateChips(illegalState)).toBeFalsy();
expect(validateChips(legalState)).toBeTruthy();
})
test('no pickup of unavailable resources', () => {
const illegalState = {
...mockState,
gameboard: {
...getChipsState.gameboard,
tradingResources: {
ruby: 4,
sapphire: 4,
emerald: 1,
diamond: 4,
onyx: 2,
gold: 4
}
},
actions: {
...initialActions,
}
}
})
})
describe('getChips', () => {
})
})

View File

@@ -1,87 +0,0 @@
import cardTierToKey from "../../../util/cardTierToKey";
import { initialActions } from "../../../util/stateSetters";
import { turnOrderUtil } from "../../../util/turnOrderUtil";
import { AppState, CardData, PlayerData, ResourceCost, setStateType } from "../../../util/types";
import { useCurrentPlayer } from "../../../util/useCurrentPlayer";
import { getTotalBuyingPower } from "./buyCardActions";
export const buyCard = (card: CardData, state: AppState, setState: setStateType) => {
/**
* functionality: adds target card's data to current player's collection of cards,
* removes the card from the active state of the gameboard, replaces it with
* a new card in the correct tier, and runs turn order utility
*
* @param card -> the target card, @param state -> current app state
*/
let currentPlayer = useCurrentPlayer(state);
setState((prev: AppState) => {
if (!currentPlayer) return prev;
const { newPlayers, roundIncrement } = turnOrderUtil(prev, currentPlayer);
let newPlayerInventory = currentPlayer.inventory;
let newResourcePool = prev.gameboard.tradingResources;
const totalBuyingPower = getTotalBuyingPower(state);
// iterate through cost values of card to purchase
for (let [gem, cost] of Object.entries(card.resourceCost)) {
if (cost < 1) continue;
let inventoryValue = newPlayerInventory[gem as keyof ResourceCost];
let globalResource = newResourcePool[gem as keyof ResourceCost];
if (!inventoryValue || !globalResource) {
continue;
} else {
let i = cost;
// prevents duplication of resources when purchasing a card using permanent resources from cards
if (totalBuyingPower[gem as keyof ResourceCost] !== inventoryValue) {
console.log('caught');
}
while (i > 0) {
inventoryValue -= 1;
globalResource += 1;
i--;
}
newResourcePool[gem as keyof ResourceCost] = globalResource;
newPlayerInventory[gem as keyof ResourceCost] = inventoryValue;
}
}
let updatedPlayer: PlayerData = {
...currentPlayer,
cards: [...currentPlayer.cards, card],
inventory: newPlayerInventory
}
let newScore = 0;
for (let each of updatedPlayer.cards) {
newScore += each.points || 0;
}
updatedPlayer.points = newScore;
const idx = newPlayers.findIndex((one: PlayerData) => one.id === currentPlayer?.id);
newPlayers[idx] = updatedPlayer;
let updatedRows = prev.gameboard.cardRows;
if (card.tier) {
const tierKey = cardTierToKey(card.tier);
updatedRows[tierKey] = prev.gameboard.cardRows[tierKey].filter((found: CardData) => found.resourceCost !== card.resourceCost);
}
return {
...prev,
round: (roundIncrement ? prev.round + 1 : prev.round),
players: newPlayers,
gameboard: {
...prev.gameboard,
tradingResources: prev.gameboard.tradingResources,
cardRows: updatedRows
},
actions: initialActions
}
})
}

View File

@@ -31,9 +31,7 @@ export const reserveCard = (state: AppState, setState: setStateType, card: CardD
const updatedPlayer = {
...currentPlayer,
reservedCards: currentPlayer.reservedCards ? [
...currentPlayer.reservedCards, card
] : [card],
reservedCards: currentPlayer.reservedCards ? [...currentPlayer.reservedCards, card] : [card],
inventory: goldAllowable(currentPlayer) ? {
...currentPlayer.inventory,
gold: currentPlayer.inventory.gold && currentPlayer.inventory.gold + 1

View File

@@ -1,36 +1,77 @@
import { setStateAwaitAction, setStateBuyCard, setStateGetChips, setStateReserveCard } from "../../util/stateSetters";
import { useEffect, useState } from "react";
import { PlayerProps } from "../../util/propTypes";
import { CardData, PlayerData } from "../../util/types"
import { useEffect, useState } from "react";
import { v4 } from "uuid";
import { hasMaxReserved } from "./ActionMethods/reserveCardActions";
import { hasMaxChips } from "./ActionMethods/getChipsActions";
import { setStateAwaitAction, setStateBuyCard, setStateGetChips, setStateReserveCard } from "../../util/stateSetters";
import { v4 } from "uuid";
export default function Player({ player, state, setState }: PlayerProps) {
const [dynamic, setDynamic] = useState<PlayerData>();
const [prompt, setPrompt] = useState("Your turn! Select an action type below.");
const [cardView, setCardView] = useState(<p>Cards:</p>);
const [reservedView, setReservedView] = useState(<p>Reserved cards:</p>);
useEffect(() => {
setDynamic(state.players.find((element: PlayerData) => element.id === player.id))
}, [state]);
useEffect(() => {
dynamic && setCardView(
<>
<p>Cards:</p>
{
dynamic.cards.map((data: CardData) => {
return (
<div key={v4()} className="mini-card" style={{backgroundColor: 'white'}}>
<p>{data.gemValue} card</p>
<p>{data.points + " points" || null}</p>
{
Object.entries(data.resourceCost).map(([key, value]) => {
return value > 0 && <p key={v4()}>{key}: {value}</p>
})
}
</div>
)
})
}
</>
)
dynamic && setReservedView(
<>
<p>Reserved cards:</p>
{
dynamic.reservedCards?.map((data: CardData) => {
return (
<div key={v4()} className="mini-card" style={{backgroundColor: 'white'}}>
<p>{data.gemValue} cards</p>
<p>{data.points + " points" || null}</p>
{
Object.entries(data.resourceCost).map(([key, value]) => {
return value > 0 && <p key={v4()}>{key}: {value}</p>
})
}
</div>
)
})
}
</>
)
}, [dynamic, setState])
const handleClick = (actionSelection: number) => {
switch (actionSelection) {
case 0:
setState((prev) => setStateGetChips(prev));
setPrompt('Make your selection of up to three chips.');
break;
case 1:
setState((prev) => setStateBuyCard(prev));
setPrompt('Choose a card above to purchase.');
break;
case 2:
setState((prev) => setStateReserveCard(prev));
setPrompt('Choose a card above to reserve.');
break;
default:
setState((prev) => setStateAwaitAction(prev));
setPrompt("Your turn! Select an action type below.");
break;
}
}
@@ -45,8 +86,8 @@ export default function Player({ player, state, setState }: PlayerProps) {
{/* Dynamic data from state */}
<section className="turn-and-action-based">
<p>Score: {dynamic?.points}</p>
<p>{dynamic?.turnActive ? prompt : '...'}</p>
<p>Score: {dynamic && dynamic.points}</p>
<p>{dynamic?.turnActive ? "It's your turn!" : "..."}</p>
<button disabled={dynamic && hasMaxChips(dynamic)} onClick={() => handleClick(0)}>Get Chips</button>
<button onClick={() => handleClick(1)}>Buy Card</button>
<button disabled={dynamic && hasMaxReserved(dynamic)} onClick={() => handleClick(2)}>Reserve Card</button>
@@ -63,27 +104,11 @@ export default function Player({ player, state, setState }: PlayerProps) {
</div>
<div className="player-cards">
<p>Cards:</p>
{ dynamic && dynamic.cards.length > 0 && dynamic.cards.map((data: CardData) => {
return (
<div key={v4()} className="mini-card" style={{backgroundColor: 'white'}}>
<p>{data.gemValue} card</p>
<p>{data.points + " points" || null}</p>
</div>
)})
}
{dynamic && cardView}
</div>
<div className="reserved-cards">
<p>Reserved cards:</p>
{ dynamic?.reservedCards && dynamic.reservedCards?.map((data: CardData) => {
return (
<div key={v4()} className="mini-card" style={{backgroundColor: 'white'}}>
<p>{data.gemValue} cards</p>
<p>{data.points + " points" || null}</p>
</div>
)
})}
{dynamic && reservedView}
</div>
</section>
</div>

View File

@@ -1,8 +1,11 @@
import { useEffect, useState } from "react";
import { StateProps } from "../../util/propTypes";
import { useCurrentPlayer } from "../../util/useCurrentPlayer";
import { GetChipsHTML, ReserveCardHTML } from "./ViewHTML";
export default function SelectionView({ state, setState }: StateProps) {
const [currentPlayer, setCurrentPlayer] = useState(useCurrentPlayer(state));
const actionTypes = [
state.actions.getChips,
state.actions.buyCard,
@@ -16,14 +19,25 @@ export default function SelectionView({ state, setState }: StateProps) {
case (actionTypes[0].active):
return <GetChipsHTML state={state} setState={setState} />
case (actionTypes[1].active):
return <strong>Please make your selection above:</strong>;
return (
<div className="selection-view">
<h2>{currentPlayer?.name} has elected to purchase a card!</h2>
<strong>Choose a card above to purchase.</strong>
</div>
)
case (actionTypes[2].active):
return <ReserveCardHTML state={state} setState={setState} />;
default:
return <></>;
return (
<div className="selection-view">
<h2>{currentPlayer ? `It is currently ${currentPlayer.name}'s turn!` : "Loading..."}</h2>
</div>
);
}
})
}, [state])
setCurrentPlayer(useCurrentPlayer(state));
}, [state, state.actions, setState])
return view
}

View File

@@ -10,6 +10,7 @@ const { getChips } = getChipsActions;
export const GetChipsHTML = ({ state, setState }: StateProps) => {
const [prompt, setPrompt] = useState("");
const currentPlayer = useCurrentPlayer(state);
useEffect(() => {
if (!state.actions.getChips.active) setPrompt("");
@@ -22,6 +23,7 @@ export const GetChipsHTML = ({ state, setState }: StateProps) => {
return (
<div className="selection-view">
<h2>{currentPlayer?.name} has elected to collect resources!</h2>
<strong>{prompt}</strong>
<div className="current-selections">
{
@@ -49,6 +51,7 @@ export const ReserveCardHTML = ({ state, setState }: StateProps) => {
return (
<div className="selection-view">
<h2>{currentPlayer?.name} has elected to reserve a card!</h2>
<strong>Please make your selection above.</strong>
{ !hasMaxChips(currentPlayer) && (
<div className="take-gold">

View File

@@ -0,0 +1,12 @@
import { Link } from "react-router-dom";
import { StateProps } from "../util/propTypes";
export default function ResumeGame({ state, setState }: StateProps) {
return (
<div className="resume-game App">
<h2>Congrats! You've found an in-progress feature.</h2>
<p>Check back in here later to enter a save game token to pick up a game from where you left off.</p>
<p>In the meantime, click <Link to="/">here</Link> to head back home.</p>
</div>
)
}

View File

@@ -0,0 +1,22 @@
import getTotalBuyingPower from "./getTotalBuyingPower";
import { NobleData, PlayerData, ResourceCost } from "./types";
export const canPickUpNoble = (player: PlayerData, noble: NobleData) => {
const totalBuyingPower = getTotalBuyingPower(player);
const playerInventory = player.inventory;
for (let key of Object.keys(totalBuyingPower)) {
const typedKey = key as keyof ResourceCost;
let coinValue = playerInventory[typedKey] || 0;
if (!noble.resourceCost[typedKey]) continue;
// @ts-ignore
if ((totalBuyingPower[typedKey] - coinValue) >= noble.resourceCost[typedKey]) {
continue;
} else {
return;
}
}
return noble;
}

View File

@@ -0,0 +1,24 @@
import { PlayerData, ResourceCost } from "./types";
export default function getTotalBuyingPower(currentPlayer: PlayerData) {
let totalBuyingPower = {
ruby: 0,
sapphire: 0,
emerald: 0,
diamond: 0,
onyx: 0,
gold: 0,
}
if (!currentPlayer) return totalBuyingPower;
for (let [key,quantity] of Object.entries(currentPlayer.inventory)) {
totalBuyingPower[key as keyof ResourceCost] += quantity;
}
for (let each of currentPlayer.cards) {
totalBuyingPower[each.gemValue as keyof ResourceCost] += 1;
}
return totalBuyingPower;
}

View File

@@ -33,7 +33,6 @@ const setNobles = (state: AppState, setState: setStateType) => {
const setResources = (state: AppState) => {
let newResources = state.gameboard.tradingResources;
console.log(state.players.length);
switch (state.players.length) {
case 2:
for (let [key, value] of Object.entries(newResources)) {

View File

@@ -1,5 +1,6 @@
import { AppState, CardData, NobleData, PlayerData, ResourceCost } from "./types";
import CardDeck from '../data/cards.json';
import { useCurrentPlayer } from "./useCurrentPlayer";
export const initialActions = {
buyCard: { active: false },
@@ -77,3 +78,29 @@ export const setStateReserveCard = (prev: AppState) => {
}
}
}
export const setStateGetNoble = (prev: AppState, noble: NobleData) => {
const currentPlayer = useCurrentPlayer(prev);
if (!currentPlayer) return prev;
const updatedPlayer = {
...currentPlayer,
nobles: [...currentPlayer.nobles, noble],
points: currentPlayer.points + 3
}
const idx = prev.players.indexOf(currentPlayer);
const newPlayers = prev.players;
newPlayers[idx] = updatedPlayer;
const newNobles = prev.gameboard.nobles.filter((x: NobleData) => x.resourceCost !== noble.resourceCost);
return {
...prev,
players: newPlayers,
gameboard: {
...prev.gameboard,
nobles: newNobles
}
}
}

View File

@@ -44,6 +44,7 @@ export const mockState: AppState = {
}
// mock data for midgame
// playerOneMidGame buys high diamond cost card
export const playerOneMidGame = {
...mockPlayerOne,
cards: [
@@ -83,6 +84,7 @@ export const playerOneMidGame = {
}
}
// playerTwoMidGame buys high ruby cost card
export const playerTwoMidGame = {
...mockPlayerTwo,
cards: [
@@ -126,7 +128,7 @@ export const midGameState: AppState = {
gameboard: {
...initialState.gameboard,
tradingResources: {
ruby: 1,
ruby: 0,
sapphire: 1,
emerald: 1,
onyx: 1,