32 Commits

Author SHA1 Message Date
Mikayla Dobson
1e24bd0d6c fixed bug in sass, may backtrack to modify player ui 2022-09-20 12:06:35 -05:00
e82d12b10e unusual behavior with sass 2022-09-11 14:07:37 -05:00
71fec57a94 defined helper for evaluating ui collapse state, working to implement alternate styles 2022-09-11 13:21:09 -05:00
6ac83ef008 to do: debugging for handling ui collapse states 2022-09-10 19:55:35 -05:00
c887e5ad74 incorporating callback to lift ui collapse state 2022-09-10 16:31:54 -05:00
5a7830340a optimized image rendering, tweaks to game ui 2022-09-10 15:58:29 -05:00
7505c3316d in progress: enable collapse for gameboard components 2022-08-27 16:13:54 -05:00
cd187bf595 in progress: major changes to gameboard styling 2022-08-27 15:57:09 -05:00
8be8933446 various changes to reserved card handling in player ui 2022-08-27 14:53:02 -05:00
Mikayla Dobson
a9a6b83500 streamlined gameboard view, proportion adjustments 2022-08-26 16:58:51 -05:00
Mikayla Dobson
25f2bb5837 various ui improvements; card photo, row collapse support 2022-08-26 16:21:11 -05:00
38dd1519fa updates to card json. cards render photos 2022-08-25 16:52:28 -05:00
75d161a10b beginning to define standards for chip design 2022-08-25 13:21:35 -05:00
e987a2aad8 introducing logic for color sorting of chips 2022-08-25 13:04:57 -05:00
4cfc41374e css migrated to scss, beginning to refactor 2022-08-25 12:09:42 -05:00
3156029ae0 beginning ui rework, integrate sass 2022-08-25 12:05:20 -05:00
134ab9a284 endgame logic; bug fix in gold chip for reserved cards 2022-08-19 13:35:23 -05:00
4d3f6bfeb4 patch for score calculations 2022-08-19 12:47:11 -05:00
8c6222864a allows player to take gold chip when reserving card 2022-08-17 12:10:07 -05:00
3cdc1a78c8 buy card appears to account for permanent resource upgrades 2022-08-17 11:44:45 -05:00
e922a73fa1 to do: differentiate between gold for purchase vs. held card for purchase 2022-08-16 12:48:05 -05:00
6914d44900 implemented purchase of a reserved card 2022-08-16 12:18:22 -05:00
40904d36d3 version maintenance 2022-08-16 11:31:22 -05:00
3234966629 merge conflict 2022-08-12 15:06:11 -05:00
15e32503e3 quick bug fix 2022-08-12 14:48:09 -05:00
fb38130c17 gold chips appear to function correctly? 2022-08-12 14:30:36 -05:00
2fba7b30fa recognizes gold chip requirement 2022-08-12 13:37:17 -05:00
74587d0a0e ui update: player card view shows number of each permanent resource upgrade 2022-08-12 11:19:37 -05:00
ab6a190cb5 in progress: gold chip functionality 2022-08-12 10:38:45 -05:00
6a40c0e592 tooExpensive gold chips test fails 2022-08-09 14:57:31 -05:00
35931eb8e5 implemented usePreviousPlayer, preparing to implement gold chip funcitonality 2022-08-09 14:27:59 -05:00
Mikayla Dobson
fd6d96737a Merge pull request #2 from innocuous-symmetry/rework-080522
Rework 080522
2022-08-06 19:28:44 -05:00
56 changed files with 4759 additions and 1633 deletions

