Rework 080522 #2
1425
package-lock.json
generated
1425
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -17,10 +17,12 @@
|
|||||||
"uuid": "^8.3.2"
|
"uuid": "^8.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@testing-library/react": "^13.3.0",
|
||||||
"@types/react": "^18.0.15",
|
"@types/react": "^18.0.15",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"@vitejs/plugin-react": "^2.0.0",
|
"@vitejs/plugin-react": "^2.0.0",
|
||||||
|
"jsdom": "^20.0.0",
|
||||||
"typescript": "^4.6.4",
|
"typescript": "^4.6.4",
|
||||||
"vite": "^3.0.0",
|
"vite": "^3.0.0",
|
||||||
"vitest": "^0.20.2"
|
"vitest": "^0.20.2"
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { BrowserRouter, Routes, Route } from 'react-router-dom'
|
import { BrowserRouter, Routes, Route } from 'react-router-dom'
|
||||||
import { initialState } from './util/stateSetters';
|
import { initialState } from './util/stateSetters';
|
||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import Gameboard from './components/Gameboard/Gameboard'
|
import Gameboard from './components/Gameboard/Gameboard'
|
||||||
import GameConstructor from './components/GameConstructor';
|
import GameConstructor from './components/GameConstructor';
|
||||||
import './App.css'
|
import './App.css'
|
||||||
|
import ResumeGame from './components/ResumeGame';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [state, setState] = useState(initialState);
|
const [state, setState] = useState(initialState);
|
||||||
@@ -14,9 +15,11 @@ function App() {
|
|||||||
<h1>SPLENDOR</h1>
|
<h1>SPLENDOR</h1>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
{/* @ts-ignore */}a
|
{/* @ts-ignore */}
|
||||||
<Route path="/" element={<GameConstructor state={state} setState={setState} />} />
|
<Route path="/" element={<GameConstructor state={state} setState={setState} />} />
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
|
<Route path="/resume-game" element={<ResumeGame state={state} setState={setState} /> } />
|
||||||
|
{/* @ts-ignore */}
|
||||||
<Route path="/game" element={<Gameboard state={state} setState={setState} />} />
|
<Route path="/game" element={<Gameboard state={state} setState={setState} />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ const { buyCard, tooExpensive } = buyCardActions;
|
|||||||
export default function Card({ data, state, setState }: CardProps) {
|
export default function Card({ data, state, setState }: CardProps) {
|
||||||
const currentPlayer = useCurrentPlayer(state);
|
const currentPlayer = useCurrentPlayer(state);
|
||||||
|
|
||||||
|
if (!data) return <div className="card"></div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`card`}>
|
<div className="card">
|
||||||
<div className="top-row">
|
<div className="top-row">
|
||||||
<p>Counts as: {data.gemValue}</p>
|
<p>Counts as: {data.gemValue}</p>
|
||||||
<p>Point value: {data.points || 0}</p>
|
<p>Point value: {data.points || 0}</p>
|
||||||
@@ -23,7 +25,7 @@ export default function Card({ data, state, setState }: CardProps) {
|
|||||||
}
|
}
|
||||||
{ state.actions.buyCard.active &&
|
{ state.actions.buyCard.active &&
|
||||||
<button
|
<button
|
||||||
onClick={() => buyCard(data, state, setState)}
|
onClick={() => buyCard(state, setState, data)}
|
||||||
disabled={tooExpensive(data, state)}>
|
disabled={tooExpensive(data, state)}>
|
||||||
Buy This Card
|
Buy This Card
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ import { CardRowProps } from '../../util/propTypes';
|
|||||||
import { CardData } from "../../util/types"
|
import { CardData } from "../../util/types"
|
||||||
import Card from "../Card/Card"
|
import Card from "../Card/Card"
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
import cardTierToKey from '../../util/cardTierToKey';
|
||||||
|
|
||||||
export default function CardRow({tier, state, setState}: CardRowProps) {
|
export default function CardRow({tier, state, setState}: CardRowProps) {
|
||||||
|
const typedTier = cardTierToKey(tier);
|
||||||
|
|
||||||
let cards: Array<CardData>
|
let cards: Array<CardData>
|
||||||
switch (tier) {
|
switch (tier) {
|
||||||
case 1:
|
case 1:
|
||||||
@@ -24,6 +27,9 @@ export default function CardRow({tier, state, setState}: CardRowProps) {
|
|||||||
<div className={`card-row tier-${tier}`}>
|
<div className={`card-row tier-${tier}`}>
|
||||||
<p>Tier: {tier}</p>
|
<p>Tier: {tier}</p>
|
||||||
<div className="card-row-cards-visible">
|
<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) => {
|
{ cards && cards.map((cardData: CardData) => {
|
||||||
return <Card key={v4()} data={cardData} state={state} setState={setState} />
|
return <Card key={v4()} data={cardData} state={state} setState={setState} />
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from "react"
|
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 { CardData, NobleData, PlayerData } from "../util/types";
|
||||||
import { StateProps } from '../util/propTypes';
|
import { StateProps } from '../util/propTypes';
|
||||||
|
|
||||||
@@ -103,7 +103,9 @@ export default function GameConstructor({ state, setState }: StateProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="game-constructor App">
|
<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>
|
<div>
|
||||||
<label htmlFor="P1-NAME">Player 1 Name:</label>
|
<label htmlFor="P1-NAME">Player 1 Name:</label>
|
||||||
@@ -162,7 +164,7 @@ export default function GameConstructor({ state, setState }: StateProps) {
|
|||||||
</input>
|
</input>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
id="P1-START"
|
id="P4-START"
|
||||||
onChange={() => handleRadio(4)}
|
onChange={() => handleRadio(4)}
|
||||||
checked={starter === 3 && input.playerFour.name.length > 0}>
|
checked={starter === 3 && input.playerFour.name.length > 0}>
|
||||||
</input>
|
</input>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { AppState, ResourceCost } from '../../util/types';
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { getChipsActions } from '../Player/ActionMethods';
|
import { getChipsActions } from '../Player/ActionMethods';
|
||||||
import { StateProps } from '../../util/propTypes';
|
import { StateProps } from '../../util/propTypes';
|
||||||
const { validateChips } = getChipsActions;
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
// components
|
// components
|
||||||
import Nobles from './Nobles';
|
import Nobles from './Nobles';
|
||||||
@@ -12,6 +12,7 @@ import AvailableChips from '../Resources/AvailableChips';
|
|||||||
import AllPlayers from '../Player/AllPlayers';
|
import AllPlayers from '../Player/AllPlayers';
|
||||||
import CardRow from '../Card/CardRow';
|
import CardRow from '../Card/CardRow';
|
||||||
import SelectionView from '../Resources/SelectionView';
|
import SelectionView from '../Resources/SelectionView';
|
||||||
|
const { validateChips } = getChipsActions;
|
||||||
|
|
||||||
export default function Gameboard({ state, setState }: StateProps) {
|
export default function Gameboard({ state, setState }: StateProps) {
|
||||||
const [view, setView] = useState(<p>Loading...</p>);
|
const [view, setView] = useState(<p>Loading...</p>);
|
||||||
@@ -19,7 +20,6 @@ export default function Gameboard({ state, setState }: StateProps) {
|
|||||||
// callbacks for lifting state
|
// callbacks for lifting state
|
||||||
const liftSelection = useCallback((value: keyof ResourceCost) => {
|
const liftSelection = useCallback((value: keyof ResourceCost) => {
|
||||||
if (!state.actions.getChips.active) return;
|
if (!state.actions.getChips.active) return;
|
||||||
|
|
||||||
setState((prev: AppState) => {
|
setState((prev: AppState) => {
|
||||||
let newSelection = prev.actions.getChips.selection;
|
let newSelection = prev.actions.getChips.selection;
|
||||||
newSelection?.push(value);
|
newSelection?.push(value);
|
||||||
@@ -38,7 +38,6 @@ export default function Gameboard({ state, setState }: StateProps) {
|
|||||||
|
|
||||||
const result = validateChips(newState);
|
const result = validateChips(newState);
|
||||||
newState.actions.getChips.valid = result;
|
newState.actions.getChips.valid = result;
|
||||||
|
|
||||||
return newState;
|
return newState;
|
||||||
})
|
})
|
||||||
}, [state]);
|
}, [state]);
|
||||||
@@ -50,15 +49,21 @@ export default function Gameboard({ state, setState }: StateProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCardRows(state);
|
setCardRows(state);
|
||||||
|
|
||||||
|
for (let player of state.players) {
|
||||||
|
if (player.points >= 15) {
|
||||||
|
console.log('trigger endgame');
|
||||||
|
}
|
||||||
|
}
|
||||||
}, [state])
|
}, [state])
|
||||||
|
|
||||||
// displays state of board if data is populated
|
// displays state of board if data is populated, otherwise points to game constructor
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!state.players.length) {
|
if (!state.players.length) {
|
||||||
setView(
|
setView(
|
||||||
<div className="error-page">
|
<div className="error-page">
|
||||||
<strong>Sorry! It appears we've lost track of your game data.</strong>
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
144
src/components/Gameboard/Nobles.test.ts
Normal file
144
src/components/Gameboard/Nobles.test.ts
Normal 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', () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,21 +1,16 @@
|
|||||||
import { useEffect } from "react";
|
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
import { NobleData, ResourceCost } from "../../util/types";
|
import { NobleData, ResourceCost } from "../../util/types";
|
||||||
import { StateProps } from "../../util/propTypes";
|
import { StateProps } from "../../util/propTypes";
|
||||||
import "./Nobles.css"
|
import "./Nobles.css"
|
||||||
|
|
||||||
export default function Nobles({ state, setState }: StateProps) {
|
export default function Nobles({ state }: StateProps) {
|
||||||
const removeNoble = (noble: NobleData) => {
|
if (!state.gameboard.nobles.length) {
|
||||||
console.log(noble);
|
return (
|
||||||
setState((prev) => {
|
<div className="nobles-panel">
|
||||||
return {
|
<strong>NOBLES</strong>
|
||||||
...prev,
|
<p>All nobles have been acquired!</p>
|
||||||
gameboard: {
|
</div>
|
||||||
...prev.gameboard,
|
)
|
||||||
nobles: prev.gameboard.nobles.filter((each) => each.nobleid !== noble.nobleid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import { buyCard, getTotalBuyingPower, tooExpensive, updateResources } from './buyCardActions';
|
import { expensiveCard, midGameCardOne, midGameCardTwo, midGameState, mockPlayerOne, mockPlayerTwo, mockState } from '../../../util/testUtils';
|
||||||
import { test, expect, describe } from 'vitest';
|
import { buyCard, tooExpensive } from './buyCardActions';
|
||||||
import { expensiveCard, midGameCardOne, midGameState, mockPlayerOne, mockPlayerTwo, mockState } from '../../../util/testUtils';
|
import getTotalBuyingPower from '../../../util/getTotalBuyingPower';
|
||||||
import { useCurrentPlayer } from '../../../util/useCurrentPlayer';
|
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', () => {
|
describe('buy cards', () => {
|
||||||
test('detects unaffordable cards', () => {
|
test('detects unaffordable cards', () => {
|
||||||
@@ -29,7 +38,7 @@ describe('buy cards', () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalBuyingPower = getTotalBuyingPower(modifiedState);
|
const totalBuyingPower = getTotalBuyingPower(mockPlayerOne);
|
||||||
|
|
||||||
const expectedValue = {
|
const expectedValue = {
|
||||||
ruby: 3,
|
ruby: 3,
|
||||||
@@ -42,21 +51,7 @@ describe('buy cards', () => {
|
|||||||
|
|
||||||
expect(totalBuyingPower).toStrictEqual(expectedValue);
|
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('get chips', () => {})
|
||||||
describe('reserve card', () => {})
|
// describe('reserve card', () => {})
|
||||||
@@ -1,40 +1,17 @@
|
|||||||
import { initialActions } from "../../../util/stateSetters";
|
|
||||||
import { turnOrderUtil } from "../../../util/turnOrderUtil";
|
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";
|
import { useCurrentPlayer } from "../../../util/useCurrentPlayer";
|
||||||
|
import getTotalBuyingPower from "../../../util/getTotalBuyingPower";
|
||||||
export const getTotalBuyingPower = (state: AppState) => {
|
import { initialActions, setStateGetNoble } from "../../../util/stateSetters";
|
||||||
const currentPlayer = useCurrentPlayer(state);
|
import { canPickUpNoble } from "../../../util/canPickUpNoble";
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const tooExpensive = (card: CardData, state: AppState): boolean => {
|
export const tooExpensive = (card: CardData, state: AppState): boolean => {
|
||||||
const currentPlayer = useCurrentPlayer(state);
|
const currentPlayer = useCurrentPlayer(state);
|
||||||
if (!currentPlayer) return true;
|
if (!currentPlayer) return true;
|
||||||
for (let [gemType, cost] of Object.entries(card.resourceCost)) {
|
for (let [cardGemType, cardCost] of Object.entries(card.resourceCost)) {
|
||||||
let totalBuyingPower = getTotalBuyingPower(state);
|
let totalBuyingPower = getTotalBuyingPower(currentPlayer);
|
||||||
for (let [heldResource, quantity] of Object.entries(totalBuyingPower)) {
|
for (let [heldResource, quantity] of Object.entries(totalBuyingPower)) {
|
||||||
if (gemType === heldResource && quantity < cost) {
|
if (cardGemType === heldResource && quantity < cardCost) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,45 +20,85 @@ export const tooExpensive = (card: CardData, state: AppState): boolean => {
|
|||||||
return false;
|
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) => {
|
export const buyCard = (state: AppState, setState: setStateType, card: CardData) => {
|
||||||
let currentPlayer = useCurrentPlayer(state);
|
const currentPlayer = useCurrentPlayer(state);
|
||||||
if (!currentPlayer) return;
|
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);
|
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 {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
|
players: newPlayers,
|
||||||
|
round: (roundIncrement ? prev.round + 1 : prev.round),
|
||||||
|
actions: initialActions,
|
||||||
gameboard: {
|
gameboard: {
|
||||||
...prev.gameboard,
|
...prev.gameboard,
|
||||||
cardRows: prev.gameboard.cardRows,
|
tradingResources: newResourcePool,
|
||||||
tradingResources: newTradingResources
|
cardRows: {
|
||||||
},
|
...prev.gameboard.cardRows,
|
||||||
round: (roundIncrement ? prev.round + 1 : prev.round),
|
[typedCardTier]: newTargetCardRow
|
||||||
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
82
src/components/Player/ActionMethods/getChips.test.ts
Normal file
82
src/components/Player/ActionMethods/getChips.test.ts
Normal 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', () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -31,9 +31,7 @@ export const reserveCard = (state: AppState, setState: setStateType, card: CardD
|
|||||||
|
|
||||||
const updatedPlayer = {
|
const updatedPlayer = {
|
||||||
...currentPlayer,
|
...currentPlayer,
|
||||||
reservedCards: currentPlayer.reservedCards ? [
|
reservedCards: currentPlayer.reservedCards ? [...currentPlayer.reservedCards, card] : [card],
|
||||||
...currentPlayer.reservedCards, card
|
|
||||||
] : [card],
|
|
||||||
inventory: goldAllowable(currentPlayer) ? {
|
inventory: goldAllowable(currentPlayer) ? {
|
||||||
...currentPlayer.inventory,
|
...currentPlayer.inventory,
|
||||||
gold: currentPlayer.inventory.gold && currentPlayer.inventory.gold + 1
|
gold: currentPlayer.inventory.gold && currentPlayer.inventory.gold + 1
|
||||||
|
|||||||
@@ -1,36 +1,77 @@
|
|||||||
|
import { setStateAwaitAction, setStateBuyCard, setStateGetChips, setStateReserveCard } from "../../util/stateSetters";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import { PlayerProps } from "../../util/propTypes";
|
import { PlayerProps } from "../../util/propTypes";
|
||||||
import { CardData, PlayerData } from "../../util/types"
|
import { CardData, PlayerData } from "../../util/types"
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { v4 } from "uuid";
|
|
||||||
import { hasMaxReserved } from "./ActionMethods/reserveCardActions";
|
import { hasMaxReserved } from "./ActionMethods/reserveCardActions";
|
||||||
import { hasMaxChips } from "./ActionMethods/getChipsActions";
|
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) {
|
export default function Player({ player, state, setState }: PlayerProps) {
|
||||||
const [dynamic, setDynamic] = useState<PlayerData>();
|
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(() => {
|
useEffect(() => {
|
||||||
setDynamic(state.players.find((element: PlayerData) => element.id === player.id))
|
setDynamic(state.players.find((element: PlayerData) => element.id === player.id))
|
||||||
}, [state]);
|
}, [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) => {
|
const handleClick = (actionSelection: number) => {
|
||||||
switch (actionSelection) {
|
switch (actionSelection) {
|
||||||
case 0:
|
case 0:
|
||||||
setState((prev) => setStateGetChips(prev));
|
setState((prev) => setStateGetChips(prev));
|
||||||
setPrompt('Make your selection of up to three chips.');
|
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
setState((prev) => setStateBuyCard(prev));
|
setState((prev) => setStateBuyCard(prev));
|
||||||
setPrompt('Choose a card above to purchase.');
|
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
setState((prev) => setStateReserveCard(prev));
|
setState((prev) => setStateReserveCard(prev));
|
||||||
setPrompt('Choose a card above to reserve.');
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
setState((prev) => setStateAwaitAction(prev));
|
setState((prev) => setStateAwaitAction(prev));
|
||||||
setPrompt("Your turn! Select an action type below.");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,8 +86,8 @@ export default function Player({ player, state, setState }: PlayerProps) {
|
|||||||
|
|
||||||
{/* Dynamic data from state */}
|
{/* Dynamic data from state */}
|
||||||
<section className="turn-and-action-based">
|
<section className="turn-and-action-based">
|
||||||
<p>Score: {dynamic?.points}</p>
|
<p>Score: {dynamic && dynamic.points}</p>
|
||||||
<p>{dynamic?.turnActive ? prompt : '...'}</p>
|
<p>{dynamic?.turnActive ? "It's your turn!" : "..."}</p>
|
||||||
<button disabled={dynamic && hasMaxChips(dynamic)} onClick={() => handleClick(0)}>Get Chips</button>
|
<button disabled={dynamic && hasMaxChips(dynamic)} onClick={() => handleClick(0)}>Get Chips</button>
|
||||||
<button onClick={() => handleClick(1)}>Buy Card</button>
|
<button onClick={() => handleClick(1)}>Buy Card</button>
|
||||||
<button disabled={dynamic && hasMaxReserved(dynamic)} onClick={() => handleClick(2)}>Reserve 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>
|
||||||
|
|
||||||
<div className="player-cards">
|
<div className="player-cards">
|
||||||
<p>Cards:</p>
|
{dynamic && cardView}
|
||||||
{ 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>
|
|
||||||
)})
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="reserved-cards">
|
<div className="reserved-cards">
|
||||||
<p>Reserved cards:</p>
|
{dynamic && reservedView}
|
||||||
{ 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>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { StateProps } from "../../util/propTypes";
|
import { StateProps } from "../../util/propTypes";
|
||||||
|
import { useCurrentPlayer } from "../../util/useCurrentPlayer";
|
||||||
import { GetChipsHTML, ReserveCardHTML } from "./ViewHTML";
|
import { GetChipsHTML, ReserveCardHTML } from "./ViewHTML";
|
||||||
|
|
||||||
export default function SelectionView({ state, setState }: StateProps) {
|
export default function SelectionView({ state, setState }: StateProps) {
|
||||||
|
const [currentPlayer, setCurrentPlayer] = useState(useCurrentPlayer(state));
|
||||||
|
|
||||||
const actionTypes = [
|
const actionTypes = [
|
||||||
state.actions.getChips,
|
state.actions.getChips,
|
||||||
state.actions.buyCard,
|
state.actions.buyCard,
|
||||||
@@ -16,14 +19,25 @@ export default function SelectionView({ state, setState }: StateProps) {
|
|||||||
case (actionTypes[0].active):
|
case (actionTypes[0].active):
|
||||||
return <GetChipsHTML state={state} setState={setState} />
|
return <GetChipsHTML state={state} setState={setState} />
|
||||||
case (actionTypes[1].active):
|
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):
|
case (actionTypes[2].active):
|
||||||
return <ReserveCardHTML state={state} setState={setState} />;
|
return <ReserveCardHTML state={state} setState={setState} />;
|
||||||
default:
|
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
|
return view
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,7 @@ const { getChips } = getChipsActions;
|
|||||||
|
|
||||||
export const GetChipsHTML = ({ state, setState }: StateProps) => {
|
export const GetChipsHTML = ({ state, setState }: StateProps) => {
|
||||||
const [prompt, setPrompt] = useState("");
|
const [prompt, setPrompt] = useState("");
|
||||||
|
const currentPlayer = useCurrentPlayer(state);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!state.actions.getChips.active) setPrompt("");
|
if (!state.actions.getChips.active) setPrompt("");
|
||||||
@@ -22,6 +23,7 @@ export const GetChipsHTML = ({ state, setState }: StateProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="selection-view">
|
<div className="selection-view">
|
||||||
|
<h2>{currentPlayer?.name} has elected to collect resources!</h2>
|
||||||
<strong>{prompt}</strong>
|
<strong>{prompt}</strong>
|
||||||
<div className="current-selections">
|
<div className="current-selections">
|
||||||
{
|
{
|
||||||
@@ -49,6 +51,7 @@ export const ReserveCardHTML = ({ state, setState }: StateProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="selection-view">
|
<div className="selection-view">
|
||||||
|
<h2>{currentPlayer?.name} has elected to reserve a card!</h2>
|
||||||
<strong>Please make your selection above.</strong>
|
<strong>Please make your selection above.</strong>
|
||||||
{ !hasMaxChips(currentPlayer) && (
|
{ !hasMaxChips(currentPlayer) && (
|
||||||
<div className="take-gold">
|
<div className="take-gold">
|
||||||
|
|||||||
12
src/components/ResumeGame.tsx
Normal file
12
src/components/ResumeGame.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
22
src/util/canPickUpNoble.ts
Normal file
22
src/util/canPickUpNoble.ts
Normal 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;
|
||||||
|
}
|
||||||
24
src/util/getTotalBuyingPower.ts
Normal file
24
src/util/getTotalBuyingPower.ts
Normal 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;
|
||||||
|
}
|
||||||
@@ -33,7 +33,6 @@ const setNobles = (state: AppState, setState: setStateType) => {
|
|||||||
const setResources = (state: AppState) => {
|
const setResources = (state: AppState) => {
|
||||||
let newResources = state.gameboard.tradingResources;
|
let newResources = state.gameboard.tradingResources;
|
||||||
|
|
||||||
console.log(state.players.length);
|
|
||||||
switch (state.players.length) {
|
switch (state.players.length) {
|
||||||
case 2:
|
case 2:
|
||||||
for (let [key, value] of Object.entries(newResources)) {
|
for (let [key, value] of Object.entries(newResources)) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { AppState, CardData, NobleData, PlayerData, ResourceCost } from "./types";
|
import { AppState, CardData, NobleData, PlayerData, ResourceCost } from "./types";
|
||||||
import CardDeck from '../data/cards.json';
|
import CardDeck from '../data/cards.json';
|
||||||
|
import { useCurrentPlayer } from "./useCurrentPlayer";
|
||||||
|
|
||||||
export const initialActions = {
|
export const initialActions = {
|
||||||
buyCard: { active: false },
|
buyCard: { active: false },
|
||||||
@@ -76,4 +77,30 @@ 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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -44,6 +44,7 @@ export const mockState: AppState = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mock data for midgame
|
// mock data for midgame
|
||||||
|
// playerOneMidGame buys high diamond cost card
|
||||||
export const playerOneMidGame = {
|
export const playerOneMidGame = {
|
||||||
...mockPlayerOne,
|
...mockPlayerOne,
|
||||||
cards: [
|
cards: [
|
||||||
@@ -83,6 +84,7 @@ export const playerOneMidGame = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// playerTwoMidGame buys high ruby cost card
|
||||||
export const playerTwoMidGame = {
|
export const playerTwoMidGame = {
|
||||||
...mockPlayerTwo,
|
...mockPlayerTwo,
|
||||||
cards: [
|
cards: [
|
||||||
@@ -126,7 +128,7 @@ export const midGameState: AppState = {
|
|||||||
gameboard: {
|
gameboard: {
|
||||||
...initialState.gameboard,
|
...initialState.gameboard,
|
||||||
tradingResources: {
|
tradingResources: {
|
||||||
ruby: 1,
|
ruby: 0,
|
||||||
sapphire: 1,
|
sapphire: 1,
|
||||||
emerald: 1,
|
emerald: 1,
|
||||||
onyx: 1,
|
onyx: 1,
|
||||||
|
|||||||
Reference in New Issue
Block a user