2441
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,18 +10,22 @@
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-router-dom": "^6.3.0",
"scss": "^0.2.4",
"sass": "^1.54.5",
"uuid": "^8.3.2"
},
"devDependencies": {
"@testing-library/react": "^13.3.0",
"@types/enzyme": "^3.10.12",
"@types/enzyme-adapter-react-16": "^1.0.6",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/uuid": "^8.3.4",
"@vitejs/plugin-react": "^2.0.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.6",
"jsdom": "^20.0.0",
"typescript": "^4.6.4",
"vite": "^3.0.0",

View File

@@ -1,41 +0,0 @@
#root {
display: flex;
align-items: center;
justify-content: center;
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.card-row {
display: flex;
flex-flow: column nowrap;
margin: 2rem;
width: 80vw;
}
.card-row-cards-visible {
display: flex;
flex-flow: row nowrap;
width: 100%;
justify-content: space-around;
}
.card {
width: 25%;
}
.tier-1 {
background-color: green;
}
.tier-2 {
background-color: yellow;
color: black;
}
.tier-3 {
background-color: blue;
}

View File

@@ -1,11 +1,11 @@
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { initialState } from './util/stateSetters';
import { useEffect, useState } from 'react'
import { initialState } from './hooks/stateSetters';
import { useState } from 'react'
import Gameboard from './components/Gameboard/Gameboard'
import GameConstructor from './components/GameConstructor';
import './App.css'
import ResumeGame from './components/ResumeGame';
import "./sass/App.scss";
function App() {
const [state, setState] = useState(initialState);

View File

@@ -1,42 +1,64 @@
import { v4 } from 'uuid';
import { CardProps } from '../../util/propTypes';
import { ResourceCost } from '../../util/types';
import { useCurrentPlayer } from '../../util/useCurrentPlayer';
import { CardData, PlayerCards, ResourceCost } from '../../util/types';
import { useCurrentPlayer } from '../../hooks/useCurrentPlayer';
import { buyCardActions } from '../Player/ActionMethods';
import { hasMaxReserved, reserveCard } from '../Player/ActionMethods/reserveCardActions';
const { buyCard, tooExpensive } = buyCardActions;
export default function Card({ data, state, setState }: CardProps) {
export default function Card({ data, state, setState, reserved = false, collapsed = false }: CardProps) {
const currentPlayer = useCurrentPlayer(state);
if (!data || !currentPlayer) return <div className="card"></div>;
if (!data) return <div className="card"></div>;
const purchaseDisabled = (): boolean => {
// TO DO: check whether a card belongs to the current player,
// if card is tagged as reserved
return tooExpensive(data, state);
}
return (
<div className="card">
<div className="top-row">
<p>Counts as: {data.gemValue}</p>
<p>Point value: {data.points || 0}</p>
<p>Cost:</p>
<div className='card' key={v4()}>
{ /*
{ collapsed ? <div className={`img-placeholder-${data.gemValue}`}></div> : <img src={data.image} loading="lazy" /> }
*/ }
<div className={reserved ? `foreground-${data.gemValue}` : 'foreground'}>
<section className="card-top-section">
<p>{data.gemValue.toUpperCase()}</p>
<div className="total-card-cost">
{
Object.keys(data.resourceCost).map((key: keyof ResourceCost | string) => {
// @ts-ignore
return (data.resourceCost[key as keyof ResourceCost] > 0) && <p key={v4()}>{key}: {data.resourceCost[key as keyof ResourceCost]}</p>
return (data.resourceCost[key as keyof ResourceCost] > 0) && (
<p key={v4()} className={`card-cost-${key}`}>
{data.resourceCost[key as keyof ResourceCost]}
</p>
)
})
}
</div>
{ (data.points && data.points > 0) ? <p>{data.points} {data.points === 1 ? 'point' : 'points'}</p> : null }
</section>
{
(state.actions.buyCard.active || state.actions.reserveCard.active) && (
<section className="card-action-section">
{ state.actions.buyCard.active &&
<button
onClick={() => buyCard(state, setState, data)}
disabled={tooExpensive(data, state)}>
disabled={purchaseDisabled()}>
Buy This Card
</button>
}
{ state.actions.reserveCard.active &&
{ !reserved && state.actions.reserveCard.active &&
<button
onClick={() => reserveCard(state, setState, data)}
disabled={hasMaxReserved(currentPlayer)}>
Reserve This Card
</button>
}
</section>
)
}
</div>
</div>
)

View File

@@ -0,0 +1,91 @@
@import "../../sass/helper/mixins";
@import "../../sass/helper/variables";
.card-row {
display: flex;
flex-flow: column nowrap;
margin: 1rem 0;
width: 75vw;
.card-row-top-bar {
display: flex;
align-items: center;
justify-content: center;
> * {
margin: 8px 16px;
}
}
.card-row-cards-visible {
display: flex;
flex-flow: row wrap;
justify-content: right;
width: 100%;
.card {
width: 18%;
border: 2px solid black;
img {
width: 100%;
height: 100%;
object-fit: cover;
z-index: 1;
}
.foreground {
// position: relative;
// top: -100%;
display: flex;
flex-direction: column;
align-items: center;
z-index: 6;
> * {
margin: 1rem;
padding: 6px;
border-radius: 12px;
background-color: rgb(39, 36, 36);
}
.total-card-cost {
display: flex;
flex-flow: row wrap;
justify-content: center;
@include map-gem-values(".card-cost");
}
}
}
.card-count {
background-color: black;
color: white;
width: 25%;
}
.cc-tier {
&-1 {
background-color: rgb(3, 30, 3);
}
&-2 {
background-color: rgb(80, 80, 7);
}
&-3 {
background-color: rgb(9, 9, 77);
}
}
}
}
.tier-1 {
background-color: rgb(23, 73, 23);
}
.tier-2 {
background-color: rgb(174, 174, 32);
}
.tier-3 {
background-color: rgb(35, 35, 167);
}

View File

@@ -1,10 +1,13 @@
import { useEffect, useState } from 'react';
import { v4 } from 'uuid';
import cardTierToKey from '../../util/mechanics/cardTierToKey';
import { CardRowProps } from '../../util/propTypes';
import { CardData } from "../../util/types"
import Card from "../Card/Card"
import { v4 } from 'uuid';
import cardTierToKey from '../../util/cardTierToKey';
import "./CardRow.scss";
export default function CardRow({tier, state, setState}: CardRowProps) {
export default function CardRow({tier, state, setState, liftCollapsed}: CardRowProps) {
const [collapsed, setCollapsed] = useState(true);
const typedTier = cardTierToKey(tier);
let cards: Array<CardData>
@@ -23,15 +26,22 @@ export default function CardRow({tier, state, setState}: CardRowProps) {
break;
}
useEffect(() => {
liftCollapsed(collapsed, tier);
}, [collapsed])
return (
<div className={`card-row tier-${tier}`}>
<div className={`card-row tier-${tier} ${collapsed && 'collapsed'}`}>
<div className="card-row-top-bar">
<p>Tier: {tier}</p>
<div className="card-row-cards-visible">
<div className="card card-count">
<button onClick={() => setCollapsed(!collapsed)}>{collapsed ? "Show" : "Hide"}</button>
</div>
<div className={`card-row-cards-visible ${collapsed && 'hidden'}`}>
<div className={`card-count cc-tier-${tier}`}>
<p>Remaining: {state.gameboard.deck[typedTier].length}</p>
</div>
{ 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} collapsed={collapsed} />
})}
</div>
</div>

View File

@@ -65,7 +65,13 @@ export default function GameConstructor({ state, setState }: StateProps) {
turnActive: val.starter,
points: 0,
nobles: new Array<NobleData>,
cards: new Array<CardData>,
cards: {
ruby: new Array<CardData>,
sapphire: new Array<CardData>,
emerald: new Array<CardData>,
diamond: new Array<CardData>,
onyx: new Array<CardData>,
},
inventory: {
ruby: 0,
sapphire: 0,

View File

@@ -0,0 +1,47 @@
.gameboard {
display: flex;
flex-flow: column nowrap;
align-items: flex-start;
justify-content: center;
background-color: rgb(57, 57, 65);
width: 90vw;
padding: 0 18px 12px;
#round-marker {
padding-left: 8px;
}
.gameboard-columns {
display: flex;
flex-flow: row nowrap;
justify-content: center;
align-items: flex-start;
width: 100%;
section {
display: flex;
flex-flow: column nowrap;
}
.gameboard-left {
align-items: flex-start;
width: 50%;
}
.gameboard-right {
justify-content: flex-end;
width: 50%;
}
.gameboard-left-expanded {
align-items: flex-start;
width: 90%;
}
.gameboard-right-compact {
justify-content: flex-end;
width: 10%;
}
}
}

View File

@@ -1,63 +1,88 @@
// types, data, utils
import { AppState, ResourceCost } from '../../util/types';
import { useCallback, useEffect, useState } from 'react';
import { getChipsActions } from '../Player/ActionMethods';
import { StateProps } from '../../util/propTypes';
import { Link } from 'react-router-dom';
import initializeBoard, { setCardRows } from '../../util/setup/initializeBoard';
import { AppState, PlayerData, ResourceCost, UIState } from '../../util/types';
import { defaultUIState } from '../../util/setup/defaultUIState';
import { StateProps } from '../../util/propTypes';
import './Gameboard.scss';
// components
import Nobles from './Nobles';
import initializeBoard, { setCardRows } from '../../util/initializeBoard';
import AvailableChips from '../Resources/AvailableChips';
import Nobles from '../Nobles/Nobles';
import AllPlayers from '../Player/AllPlayers';
import CardRow from '../Card/CardRow';
import SelectionView from '../Resources/SelectionView';
const { validateChips } = getChipsActions;
import { useCurrentPlayer } from '../../hooks/useCurrentPlayer';
import usePreviousPlayer from '../../hooks/usePreviousPlayer';
import { shouldRightSideCollapse } from '../../util/mechanics/shouldRightSideCollapse';
import { setStateUpdateSelection } from '../../hooks/stateSetters';
export default function Gameboard({ state, setState }: StateProps) {
const [view, setView] = useState(<p>Loading...</p>);
const [endgame, setEndgame] = useState<PlayerData>();
const [UICollapse, setUICollapse] = useState<UIState>(defaultUIState);
// 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;
})
setState((prev: AppState) => setStateUpdateSelection(prev, value));
}, [state]);
const liftCollapsed = useCallback((collapsed: boolean, tier = 5) => {
setUICollapse((prev: UIState) => {
switch (tier) {
case 1:
return {
...prev,
tierOneCollapsed: collapsed
}
case 2:
return {
...prev,
tierTwoCollapsed: collapsed
}
case 3:
return {
...prev,
tierThreeCollapsed: collapsed
}
default:
return {
...prev,
noblesCollapsed: collapsed
}
}
});
}, [UICollapse, setUICollapse])
// util functions, setup on mount
useEffect(() => {
initializeBoard(state, setState);
}, [])
useEffect(() => initializeBoard(state, setState), [])
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, otherwise points to game constructor
// endgame logic: once triggered, sets "endgame" to the player who triggered the effect
useEffect(() => {
const previousPlayer = usePreviousPlayer(state);
if (previousPlayer && previousPlayer.points >= 15) setEndgame(previousPlayer);
}, [state])
// endgame logic: determines the player with highest score after remaining allowed turns
useEffect(() => {
if (endgame) {
let winner: PlayerData;
const winnerData = state.players;
const currentPlayer = useCurrentPlayer(state);
if (!currentPlayer) return;
if (currentPlayer.id <= endgame.id) {
winner = winnerData.sort((x,y) => x.points + y.points)[0];
console.log(winner.name + ' wins!');
}
}
}, [state, endgame])
// rendering: displays state of board if data is populated, otherwise points to game constructor
useEffect(() => {
if (!state.players.length) {
setView(
@@ -68,20 +93,25 @@ export default function Gameboard({ state, setState }: StateProps) {
);
} else {
setView(
<div className="gameboard-rows">
<strong>Round: {state.round}</strong>
<Nobles state={state} setState={setState} />
<CardRow tier={3} state={state} setState={setState} />
<CardRow tier={2} state={state} setState={setState} />
<CardRow tier={1} state={state} setState={setState} />
<SelectionView state={state} setState={setState} />
<AvailableChips state={state} setState={setState} liftSelection={liftSelection} />
<AllPlayers state={state} setState={setState} />
<div className="gameboard">
<h2 id="round-marker">Round: {state.round}</h2>
<div className="gameboard-columns">
<section className={shouldRightSideCollapse(UICollapse) ? "gameboard-left-expanded" : "gameboard-left"}>
<Nobles state={state} setState={setState} liftCollapsed={liftCollapsed} />
<CardRow tier={3} state={state} setState={setState} liftCollapsed={liftCollapsed} />
<CardRow tier={2} state={state} setState={setState} liftCollapsed={liftCollapsed} />
<CardRow tier={1} state={state} setState={setState} liftCollapsed={liftCollapsed} />
</section>
<section className={shouldRightSideCollapse(UICollapse) ? "gameboard-right-compact" : "gameboard-right"}>
<AllPlayers
state={state} setState={setState} liftSelection={liftSelection}
UICollapse={UICollapse} setUICollapse={setUICollapse} liftCollapsed={liftCollapsed} />
</section>
</div>
</div>
)
}
}, [state]);
}, [state, UICollapse]);
// render
return view
}

View File

@@ -1,18 +0,0 @@
.nobles-panel {
display: flex;
flex-flow: column nowrap;
background-color: rgb(240, 236, 225);
padding: 1.5rem;
color: black;
}
.all-nobles {
display: flex;
flex-flow: row nowrap;
justify-content: space-around;
}
.noble-card {
display: inline-flex;
flex-flow: column nowrap;
}

View File

@@ -1,39 +0,0 @@
import { v4 } from "uuid";
import { NobleData, ResourceCost } from "../../util/types";
import { StateProps } from "../../util/propTypes";
import "./Nobles.css"
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 (
<div className="nobles-panel">
<strong>NOBLES</strong>
<div className="all-nobles">
{
state && state.gameboard.nobles.map((noble: NobleData) => {
return (
<div className="noble-card" key={v4()}>
<p>Points: {noble.points}</p>
<p>Cost:</p>
{
Object.keys(noble.resourceCost).map((each) => {
// @ts-ignore
return (noble.resourceCost[each as keyof ResourceCost] > 0) && <p key={v4()}>{each}: {noble.resourceCost[each as keyof ResourceCost]}</p>
})
}
</div>
)
})
}
</div>
</div>
)
}

View File

@@ -1,6 +1,6 @@
import { expect, it, describe, vi } from 'vitest';
import initializeBoard from '../../util/initializeBoard';
import { initialState } from '../../util/stateSetters';
import initializeBoard from '../../util/setup/initializeBoard';
import { initialState } from '../../hooks/stateSetters';
import { AppState, setStateType } from '../../util/types';
describe('game config', () => {

View File

@@ -0,0 +1,44 @@
@import "../../sass/helper/mixins";
.nobles-panel {
display: flex;
flex-flow: column nowrap;
background-color: rgb(212, 196, 152);
color: black;
padding: 1rem 0;
width: 45vw;
.nobles-topbar {
display: flex;
flex-flow: row nowrap;
justify-content: center;
align-items: baseline;
> * {
margin: 0 1rem;
}
}
.all-nobles {
display: flex;
flex-flow: row nowrap;
justify-content: space-around;
.noble-card {
display: inline-flex;
flex-flow: column nowrap;
align-items: center;
height: 100%;
min-width: 25%;
border-right: 1px solid black;
&:last-child {
border-right: none;
}
.mapped-noble-costs {
display: flex;
@include map-gem-values(".noble-cost");
}
}
}
}

View File

@@ -0,0 +1,54 @@
import { v4 } from "uuid";
import { NobleData, ResourceCost } from "../../util/types";
import { NobleProps } from "../../util/propTypes";
import "../Nobles/Nobles.scss"
import { useEffect, useState } from "react";
export default function Nobles({ state, liftCollapsed }: NobleProps) {
const [collapsed, setCollapsed] = useState(true);
useEffect(() => {
liftCollapsed(collapsed);
}, [collapsed]);
if (!state.gameboard.nobles.length) {
return (
<div className="nobles-panel">
<strong>NOBLES</strong>
<p>All nobles have been acquired!</p>
</div>
)
}
return (
<div className={`nobles-panel ${collapsed && 'collapsed'}`}>
<div className="nobles-topbar">
<strong className="nobles-header">Nobles</strong>
<button onClick={() => setCollapsed(!collapsed)}>{collapsed ? "Show" : "Hide"}</button>
</div>
<div className={collapsed ? "hidden" : "all-nobles"}>
{
state && state.gameboard.nobles.map((noble: NobleData) => {
return (
<div className="noble-card" key={v4()}>
<p>Cost:</p>
<div className="mapped-noble-costs">
{
Object.keys(noble.resourceCost).map((each) => {
// @ts-ignore
return (noble.resourceCost[each as keyof ResourceCost] > 0) && (
<p key={v4()} className={`noble-cost-${each}`}>
{noble.resourceCost[each as keyof ResourceCost]}
</p>
)
})
}
</div>
</div>
)
})
}
</div>
</div>
)
}

View File

@@ -1,5 +1,5 @@
import getTotalBuyingPower from "./getTotalBuyingPower";
import { NobleData, PlayerData, ResourceCost } from "./types";
import getTotalBuyingPower from "../../util/mechanics/getTotalBuyingPower";
import { NobleData, PlayerData, ResourceCost } from "../../util/types";
export const canPickUpNoble = (player: PlayerData, noble: NobleData) => {
const totalBuyingPower = getTotalBuyingPower(player);

View File

@@ -1,57 +0,0 @@
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', () => {
const result = tooExpensive(expensiveCard, mockState);
expect(result).toBeTruthy();
})
test('calculates total buying power', () => {
let modifiedState = {
...mockState,
players: [
{
...mockPlayerOne,
inventory: {
ruby: 3,
sapphire: 3,
emerald: 3,
onyx: 3,
diamond: 3,
gold: 3
},
cards: [expensiveCard]
},
mockPlayerTwo
]
}
const totalBuyingPower = getTotalBuyingPower(mockPlayerOne);
const expectedValue = {
ruby: 3,
sapphire: 3,
emerald: 3,
onyx: 3,
diamond: 4,
gold: 3
}
expect(totalBuyingPower).toStrictEqual(expectedValue);
})
})
// describe('get chips', () => {})
// describe('reserve card', () => {})

View File

@@ -1,18 +1,34 @@
import { turnOrderUtil } from "../../../util/turnOrderUtil";
import { AppState, CardData, FullDeck, ResourceCost, setStateType } from "../../../util/types";
import { useCurrentPlayer } from "../../../util/useCurrentPlayer";
import getTotalBuyingPower from "../../../util/getTotalBuyingPower";
import { initialActions, setStateGetNoble } from "../../../util/stateSetters";
import { canPickUpNoble } from "../../../util/canPickUpNoble";
import { turnOrderUtil } from "../../../util/mechanics/TurnOrderUtil";
import getTotalBuyingPower from "../../../util/mechanics/getTotalBuyingPower";
import { AppState, CardData, PlayerCards, ResourceCost, setStateType } from "../../../util/types";
import cardTierToKey from "../../../util/mechanics/cardTierToKey";
import { canPickUpNoble } from "../../Nobles/canPickUpNoble";
import { initialActions, setStateGetNoble } from "../../../hooks/stateSetters";
import { useCurrentPlayer } from "../../../hooks/useCurrentPlayer";
import usePreviousPlayer from "../../../hooks/usePreviousPlayer";
export const tooExpensive = (card: CardData, state: AppState): boolean => {
const currentPlayer = useCurrentPlayer(state);
if (!currentPlayer) return true;
for (let [cardGemType, cardCost] of Object.entries(card.resourceCost)) {
let availableGold = currentPlayer.inventory.gold || 0;
// iterate through resource costs on card
for (let [cardResource, cardQuantity] of Object.entries(card.resourceCost)) {
let totalBuyingPower = getTotalBuyingPower(currentPlayer);
for (let [heldResource, quantity] of Object.entries(totalBuyingPower)) {
if (cardGemType === heldResource && quantity < cardCost) {
return true;
// iterate through each section of player's total buying power
for (let [heldResource, heldQuantity] of Object.entries(totalBuyingPower)) {
// match card resource to held quantity
if (cardResource === heldResource) {
let adjustedQuantity = heldQuantity;
// enter while loop if adjustedQuantity does not cover cost
while (adjustedQuantity < cardQuantity) {
// if there is no gold available to modify cost, card is too expensive
if (!availableGold) return true;
// else, add to the insufficient quantity and decrement available gold
adjustedQuantity++;
availableGold--;
}
}
}
}
@@ -28,52 +44,96 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData)
// shift turn order and identify current player in new player state
const { newPlayers, roundIncrement } = turnOrderUtil(prev, currentPlayer);
const idx = newPlayers.indexOf(currentPlayer);
const updatedPlayer = newPlayers[idx];
// pointers for each value to be modified
const cardCost = card.resourceCost;
const playerBuyingPower = getTotalBuyingPower(currentPlayer);
const newPlayerInventory = updatedPlayer.inventory;
// assign pointers
const newResourcePool = prev.gameboard.tradingResources;
const cardCost = card.resourceCost;
const updatedPlayer = newPlayers[idx];
let availableGold = updatedPlayer.inventory['gold'] || 0;
for (let key of Object.keys(cardCost)) {
// iterate and perform purchase logic
for (let key of Object.keys(updatedPlayer.inventory)) {
const typedKey = key as keyof ResourceCost;
let adjustedCost = cardCost[typedKey];
let adjustedInventoryValue = newPlayerInventory[typedKey];
let adjustedResourcePoolValue = newResourcePool[typedKey] || 0;
if (!adjustedCost || !adjustedInventoryValue) continue;
if (key === 'gold') continue;
if (cardCost[typedKey] === 0) continue;
// before decrementing player inventory values, account for total buying power
const buyingPowerDifference = playerBuyingPower[typedKey] - adjustedInventoryValue;
adjustedCost -= buyingPowerDifference;
// derived pointers for sections of state
let inventoryPointer = updatedPlayer.inventory[typedKey] || 0;
let resourcePoolPointer = newResourcePool[typedKey] || 0;
let cardCostPointer = cardCost[typedKey] || 0;
let heldCardPointer = updatedPlayer.cards[key as keyof PlayerCards].length || 0;
let goldToReturn = 0;
while (adjustedCost > 0) {
adjustedInventoryValue--;
adjustedCost--;
adjustedResourcePoolValue++;
// reduce required cost by number of permanent resources
while (heldCardPointer > 0) {
heldCardPointer--;
cardCostPointer--;
}
// assign modified values to player inventory and resource pool
newPlayerInventory[typedKey] = adjustedInventoryValue;
newResourcePool[typedKey] = adjustedResourcePoolValue;
// use available gold to account for difference between cardCost and inventory
while (cardCostPointer > inventoryPointer) {
if (availableGold > 0) {
availableGold--;
cardCostPointer--;
goldToReturn++;
} else {
break;
}
}
// 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;
// redistribute gold back into resource pool
while (goldToReturn > 0) {
updatedPlayer.inventory['gold'] && updatedPlayer.inventory['gold']--;
newResourcePool['gold'] && newResourcePool['gold']++;
goldToReturn--;
}
// attempt to queue replacement card from full deck
const typedCardTier = ["tierThree", "tierTwo", "tierOne"][2 - (card.tier-1)] as keyof FullDeck;
// complete remaining details of transaction
while (cardCostPointer > 0) {
cardCostPointer--;
inventoryPointer--;
resourcePoolPointer++;
}
// reassign to higher scope variables
newResourcePool[typedKey] = resourcePoolPointer;
updatedPlayer.inventory[typedKey] = inventoryPointer;
updatedPlayer.inventory['gold'] = availableGold;
}
// update player's held cards
updatedPlayer.cards = {
...updatedPlayer.cards,
[card.gemValue]: [...updatedPlayer.cards[card.gemValue as keyof PlayerCards], card]
}
let reservedCardCheck = false;
// checks if current card was bought from reserved cards, removing it if so
if (updatedPlayer.reservedCards) {
let beforeLength = updatedPlayer.reservedCards.length;
updatedPlayer.reservedCards = updatedPlayer.reservedCards.filter((each: CardData) => each.resourceCost !== card.resourceCost);
let afterLength = updatedPlayer.reservedCards.length;
if (beforeLength !== afterLength) reservedCardCheck = true;
}
let newScore = 0;
for (let each in updatedPlayer.cards) {
updatedPlayer.cards[each as keyof PlayerCards].forEach((value: CardData) => newScore += (value.points || 0));
// newScore += updatedPlayer.cards[each as keyof PlayerCards].length;
}
newScore += (updatedPlayer.nobles.length * 3)
updatedPlayer.points = newScore;
const typedCardTier = cardTierToKey(card.tier);
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];
if (!reservedCardCheck) {
const replacementCard = newFullDeckTargetTier.shift();
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,
@@ -95,10 +155,15 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData)
}
});
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));
console.log(state.players);
// iterates through gameboard nobles to determine if any can be acquired
for (let noble of state.gameboard.nobles) {
const previousPlayer = usePreviousPlayer(state);
if (!previousPlayer) return;
if (canPickUpNoble(previousPlayer, noble)) {
setState((prev) => setStateGetNoble(prev, noble, previousPlayer));
}
}
}

View File

@@ -1,8 +1,8 @@
import { AppState, PlayerData, setStateType } from '../../../util/types';
import { useCurrentPlayer } from '../../../util/useCurrentPlayer';
import { useCurrentPlayer } from '../../../hooks/useCurrentPlayer';
// @ts-ignore
import { turnOrderUtil } from '../../../util/turnOrderUtil';
import { initialActions } from "../../../util/stateSetters";
import { turnOrderUtil } from '../../../util/mechanics/TurnOrderUtil';
import { initialActions } from "../../../hooks/stateSetters";
export const hasMaxChips = (player: PlayerData | null): boolean => {
if (!player) return true;

View File

@@ -1,8 +1,8 @@
import cardTierToKey from "../../../util/cardTierToKey";
import { initialActions } from "../../../util/stateSetters";
import { turnOrderUtil } from "../../../util/turnOrderUtil";
import cardTierToKey from "../../../util/mechanics/cardTierToKey";
import { initialActions } from "../../../hooks/stateSetters";
import { turnOrderUtil } from "../../../util/mechanics/TurnOrderUtil";
import { AppState, CardData, FullDeck, PlayerData, setStateType } from "../../../util/types";
import { useCurrentPlayer } from "../../../util/useCurrentPlayer";
import { useCurrentPlayer } from "../../../hooks/useCurrentPlayer";
export const goldAllowable = (player: PlayerData | null): boolean => {
if (!player) return false;
@@ -29,13 +29,20 @@ export const reserveCard = (state: AppState, setState: setStateType, card: CardD
setState((prev: AppState) => {
const { newPlayers, roundIncrement } = turnOrderUtil(prev, currentPlayer);
const updatedPlayer = {
...currentPlayer,
reservedCards: currentPlayer.reservedCards ? [...currentPlayer.reservedCards, card] : [card],
inventory: goldAllowable(currentPlayer) ? {
const updatedPlayer = currentPlayer;
updatedPlayer.reservedCards = currentPlayer.reservedCards ? [
...currentPlayer.reservedCards, card
] : [card];
const newResources = prev.gameboard.tradingResources;
if (prev.actions.reserveCard.includeGold && newResources.gold) {
updatedPlayer.inventory = {
...currentPlayer.inventory,
gold: currentPlayer.inventory.gold && currentPlayer.inventory.gold + 1
} : currentPlayer.inventory
gold: currentPlayer.inventory.gold ? currentPlayer.inventory.gold + 1 : 1
}
newResources.gold = newResources.gold - 1;
}
const idx = newPlayers.indexOf(currentPlayer);

View File

@@ -0,0 +1,125 @@
import { describe, expect, it, test, vi } from "vitest"
import Adapter from 'enzyme-adapter-react-16';
import { shallow } from "enzyme"
import { initialState } from "../../../../hooks/stateSetters"
import { mockCardRows, mockPlayerOne, mockPlayerTwo, sampleTierOneCard } from "../../../../util/testUtils"
import { AppState, PlayerData, setStateType } from "../../../../util/types"
import { buyCard, tooExpensive } from "../buyCardActions"
import { configure } from "enzyme";
import Player from "../../Player"
configure({ adapter: new Adapter() });
const testPlayer: PlayerData = {
name: "Test Player",
id: 1,
starter: true,
turnActive: true,
points: 0,
cards: {
ruby: [],
sapphire: [],
emerald: [],
diamond: [],
onyx:[]
},
nobles: [],
inventory: {
ruby: 1,
sapphire: 1,
emerald: 1,
diamond: 1,
onyx: 1
}
}
describe("buy card methods", () => {
describe("tooExpensive", () => {
it('detects unaffordable cards', () => {
const sampleState: AppState = {
...initialState,
players: [testPlayer, mockPlayerTwo]
}
expect(tooExpensive(sampleTierOneCard, sampleState)).toBeTruthy();
})
it('accounts for gold chips', () => {
const goldPlayer: PlayerData = {
...testPlayer,
inventory: {
ruby: 1,
sapphire: 1,
emerald: 1,
diamond: 1,
onyx: 2,
gold: 1
}
}
const sampleState: AppState = {
...initialState,
players: [goldPlayer, mockPlayerTwo],
}
expect(tooExpensive(sampleTierOneCard, sampleState)).toBeFalsy();
})
})
describe('buyCard', () => {
const setState: setStateType = vi.fn();
test('renders Player component', () => {
const wrapper = shallow(<Player player={testPlayer} state={initialState} setState={setState} />)
const name = wrapper.contains("Test Player");
expect(name).toBeTruthy();
})
it('updates player inventory', () => {
// const updatedPlayerOne: PlayerData = {
// ...mockPlayerOne,
// inventory: {
// ...mockPlayerOne.inventory,
// onyx: 3
// }
// }
// const currentState: AppState = {
// ...initialState,
// players: [updatedPlayerOne, mockPlayerTwo],
// gameboard: {
// ...initialState.gameboard,
// cardRows: mockCardRows
// }
// }
// // shallow renders a new gameboard component which encapsulates current players
// const wrapper = shallow(<Gameboard state={currentState} setState={setState} />)
// initializeBoard(currentState, setState);
// expect(currentState.gameboard.cardRows.tierOne).toHaveLength(4);
// expect(currentState.gameboard.cardRows.tierOne).toContain(sampleTierOneCard);
// const playerUI = shallow((<Player player={updatedPlayerOne} state={currentState} setState={setState}></Player>))
// expect(playerUI.children()).toContain("Name: Player One");
})
it('spends a player\'s gold when necessary', () => {
})
it('updates gameboard resources', () => {
})
it('triggers player to pick up first possible noble', () => {
})
it('only allows one noble to be acquired at a time', () => {
expect(1).toBe(0);
})
})
})
export default {}

View File

@@ -1,8 +1,8 @@
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";
import { initialActions } from "../../../../hooks/stateSetters";
import { mockPlayerOne, mockState } from "../../../../util/testUtils";
import { AppState, PlayerData } from "../../../../util/types";
import { hasMaxChips, validateChips } from "../getChipsActions";
const getChipsState: AppState = {
...mockState,
@@ -75,8 +75,4 @@ describe('get chips', () => {
}
})
})
describe('getChips', () => {
})
})

View File

@@ -0,0 +1 @@
export default {}

View File

@@ -1,9 +0,0 @@
.all-players {
display: flex;
background-color: rgb(237, 213, 156);
color: black;
}
.all-players p {
margin: 1rem;
}

View File

@@ -0,0 +1,108 @@
@import '../../sass/helper/mixins';
@import '../../sass/helper/variables';
@import '../../sass/helper/placeholders';
// local placeholder
%all-players-base {
display: flex;
flex-flow: column nowrap;
justify-content: space-around;
align-items: center;
background-color: rgb(188, 176, 146);
color: black;
.player-ui {
.subheader {
font-weight: bold;
}
background-color:rgb(172, 149, 94);
margin: 1rem 0;
padding: 0 2rem;
}
.card {
width: 100%;
img {
display: none;
}
@each $gem in $gemlist {
@include map-gem-values('.foreground');
.foreground-#{$gem} {
display: flex;
flex-direction: column;
align-items: center;
padding: 8px;
p {
display: inline;
padding: 8px;
}
.total-card-cost {
display: flex;
flex-flow: row wrap;
justify-content: center;
@include map-gem-values(".card-cost");
}
}
}
}
}
.all-players {
@extend %all-players-base;
.selection-view {
.current-selections {
display: flex;
align-items: center;
justify-content: center;
@include map-gem-values(".selection-value");
p {
margin: 1rem;
padding: 1rem;
border-radius: 50%;
}
}
}
.player-ui {
.subheader {
font-weight: bold;
}
background-color:rgb(172, 149, 94);
margin: 1rem 0;
padding: 0 2rem;
.turn-and-action-based {
display: flex;
flex-flow: row nowrap;
}
.resources {
.player-chips-enum {
display: flex;
justify-content: center;
@include map-gem-values(".player-chip");
}
}
.reserved-card-view {
background-color: rgb(232, 224, 200);
.reserved-card-cost {
display: flex;
flex-flow: row wrap;
justify-content: center;
@include map-gem-values(".reserve-cost");
}
}
}
}
.all-players-mini {
@extend %all-players-base;
}

View File

@@ -1,15 +1,76 @@
// framework
import { useEffect, useMemo, useState } from "react";
import { v4 } from "uuid";
import Player from "./Player";
import { PlayerData } from "../../util/types";
import { StateProps } from "../../util/propTypes";
import "./AllPlayers.css"
export default function AllPlayers({ state, setState }: StateProps) {
const playerPool = state.players?.map((player: PlayerData) => <Player key={v4()} player={player} state={state} setState={setState} />);
// util
import { shouldRightSideCollapse } from "../../util/mechanics/shouldRightSideCollapse";
import { AllPlayersProps } from "../../util/propTypes";
import { useCurrentPlayer } from "../../hooks/useCurrentPlayer";
import { defaultUIState } from "../../util/setup/defaultUIState";
import { UIState } from "../../util/types";
// components
import AvailableChips from "../Resources/AvailableChips";
import SelectionView from "../Resources/SelectionView";
import Player from "./Player";
import "./AllPlayers.scss"
export default function AllPlayers({ state, setState, liftSelection, UICollapse, setUICollapse, liftCollapsed }: AllPlayersProps) {
const [playerView, setPlayerView] = useState<JSX.Element>();
const [collapseClass, setCollapseClass] = useState("all-players");
const collapseAll = () => {
liftCollapsed(true);
liftCollapsed(true, 3);
liftCollapsed(true, 2);
liftCollapsed(true, 1);
// let value = UICollapse[each as keyof UIState];
// if (each === "playerUICollapsed") {
// continue;
// } else if (each === "noblesCollapsed") {
// console.log(each);
// liftCollapsed(value);
// } else {
// console.log(each, value);
// switch (each) {
// case "tierThreeCollapsed":
// liftCollapsed(value, 3);
// break;
// case "tierTwoCollapsed":
// liftCollapsed(value, 2);
// break;
// case "tierOneCollapsed":
// liftCollapsed(value, 1);
// break;
// default: break;
// }
// }
}
const allowCollapseAll = useMemo(() => {
for (let each of Object.keys(UICollapse)) {
if (each === "playerUICollapsed") continue;
if (!UICollapse[each as keyof UIState]) return true;
}
return false;
}, [UICollapse]);
useEffect(() => {
const currentPlayer = useCurrentPlayer(state);
if (!currentPlayer) return;
setPlayerView(<Player key={v4()} player={currentPlayer} state={state} setState={setState} />);
}, [state]);
useEffect(() => {
setCollapseClass( shouldRightSideCollapse(UICollapse) ? "all-players-mini" : "all-players" );
}, [UICollapse]);
return (
<div className="all-players">
{ playerPool }
<div className={collapseClass}>
{ allowCollapseAll && <button onClick={collapseAll}>Collapse All</button> }
<SelectionView state={state} setState={setState} UICollapse={UICollapse} />
<AvailableChips state={state} setState={setState} liftSelection={liftSelection} />
{ playerView }
</div>
)
}

View File

@@ -1,10 +1,11 @@
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 { v4 } from "uuid";
import { setStateAwaitAction, setStateBuyCard, setStateGetChips, setStateReserveCard } from "../../hooks/stateSetters";
import { hasMaxReserved } from "./ActionMethods/reserveCardActions";
import { hasMaxChips } from "./ActionMethods/getChipsActions";
import { v4 } from "uuid";
import { CardData, PlayerData } from "../../util/types"
import { PlayerProps } from "../../util/propTypes";
import Card from "../Card/Card";
export default function Player({ player, state, setState }: PlayerProps) {
const [dynamic, setDynamic] = useState<PlayerData>();
@@ -17,46 +18,20 @@ export default function Player({ player, state, setState }: PlayerProps) {
useEffect(() => {
dynamic && setCardView(
<>
<div className="card-view">
<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>
})
Object.entries(dynamic.cards).map(([key, value]) => value.length > 0 && <p className={`mini-card ${key}`} key={v4()}>{key}: {value.length}</p>)
}
</div>
)
})
}
</>
)
dynamic && setReservedView(
<>
<div className="reserved-card-view">
<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>
})
}
{ dynamic.reservedCards?.map((data: CardData) => <Card key={v4()} data={data} state={state} setState={setState} reserved={true} />) }
</div>
)
})
}
</>
)
}, [dynamic, setState])
const handleClick = (actionSelection: number) => {
@@ -78,30 +53,40 @@ export default function Player({ player, state, setState }: PlayerProps) {
return (
<div className="player-ui" key={v4()}>
{/* Static Data */}
<section className="player-constants">
<p>Name: {player.name}</p>
<p>Is {player.starter || "not"} round starter</p>
</section>
{/* Dynamic data from state */}
<section className="turn-and-action-based">
<p className="subheader">Name: {player.name} {player.starter && "(round starter)"}</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>
<section className="turn-and-action-based">
<button
disabled={(dynamic && hasMaxChips(dynamic)) || (!dynamic?.turnActive)}
onClick={() => handleClick(0)}>
Get Chips
</button>
<button
disabled={!dynamic?.turnActive}
onClick={() => handleClick(1)}>
Buy Card
</button>
<button
disabled={(dynamic && hasMaxReserved(dynamic)) || (!dynamic?.turnActive)}
onClick={() => handleClick(2)}>
Reserve Card
</button>
</section>
<section className="resources">
<strong>{dynamic?.name}'s Resources</strong>
<div className="player-chips">
<p>Chips:</p>
<div className="player-chips-enum">
{ dynamic && Object.entries(dynamic.inventory).map(([key,value]) => {
return value > 0 && <p key={v4()}>{key}: {value}</p>
return value > 0 && (
<p key={v4()} className={`player-chip-${key}`}>{value}</p>
)
})}
</div>
</div>
<div className="player-cards">
{dynamic && cardView}

View File

@@ -1,10 +0,0 @@
.available-chips {
display: flex;
flex-flow: row nowrap;
background-color: rgb(236, 238, 186);
color: black;
}
.available-chips p {
margin: 1rem;
}

View File

@@ -0,0 +1,22 @@
@import "../../sass/helper/mixins";
@import "../../sass/helper/variables";
.available-chips {
display: flex;
flex-flow: row wrap;
align-items: center;
justify-content: center;
color: black;
margin: 1rem;
p {
margin: 1rem;
}
@include map-gem-values(".chips");
@each $gem in $gemlist {
.chips-#{$gem} {
border-radius: 50%;
}
}
}

View File

@@ -1,28 +1,26 @@
import { ResourceProps } from "../../util/propTypes";
import { ResourceCost } from "../../util/types";
import { useEffect } from "react";
import { v4 } from "uuid";
import "./AvailableChips.css"
export default function AvailableChips({ state, setState, liftSelection }: ResourceProps) {
useEffect(() => {
return;
}, [state])
import "./AvailableChips.scss"
export default function AvailableChips({ state, liftSelection }: ResourceProps) {
return (
<div className="available-chips">
<div className={state.actions.getChips.active ? 'available-chips' : 'hidden'}>
{
Object.keys(state.gameboard.tradingResources).map((key: string | keyof ResourceCost) => {
Object.keys(state.gameboard.tradingResources).map((key: string) => {
const typedKey = key as keyof ResourceCost;
return (
<div key={v4()} className={`chips-${key}`}>
<button
key={v4()}
value={key}
onClick={() => liftSelection(key as keyof ResourceCost)}
className={`chips-${key}`}
// @ts-ignore
disabled={state.gameboard.tradingResources[typedKey] <= 0}
onClick={() => liftSelection(typedKey)}
>
{key}: {state.gameboard.tradingResources[key as keyof ResourceCost]}
{state.gameboard.tradingResources[typedKey]}
</button>
</div>
)
})
}

View File

@@ -1,32 +1,34 @@
import { useEffect, useState } from "react";
import { StateProps } from "../../util/propTypes";
import { useCurrentPlayer } from "../../util/useCurrentPlayer";
import { SelectionProps } from "../../util/propTypes";
import { useCurrentPlayer } from "../../hooks/useCurrentPlayer";
import { GetChipsHTML, ReserveCardHTML } from "./ViewHTML";
import { shouldRightSideCollapse } from "../../util/mechanics/shouldRightSideCollapse";
export default function SelectionView({ state, setState }: StateProps) {
export default function SelectionView({ state, setState, UICollapse }: SelectionProps) {
const [currentPlayer, setCurrentPlayer] = useState(useCurrentPlayer(state));
const actionTypes = [
state.actions.getChips,
state.actions.buyCard,
state.actions.reserveCard
]
const [view, setView] = useState(<></>);
useEffect(() => {
setCurrentPlayer(useCurrentPlayer(state));
setView(() => {
switch (true) {
case (actionTypes[0].active):
return <GetChipsHTML state={state} setState={setState} />
return <GetChipsHTML state={state} setState={setState} UICollapse={UICollapse} />
case (actionTypes[1].active):
return (
<div className="selection-view">
<div className={shouldRightSideCollapse(UICollapse) ? "selection-view-mini" : "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} />;
return <ReserveCardHTML state={state} setState={setState} UICollapse={UICollapse} />;
default:
return (
<div className="selection-view">
@@ -35,9 +37,7 @@ export default function SelectionView({ state, setState }: StateProps) {
);
}
})
setCurrentPlayer(useCurrentPlayer(state));
}, [state, state.actions, setState])
}, [state, setState])
return view
}

View File

@@ -1,17 +1,23 @@
import { v4 } from "uuid";
import { useEffect, useState } from "react";
import { setStateGetChips } from "../../util/stateSetters";
import { StateProps } from "../../util/propTypes";
import { v4 } from "uuid";
import { setStateGetChips, setStateReserveCard, setStateReservePlusGold } from "../../hooks/stateSetters";
import { useCurrentPlayer } from "../../hooks/useCurrentPlayer";
import { shouldRightSideCollapse } from "../../util/mechanics/shouldRightSideCollapse";
import { SelectionProps } from "../../util/propTypes";
import { ResourceCost } from "../../util/types";
import { getChipsActions } from "../Player/ActionMethods";
import { useCurrentPlayer } from "../../util/useCurrentPlayer";
import { hasMaxChips } from "../Player/ActionMethods/getChipsActions";
const { getChips } = getChipsActions;
export const GetChipsHTML = ({ state, setState }: StateProps) => {
export const GetChipsHTML = ({ state, setState, UICollapse }: SelectionProps) => {
const [prompt, setPrompt] = useState("");
const [style, setStyle] = useState("");
const currentPlayer = useCurrentPlayer(state);
useEffect(() => {
setStyle(shouldRightSideCollapse(UICollapse) ? "selection-view-mini" : "selection-view");
}, [UICollapse]);
useEffect(() => {
if (!state.actions.getChips.active) setPrompt("");
if (state.actions.getChips.selection?.length === 0) {
@@ -22,13 +28,13 @@ export const GetChipsHTML = ({ state, setState }: StateProps) => {
}, [state])
return (
<div className="selection-view">
<div className={style}>
<h2>{currentPlayer?.name} has elected to collect resources!</h2>
<strong>{prompt}</strong>
<div className="current-selections">
{
state.actions.getChips.active &&
state.actions.getChips.selection?.map((each: keyof ResourceCost) => <p key={v4()}>{each}</p>)
state.actions.getChips.selection?.map((each: keyof ResourceCost) => <p className={`selection-value-${each}`} key={v4()}>{each[0].toUpperCase()}</p>)
}
</div>
{
@@ -41,16 +47,30 @@ export const GetChipsHTML = ({ state, setState }: StateProps) => {
)
}
export const ReserveCardHTML = ({ state, setState }: StateProps) => {
const [takeGold, setTakeGold] = useState("");
export const ReserveCardHTML = ({ state, setState, UICollapse }: SelectionProps) => {
const [takeGold, setTakeGold] = useState(false);
const [style, setStyle] = useState("");
const currentPlayer = useCurrentPlayer(state);
useEffect(() => {
setStyle(shouldRightSideCollapse(UICollapse) ? "selection-view-mini" : "selection-view");
}, [UICollapse]);
})
useEffect(() => {
switch (takeGold) {
case true:
setState((prev) => setStateReservePlusGold(prev));
break;
case false:
setState((prev) => setStateReserveCard(prev));
break;
default:
break;
}
}, [takeGold]);
return (
<div className="selection-view">
<div className={style}>
<h2>{currentPlayer?.name} has elected to reserve a card!</h2>
<strong>Please make your selection above.</strong>
{ !hasMaxChips(currentPlayer) && (
@@ -60,8 +80,8 @@ export const ReserveCardHTML = ({ state, setState }: StateProps) => {
<input
id="take-gold-yes"
value="Yes"
checked={takeGold === "Yes"}
onChange={() => setTakeGold("Yes")}
checked={takeGold}
onChange={() => setTakeGold(true)}
type="radio"
>
</input>
@@ -70,8 +90,8 @@ export const ReserveCardHTML = ({ state, setState }: StateProps) => {
<input
id="take-gold-no"
value="No"
checked={takeGold === "No"}
onChange={() => setTakeGold("No")}
checked={!takeGold}
onChange={() => setTakeGold(false)}
type="radio">
</input>
</div>

View File

@@ -10,7 +10,8 @@
},
"gemValue": "diamond",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/LILY-serafima-lazarenko-unsplash.jpg"
},
{
"resourceCost": {
@@ -22,7 +23,8 @@
},
"gemValue": "emerald",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/SUCCULENT-jacalyn-beales-unsplash.jpg"
},
{
"resourceCost": {
@@ -34,7 +36,8 @@
},
"gemValue": "onyx",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/VIOLET-tobias-mockenhaupt-unsplash.jpg"
},
{
"resourceCost": {
@@ -46,7 +49,8 @@
},
"gemValue": "sapphire",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/FORGET-yoksel-zok-unsplash.jpg"
},
{
"resourceCost": {
@@ -58,7 +62,8 @@
},
"gemValue": "ruby",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/ROSE-alexey-savchenko-unsplash.jpg"
},
{
"resourceCost": {
@@ -70,7 +75,8 @@
},
"gemValue": "onyx",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/VIOLET-artiom-vallat-unsplash.jpg"
},
{
"resourceCost": {
@@ -82,7 +88,8 @@
},
"gemValue": "onyx",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/VIOLET-yoksel-zok-unsplash.jpg"
},
{
"resourceCost": {
@@ -94,7 +101,8 @@
},
"gemValue": "emerald",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/SUCCULENT-annie-spratt-unsplash.jpg"
},
{
"resourceCost": {
@@ -106,7 +114,8 @@
},
"gemValue": "sapphire",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/FORGET-anna-rozwadowska-unsplash.jpg"
},
{
"resourceCost": {
@@ -118,7 +127,8 @@
},
"gemValue": "sapphire",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/FORGET-gemma-evans-unsplash.jpg"
},
{
"resourceCost": {
@@ -130,7 +140,8 @@
},
"gemValue": "diamond",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/LILY-evie-s-unsplash-2.jpg"
},
{
"resourceCost": {
@@ -142,7 +153,8 @@
},
"gemValue": "ruby",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/ROSE-arkadiy-unsplash.jpg"
},
{
"resourceCost": {
@@ -154,7 +166,8 @@
},
"gemValue": "ruby",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/ROSE-engin-akyurt-unsplash.jpg"
},
{
"resourceCost": {
@@ -166,7 +179,8 @@
},
"gemValue": "sapphire",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/FORGET-krzysztof-kowalik-unsplash.jpg"
},
{
"resourceCost": {
@@ -178,7 +192,8 @@
},
"gemValue": "diamond",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/LILY-evie-s-unsplash-2.jpg"
},
{
"resourceCost": {
@@ -190,7 +205,8 @@
},
"gemValue": "onyx",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/VIOLET-adrian-swancar-unsplash.jpg"
},
{
"resourceCost": {
@@ -202,7 +218,8 @@
},
"gemValue": "ruby",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/ROSE-engin-akyurt-unsplash.jpg"
},
{
"resourceCost": {
@@ -214,7 +231,8 @@
},
"gemValue": "ruby",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/ROSE-arkadiy-unsplash.jpg"
},
{
"resourceCost": {
@@ -226,7 +244,8 @@
},
"gemValue": "diamond",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/LILY-meghna-r-unsplash.jpg"
},
{
"resourceCost": {
@@ -238,7 +257,8 @@
},
"gemValue": "diamond",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/LILY-yi-duo-unsplash.jpg"
},
{
"resourceCost": {
@@ -250,7 +270,8 @@
},
"gemValue": "emerald",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/SUCCULENT-annie-spratt-unsplash.jpg"
},
{
"resourceCost": {
@@ -262,7 +283,8 @@
},
"gemValue": "emerald",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/SUCCULENT-annie-spratt-unsplash.jpg"
},
{
"resourceCost": {
@@ -274,7 +296,8 @@
},
"gemValue": "emerald",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/SUCCULENT-annie-spratt-unsplash.jpg"
},
{
"resourceCost": {
@@ -286,7 +309,8 @@
},
"gemValue": "onyx",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/VIOLET-nick-nice-unsplash.jpg"
},
{
"resourceCost": {
@@ -298,7 +322,8 @@
},
"gemValue": "sapphire",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/FORGET-anna-rozwadowska-unsplash.jpg"
},
{
"resourceCost": {
@@ -310,7 +335,8 @@
},
"gemValue": "onyx",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/VIOLET-yoksel-zok-unsplash.jpg"
},
{
"resourceCost": {
@@ -322,7 +348,8 @@
},
"gemValue": "diamond",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/LILY-yi-duo-unsplash.jpg"
},
{
"resourceCost": {
@@ -334,7 +361,8 @@
},
"gemValue": "emerald",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/SUCCULENT-jacalyn-beales-unsplash.jpg"
},
{
"resourceCost": {
@@ -346,7 +374,8 @@
},
"gemValue": "ruby",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/ROSE-ekrem-osmanoglu-unsplash.jpg"
},
{
"resourceCost": {
@@ -358,7 +387,8 @@
},
"gemValue": "sapphire",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/FORGET-krzysztof-kowalik-unsplash.jpg"
},
{
"resourceCost": {
@@ -370,7 +400,8 @@
},
"gemValue": "sapphire",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/FORGET-anna-rozwadowska-unsplash.jpg"
},
{
"resourceCost": {
@@ -382,7 +413,8 @@
},
"gemValue": "ruby",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/ROSE-engin-akyurt-unsplash.jpg"
},
{
"resourceCost": {
@@ -394,7 +426,8 @@
},
"gemValue": "onyx",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/VIOLET-rita-ox-unsplash.jpg"
},
{
"resourceCost": {
@@ -406,7 +439,8 @@
},
"gemValue": "emerald",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/SUCCULENT-tim-mossholder-unsplash.jpg"
},
{
"resourceCost": {
@@ -418,7 +452,8 @@
},
"gemValue": "diamond",
"points": 0,
"tier": 1
"tier": 1,
"image": "src/assets/img/LILY-evie-s-unsplash-2.jpg"
},
{
"resourceCost": {
@@ -430,7 +465,8 @@
},
"gemValue": "sapphire",
"points": 1,
"tier": 1
"tier": 1,
"image": "src/assets/img/FORGET-gemma-evans-unsplash.jpg"
},
{
"resourceCost": {
@@ -442,7 +478,8 @@
},
"gemValue": "ruby",
"points": 1,
"tier": 1
"tier": 1,
"image": "src/assets/img/ROSE-edward-howell-unsplash.jpg"
},
{
"resourceCost": {
@@ -454,7 +491,8 @@
},
"gemValue": "onyx",
"points": 1,
"tier": 1
"tier": 1,
"image": "src/assets/img/VIOLET-rita-ox-unsplash.jpg"
},
{
"resourceCost": {
@@ -466,7 +504,8 @@
},
"gemValue": "diamond",
"points": 1,
"tier": 1
"tier": 1,
"image": "src/assets/img/LILY-evie-s-unsplash-2.jpg"
},
{
"resourceCost": {
@@ -478,7 +517,8 @@
},
"gemValue": "emerald",
"points": 1,
"tier": 1
"tier": 1,
"image": "src/assets/img/SUCCULENT-annie-spratt-unsplash.jpg"
}
],
"tierTwo": [
@@ -492,7 +532,8 @@
},
"gemValue": "ruby",
"points": 2,
"tier": 2
"tier": 2,
"image": "src/assets/img/ROSE-aleza-van-der-werff-unsplash.jpg"
},
{
"resourceCost": {
@@ -504,7 +545,8 @@
},
"gemValue": "onyx",
"points": 2,
"tier": 2
"tier": 2,
"image": "src/assets/img/VIOLET-adrian-swancar-unsplash.jpg"
},
{
"resourceCost": {
@@ -516,7 +558,8 @@
},
"gemValue": "emerald",
"points": 1,
"tier": 2
"tier": 2,
"image": "src/assets/img/SUCCULENT-tim-mossholder-unsplash.jpg"
},
{
"resourceCost": {
@@ -528,7 +571,8 @@
},
"gemValue": "onyx",
"points": 2,
"tier": 2
"tier": 2,
"image": "src/assets/img/VIOLET-nick-nice-unsplash.jpg"
},
{
"resourceCost": {
@@ -540,7 +584,8 @@
},
"gemValue": "emerald",
"points": 2,
"tier": 2
"tier": 2,
"image": "src/assets/img/SUCCULENT-angele-kamp-unsplash.jpg"
},
{
"resourceCost": {
@@ -552,7 +597,8 @@
},
"gemValue": "ruby",
"points": 2,
"tier": 2
"tier": 2,
"image": "src/assets/img/ROSE-engin-akyurt-unsplash.jpg"
},
{
"resourceCost": {
@@ -564,7 +610,8 @@
},
"gemValue": "diamond",
"points": 2,
"tier": 2
"tier": 2,
"image": "src/assets/img/LILY-meghna-r-unsplash.jpg"
},
{
"resourceCost": {
@@ -576,7 +623,8 @@
},
"gemValue": "emerald",
"points": 2,
"tier": 2
"tier": 2,
"image": "src/assets/img/SUCCULENT-tim-mossholder-unsplash.jpg"
},
{
"resourceCost": {
@@ -588,7 +636,8 @@
},
"gemValue": "diamond",
"points": 2,
"tier": 2
"tier": 2,
"image": "src/assets/img/LILY-serafima-lazarenko-unsplash.jpg"
},
{
"resourceCost": {
@@ -600,7 +649,8 @@
},
"gemValue": "sapphire",
"points": 2,
"tier": 2
"tier": 2,
"image": "src/assets/img/FORGET-olga-budko-unsplash.jpg"
},
{
"resourceCost": {
@@ -612,7 +662,8 @@
},
"gemValue": "diamond",
"points": 1,
"tier": 2
"tier": 2,
"image": "src/assets/img/LILY-yi-duo-unsplash.jpg"
},
{
"resourceCost": {
@@ -624,7 +675,8 @@
},
"gemValue": "onyx",
"points": 2,
"tier": 2
"tier": 2,
"image": "src/assets/img/VIOLET-rita-ox-unsplash.jpg"
},
{
"resourceCost": {
@@ -636,7 +688,8 @@
},
"gemValue": "sapphire",
"points": 2,
"tier": 2
"tier": 2,
"image": "src/assets/img/FORGET-gemma-evans-unsplash.jpg"
},
{
"resourceCost": {
@@ -648,7 +701,8 @@
},
"gemValue": "emerald",
"points": 3,
"tier": 2
"tier": 2,
"image": "src/assets/img/SUCCULENT-annie-spratt-unsplash.jpg"
},
{
"resourceCost": {
@@ -660,7 +714,8 @@
},
"gemValue": "emerald",
"points": 2,
"tier": 2
"tier": 2,
"image": "src/assets/img/SUCCULENT-edgar-castrejon-unsplash.jpg"
},
{
"resourceCost": {
@@ -672,7 +727,8 @@
},
"gemValue": "diamond",
"points": 3,
"tier": 2
"tier": 2,
"image": "src/assets/img/LILY-deleece-cook-unsplash.jpg"
},
{
"resourceCost": {
@@ -684,7 +740,8 @@
},
"gemValue": "ruby",
"points": 2,
"tier": 2
"tier": 2,
"image": "src/assets/img/ROSE-alexey-savchenko-unsplash.jpg"
},
{
"resourceCost": {
@@ -696,7 +753,8 @@
},
"gemValue": "onyx",
"points": 1,
"tier": 2
"tier": 2,
"image": "src/assets/img/VIOLET-artiom-vallat-unsplash.jpg"
},
{
"resourceCost": {
@@ -708,7 +766,8 @@
},
"gemValue": "onyx",
"points": 1,
"tier": 2
"tier": 2,
"image": "src/assets/img/VIOLET-tobias-mockenhaupt-unsplash.jpg"
},
{
"resourceCost": {
@@ -720,7 +779,8 @@
},
"gemValue": "onyx",
"points": 3,
"tier": 2
"tier": 2,
"image": "src/assets/img/VIOLET-yoksel-zok-unsplash.jpg"
},
{
"resourceCost": {
@@ -732,7 +792,8 @@
},
"gemValue": "ruby",
"points": 1,
"tier": 2
"tier": 2,
"image": "src/assets/img/ROSE-engin-akyurt-unsplash.jpg"
},
{
"resourceCost": {
@@ -744,7 +805,8 @@
},
"gemValue": "diamond",
"points": 2,
"tier": 2
"tier": 2,
"image": "src/assets/img/LILY-evie-s-unsplash.jpg"
},
{
"resourceCost": {
@@ -756,7 +818,8 @@
},
"gemValue": "emerald",
"points": 1,
"tier": 2
"tier": 2,
"image": "src/assets/img/SUCCULENT-jacalyn-beales-unsplash.jpg"
},
{
"resourceCost": {
@@ -768,7 +831,8 @@
},
"gemValue": "diamond",
"points": 1,
"tier": 2
"tier": 2,
"image": "src/assets/img/LILY-deleece-cook-unsplash.jpg"
},
{
"resourceCost": {
@@ -780,7 +844,8 @@
},
"gemValue": "ruby",
"points": 1,
"tier": 2
"tier": 2,
"image": "src/assets/img/ROSE-alexey-savchenko-unsplash.jpg"
},
{
"resourceCost": {
@@ -792,7 +857,8 @@
},
"gemValue": "sapphire",
"points": 1,
"tier": 2
"tier": 2,
"image": "src/assets/img/FORGET-yoksel-zok-unsplash.jpg"
},
{
"resourceCost": {
@@ -804,7 +870,8 @@
},
"gemValue": "ruby",
"points": 3,
"tier": 2
"tier": 2,
"image": "src/assets/img/ROSE-ekrem-osmanoglu-unsplash.jpg"
},
{
"resourceCost": {
@@ -816,7 +883,8 @@
},
"gemValue": "sapphire",
"points": 3,
"tier": 2
"tier": 2,
"image": "src/assets/img/FORGET-yoksel-zok-unsplash.jpg"
},
{
"resourceCost": {
@@ -828,7 +896,8 @@
},
"gemValue": "sapphire",
"points": 2,
"tier": 2
"tier": 2,
"image": "src/assets/img/FORGET-yoksel-zok-unsplash.jpg"
},
{
"resourceCost": {
@@ -840,7 +909,8 @@
},
"gemValue": "sapphire",
"points": 1,
"tier": 2
"tier": 2,
"image": "src/assets/img/FORGET-gemma-evans-unsplash.jpg"
}
],
"tierThree": [
@@ -854,7 +924,8 @@
},
"gemValue": "ruby",
"points": 4,
"tier": 3
"tier": 3,
"image": "src/assets/img/ROSE-engin-akyurt-unsplash.jpg"
},
{
"resourceCost": {
@@ -866,7 +937,8 @@
},
"gemValue": "diamond",
"points": 4,
"tier": 3
"tier": 3,
"image": "src/assets/img/LILY-evie-s-unsplash.jpg"
},
{
"resourceCost": {
@@ -878,7 +950,8 @@
},
"gemValue": "diamond",
"points": 4,
"tier": 3
"tier": 3,
"image": "src/assets/img/LILY-meghna-r-unsplash.jpg"
},
{
"resourceCost": {
@@ -890,7 +963,8 @@
},
"gemValue": "emerald",
"points": 4,
"tier": 3
"tier": 3,
"image": "src/assets/img/SUCCULENT-annie-spratt-unsplash.jpg"
},
{
"resourceCost": {
@@ -902,7 +976,8 @@
},
"gemValue": "onyx",
"points": 4,
"tier": 3
"tier": 3,
"image": "src/assets/img/VIOLET-tobias-mockenhaupt-unsplash.jpg"
},
{
"resourceCost": {
@@ -914,7 +989,8 @@
},
"gemValue": "sapphire",
"points": 4,
"tier": 3
"tier": 3,
"image": "src/assets/img/FORGET-krzysztof-kowalik-unsplash.jpg"
},
{
"resourceCost": {
@@ -926,7 +1002,8 @@
},
"gemValue": "onyx",
"points": 4,
"tier": 3
"tier": 3,
"image": "src/assets/img/VIOLET-tobias-mockenhaupt-unsplash.jpg"
},
{
"resourceCost": {
@@ -938,7 +1015,8 @@
},
"gemValue": "sapphire",
"points": 4,
"tier": 3
"tier": 3,
"image": "src/assets/img/FORGET-olga-budko-unsplash.jpg"
},
{
"resourceCost": {
@@ -950,7 +1028,8 @@
},
"gemValue": "emerald",
"points": 4,
"tier": 3
"tier": 3,
"image": "src/assets/img/SUCCULENT-edgar-castrejon-unsplash.jpg"
},
{
"resourceCost": {
@@ -962,7 +1041,8 @@
},
"gemValue": "emerald",
"points": 4,
"tier": 3
"tier": 3,
"image": "src/assets/img/SUCCULENT-tim-mossholder-unsplash.jpg"
},
{
"resourceCost": {
@@ -974,7 +1054,8 @@
},
"gemValue": "ruby",
"points": 3,
"tier": 3
"tier": 3,
"image": "src/assets/img/ROSE-arkadiy-unsplash.jpg"
},
{
"resourceCost": {
@@ -986,7 +1067,8 @@
},
"gemValue": "emerald",
"points": 3,
"tier": 3
"tier": 3,
"image": "src/assets/img/SUCCULENT-jacalyn-beales-unsplash.jpg"
},
{
"resourceCost": {
@@ -998,7 +1080,8 @@
},
"gemValue": "diamond",
"points": 3,
"tier": 3
"tier": 3,
"image": "src/assets/img/LILY-deleece-cook-unsplash.jpg"
},
{
"resourceCost": {
@@ -1010,7 +1093,8 @@
},
"gemValue": "onyx",
"points": 3,
"tier": 3
"tier": 3,
"image": "src/assets/img/VIOLET-rita-ox-unsplash.jpg"
},
{
"resourceCost": {
@@ -1022,7 +1106,8 @@
},
"gemValue": "sapphire",
"points": 3,
"tier": 3
"tier": 3,
"image": "src/assets/img/FORGET-anna-rozwadowska-unsplash.jpg"
},
{
"resourceCost": {
@@ -1034,7 +1119,8 @@
},
"gemValue": "sapphire",
"points": 5,
"tier": 3
"tier": 3,
"image": "src/assets/img/FORGET-gemma-evans-unsplash.jpg"
},
{
"resourceCost": {
@@ -1046,7 +1132,8 @@
},
"gemValue": "diamond",
"points": 5,
"tier": 3
"tier": 3,
"image": "src/assets/img/LILY-evie-s-unsplash-2.jpg"
},
{
"resourceCost": {
@@ -1058,7 +1145,8 @@
},
"gemValue": "ruby",
"points": 5,
"tier": 3
"tier": 3,
"image": "src/assets/img/ROSE-aleza-van-der-werff-unsplash.jpg"
},
{
"resourceCost": {
@@ -1070,7 +1158,8 @@
},
"gemValue": "emerald",
"points": 5,
"tier": 3
"tier": 3,
"image": "src/assets/img/SUCCULENT-angele-kamp-unsplash.jpg"
},
{
"resourceCost": {
@@ -1082,8 +1171,8 @@
},
"gemValue": "onyx",
"points": 5,
"tier": 3
"tier": 3,
"image": "src/assets/img/VIOLET-nick-nice-unsplash.jpg"
}
]
}
}

View File

@@ -1,6 +1,6 @@
import { AppState, CardData, NobleData, PlayerData, ResourceCost } from "./types";
import { AppState, CardData, NobleData, PlayerData, ResourceCost, UIState } from "../util/types";
import CardDeck from '../data/cards.json';
import { useCurrentPlayer } from "./useCurrentPlayer";
import { validateChips } from "../components/Player/ActionMethods/getChipsActions";
export const initialActions = {
buyCard: { active: false },
@@ -52,6 +52,27 @@ export const setStateGetChips = (prev: AppState) => {
}
}
export const setStateUpdateSelection = (prev: AppState, value: keyof ResourceCost) => {
let newSelection = prev.actions.getChips.selection;
newSelection?.push(value);
let newState = {
...prev,
actions: {
...prev.actions,
getChips: {
active: true,
selection: newSelection,
valid: false
}
}
}
const result = validateChips(newState);
newState.actions.getChips.valid = result;
return newState;
}
export const setStateBuyCard = (prev: AppState) => {
return {
...prev,
@@ -79,17 +100,28 @@ export const setStateReserveCard = (prev: AppState) => {
}
}
export const setStateGetNoble = (prev: AppState, noble: NobleData) => {
const currentPlayer = useCurrentPlayer(prev);
if (!currentPlayer) return prev;
export const setStateReservePlusGold = (prev: AppState) => {
return {
...prev,
actions: {
...initialState.actions,
reserveCard: {
active: true,
includeGold: true,
valid: false
}
}
}
}
export const setStateGetNoble = (prev: AppState, noble: NobleData, prevPlayer: PlayerData) => {
const updatedPlayer = {
...currentPlayer,
nobles: [...currentPlayer.nobles, noble],
points: currentPlayer.points + 3
...prevPlayer,
nobles: [...prevPlayer.nobles, noble],
points: prevPlayer.points + 3
}
const idx = prev.players.indexOf(currentPlayer);
const idx = prev.players.indexOf(prevPlayer);
const newPlayers = prev.players;
newPlayers[idx] = updatedPlayer;

View File

@@ -1,4 +1,4 @@
import { AppState, PlayerData } from "./types";
import { AppState, PlayerData } from "../util/types";
export const useCurrentPlayer = (state: AppState): PlayerData | null => {
/**

View File

@@ -0,0 +1,12 @@
import { AppState } from "../util/types";
import { useCurrentPlayer } from "./useCurrentPlayer";
export default function usePreviousPlayer(state: AppState) {
const currentPlayer = useCurrentPlayer(state);
if (!currentPlayer) return;
const numPlayers = state.players.length;
const targetID = currentPlayer.id;
const idx = ((targetID - 2) < 0) ? (numPlayers - 1) : (targetID - 2);
return state.players[idx];
}

View File

@@ -1,6 +1,6 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { render } from 'react-dom'
import App from './App'
import './index.css'
import './index.scss'
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />)
const root = document.getElementById('root') as HTMLElement;
render(<App />, root);

17
src/sass/App.scss Normal file
View File

@@ -0,0 +1,17 @@
#root {
.hidden {
display: none;
}
.collapsed {
width: 20vw;
align-items: flex-end;
}
display: flex;
align-items: center;
justify-content: center;
width: 99vw;
margin: 0 0.5vw;
text-align: center;
}

View File

@@ -0,0 +1,41 @@
@import "./placeholders";
@mixin map-gem-values($parentClass) {
#{$parentClass} {
&-emerald {
background-color: green;
color: white;
}
&-diamond {
background-color: white;
color: black;
}
&-onyx {
background-color: black;
color: white;
}
&-sapphire {
background-color: blue;
color: white;
}
&-ruby {
background-color: red;
color: black;
}
&-gold {
background-color: gold;
color: black;
}
}
p {
@extend %chip-design;
}
}
@mixin get-random-image($targetList) {
$idx: random(6);
$nth: nth($targetList, $idx);
background-image: url($nth);
}

View File

@@ -0,0 +1,11 @@
// designed to extend a "p" element
%chip-design {
display: flex;
align-items: center;
justify-content: center;
margin: 5px;
padding: 8px;
width: 1rem;
height: 1rem;
border-radius: 50%;
}

View File

@@ -0,0 +1 @@
$gemlist: 'ruby', 'sapphire', 'onyx', 'emerald', 'diamond', 'gold';

View File

@@ -1,4 +1,4 @@
import { AppState, PlayerData } from "./types";
import { AppState, PlayerData } from "../types";
export const turnOrderUtil = (prev: AppState, dynamic: PlayerData) => {
let roundIncrement = false;

View File

@@ -1,4 +1,4 @@
import { FullDeck } from "./types"
import { FullDeck } from "../types"
export default function cardTierToKey(tier: number): keyof FullDeck {
switch (tier) {

View File

@@ -1,4 +1,4 @@
import { PlayerData, ResourceCost } from "./types";
import { PlayerData, ResourceCost } from "../types";
export default function getTotalBuyingPower(currentPlayer: PlayerData) {
let totalBuyingPower = {
@@ -16,8 +16,8 @@ export default function getTotalBuyingPower(currentPlayer: PlayerData) {
totalBuyingPower[key as keyof ResourceCost] += quantity;
}
for (let each of currentPlayer.cards) {
totalBuyingPower[each.gemValue as keyof ResourceCost] += 1;
for (let [gem, arr] of Object.entries(currentPlayer.cards)) {
totalBuyingPower[gem as keyof ResourceCost] += arr.length;
}
return totalBuyingPower;

View File

@@ -0,0 +1,12 @@
import { UIState } from "../types";
export const shouldRightSideCollapse = (UICollapse: UIState) => {
for (let slice of Object.keys(UICollapse)) {
if (slice === "playerUICollapsed") continue;
if (!UICollapse[slice as keyof UIState]) {
return true;
}
}
return false;
}

View File

@@ -1,4 +1,5 @@
import { AppState, CardData, PlayerData, ResourceCost, SetActionType, setStateType } from "./types";
import { Dispatch, SetStateAction } from "react";
import { AppState, CardData, PlayerData, ResourceCost, setStateType, UIState } from "./types";
export interface StateProps {
state: AppState,
@@ -7,14 +8,17 @@ export interface StateProps {
export interface CardProps extends StateProps {
data: CardData
reserved?: boolean
collapsed?: boolean
}
export interface CardRowProps extends StateProps {
tier: number
liftCollapsed: (collapsed: boolean, tier: number) => void
}
export interface AllPlayersProps extends StateProps {
setActionState: (value: SetActionType, player?: PlayerData) => void
export interface NobleProps extends StateProps {
liftCollapsed: (collapsed: boolean) => void
}
export interface PlayerProps extends StateProps {
@@ -25,3 +29,12 @@ export interface ResourceProps extends StateProps {
liftSelection: (value: keyof ResourceCost) => void
}
export interface AllPlayersProps extends ResourceProps {
UICollapse: UIState,
setUICollapse: Dispatch<SetStateAction<UIState>>
liftCollapsed: (collapsed: boolean, tier?: number) => void
}
export interface SelectionProps extends StateProps {
UICollapse: UIState
}

View File

@@ -0,0 +1,9 @@
import { UIState } from "../types";
export const defaultUIState: UIState = {
noblesCollapsed: true,
tierThreeCollapsed: true,
tierTwoCollapsed: true,
tierOneCollapsed: true,
playerUICollapsed: false
}

View File

@@ -1,5 +1,5 @@
import { AppState, FullDeck, NobleData, ResourceCost, setStateType } from "./types";
import NobleStore from '../data/nobles.json';
import { AppState, FullDeck, NobleData, ResourceCost, setStateType } from "../types";
import NobleStore from '../../data/nobles.json';
const shuffleDeck = (state: AppState, setState: setStateType) => {
if (!state.gameboard.deck) return;

View File

@@ -1,5 +1,6 @@
import { initialState } from "./stateSetters"
import { initialState } from "../hooks/stateSetters"
import { AppState, CardData, NobleData, PlayerData } from "./types"
import Cards from '../data/cards.json';
// mock data for early game
export const mockPlayerOne: PlayerData = {
@@ -43,6 +44,40 @@ export const mockState: AppState = {
players: [mockPlayerOne, mockPlayerTwo]
}
export const sampleTierOneCard: CardData = {
gemValue: 'ruby',
tier: 3,
points: 0,
resourceCost: {
ruby: 0,
sapphire: 0,
emerald: 0,
diamond: 0,
onyx: 3
}
}
export const mockCardRows = {
tierOne: [
Cards.tierOne[0],
Cards.tierOne[1],
Cards.tierOne[2],
sampleTierOneCard,
],
tierTwo: [
Cards.tierTwo[0],
Cards.tierTwo[1],
Cards.tierTwo[2],
Cards.tierTwo[3],
],
tierThree: [
Cards.tierThree[0],
Cards.tierThree[1],
Cards.tierThree[2],
Cards.tierThree[3],
]
}
// mock data for midgame
// playerOneMidGame buys high diamond cost card
export const playerOneMidGame = {

View File

@@ -62,13 +62,21 @@ export interface PlayerData {
turnActive?: boolean,
points: number,
nobles: NobleData[],
cards: CardData[],
cards: PlayerCards,
reservedCards?: CardData[],
inventory: {
[Property in keyof ResourceCost]: number
}
}
export interface PlayerCards {
ruby: CardData[],
emerald: CardData[],
sapphire: CardData[],
onyx: CardData[],
diamond: CardData[],
}
export interface FullDeck {
tierOne: CardData[],
tierTwo: CardData[],
@@ -76,10 +84,11 @@ export interface FullDeck {
}
export interface CardData {
gemValue: GemValue | string
gemValue: string
tier: number
points?: number
resourceCost: ResourceCost
image: string
}
export interface ResourceCost {
@@ -97,11 +106,10 @@ export interface NobleData {
resourceCost: ResourceCost
}
export enum GemValue {
ruby,
sapphire,
emerald,
diamond,
onyx,
gold,
export interface UIState {
noblesCollapsed: boolean
tierThreeCollapsed: boolean
tierTwoCollapsed: boolean
tierOneCollapsed: boolean
playerUICollapsed: boolean
}

View File

@@ -1,8 +1,9 @@
import { describe, expect, test } from "vitest"
import cardTierToKey from "./cardTierToKey";
import cardTierToKey from "./mechanics/cardTierToKey";
import { mockPlayerOne, mockState } from "./testUtils";
import { turnOrderUtil } from "./turnOrderUtil";
import { useCurrentPlayer } from "./useCurrentPlayer";
import { turnOrderUtil } from "./mechanics/TurnOrderUtil";
import { useCurrentPlayer } from "../hooks/useCurrentPlayer";
import getTotalBuyingPower from "./mechanics/getTotalBuyingPower";
describe('app utilities', () => {
test('useCurrentPlayer', () => {
@@ -39,6 +40,14 @@ describe('app utilities', () => {
test('initializeBoard', () => {
expect(1).toBe(1);
})
test('get total buying power', () => {
const currentPlayer = useCurrentPlayer(mockState);
if (!currentPlayer) return;
const bp = getTotalBuyingPower(currentPlayer);
expect(bp).toBeDefined();
})
})
export {}