Compare commits
21 Commits
rework-080
...
ui-backtra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c549e7020 | ||
| 75d161a10b | |||
| e987a2aad8 | |||
| 4cfc41374e | |||
| 3156029ae0 | |||
| 134ab9a284 | |||
| 4d3f6bfeb4 | |||
| 8c6222864a | |||
| 3cdc1a78c8 | |||
| e922a73fa1 | |||
| 6914d44900 | |||
| 40904d36d3 | |||
| 3234966629 | |||
| 15e32503e3 | |||
| fb38130c17 | |||
| 2fba7b30fa | |||
| 74587d0a0e | |||
| ab6a190cb5 | |||
| 6a40c0e592 | |||
| 35931eb8e5 | |||
|
|
fd6d96737a |
2441
package-lock.json
generated
2441
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -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",
|
||||
|
||||
41
src/App.css
41
src/App.css
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { v4 } from 'uuid';
|
||||
import { CardProps } from '../../util/propTypes';
|
||||
import { ResourceCost } from '../../util/types';
|
||||
import { useCurrentPlayer } from '../../util/useCurrentPlayer';
|
||||
import { useCurrentPlayer } from '../../hooks/useCurrentPlayer';
|
||||
import { buyCardActions } from '../Player/ActionMethods';
|
||||
import { hasMaxReserved, reserveCard } from '../Player/ActionMethods/reserveCardActions';
|
||||
const { buyCard, tooExpensive } = buyCardActions;
|
||||
@@ -12,32 +12,36 @@ export default function Card({ data, state, setState }: CardProps) {
|
||||
if (!data) return <div className="card"></div>;
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="top-row">
|
||||
<p>Counts as: {data.gemValue}</p>
|
||||
<p>Point value: {data.points || 0}</p>
|
||||
<p>Cost:</p>
|
||||
{
|
||||
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>
|
||||
})
|
||||
}
|
||||
{ state.actions.buyCard.active &&
|
||||
<button
|
||||
onClick={() => buyCard(state, setState, data)}
|
||||
disabled={tooExpensive(data, state)}>
|
||||
Buy This Card
|
||||
</button>
|
||||
}
|
||||
{ state.actions.reserveCard.active &&
|
||||
<button
|
||||
onClick={() => reserveCard(state, setState, data)}
|
||||
disabled={hasMaxReserved(currentPlayer)}>
|
||||
Reserve This Card
|
||||
</button>
|
||||
}
|
||||
<div className="card" key={v4()}>
|
||||
<p>Counts as: {data.gemValue}</p>
|
||||
<p>Point value: {data.points || 0}</p>
|
||||
<p>Cost:</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()} className={`card-cost-${key}`}>
|
||||
{data.resourceCost[key as keyof ResourceCost]}
|
||||
</p>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
{ state.actions.buyCard.active &&
|
||||
<button
|
||||
onClick={() => buyCard(state, setState, data)}
|
||||
disabled={tooExpensive(data, state)}>
|
||||
Buy This Card
|
||||
</button>
|
||||
}
|
||||
{ state.actions.reserveCard.active &&
|
||||
<button
|
||||
onClick={() => reserveCard(state, setState, data)}
|
||||
disabled={hasMaxReserved(currentPlayer)}>
|
||||
Reserve This Card
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
48
src/components/Card/CardRow.scss
Normal file
48
src/components/Card/CardRow.scss
Normal file
@@ -0,0 +1,48 @@
|
||||
@import "../../sass/helper/mixins";
|
||||
@import "../../sass/helper/placeholders";
|
||||
|
||||
.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%;
|
||||
border: 2px solid black;
|
||||
> * {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.total-card-cost {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@include map-gem-values(".card-cost");
|
||||
p {
|
||||
@extend %chip-design;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.tier-1 {
|
||||
background-color: rgb(9, 67, 9);
|
||||
}
|
||||
|
||||
.tier-2 {
|
||||
background-color: rgb(174, 174, 32);
|
||||
color: black;
|
||||
}
|
||||
|
||||
.tier-3 {
|
||||
background-color: rgb(35, 35, 167);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ 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) {
|
||||
const typedTier = cardTierToKey(tier);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
// types, data, utils
|
||||
import { AppState, ResourceCost } from '../../util/types';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import initializeBoard, { setCardRows } from '../../util/initializeBoard';
|
||||
import { AppState, PlayerData, ResourceCost } from '../../util/types';
|
||||
import { getChipsActions } from '../Player/ActionMethods';
|
||||
import { StateProps } from '../../util/propTypes';
|
||||
import { Link } from 'react-router-dom';
|
||||
import './Gameboard.scss';
|
||||
|
||||
// components
|
||||
import Nobles from './Nobles';
|
||||
import initializeBoard, { setCardRows } from '../../util/initializeBoard';
|
||||
import Nobles from '../Nobles/Nobles';
|
||||
import AvailableChips from '../Resources/AvailableChips';
|
||||
import AllPlayers from '../Player/AllPlayers';
|
||||
import CardRow from '../Card/CardRow';
|
||||
import SelectionView from '../Resources/SelectionView';
|
||||
import { useCurrentPlayer } from '../../hooks/useCurrentPlayer';
|
||||
import usePreviousPlayer from '../../hooks/usePreviousPlayer';
|
||||
const { validateChips } = getChipsActions;
|
||||
|
||||
export default function Gameboard({ state, setState }: StateProps) {
|
||||
const [view, setView] = useState(<p>Loading...</p>);
|
||||
const [endgame, setEndgame] = useState<PlayerData>();
|
||||
|
||||
// callbacks for lifting state
|
||||
const liftSelection = useCallback((value: keyof ResourceCost) => {
|
||||
@@ -43,21 +47,34 @@ export default function Gameboard({ state, setState }: StateProps) {
|
||||
}, [state]);
|
||||
|
||||
// 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 currentPlayer = useCurrentPlayer(state);
|
||||
if (!currentPlayer) return;
|
||||
|
||||
if (currentPlayer.id <= endgame.id) {
|
||||
const winnerData = state.players;
|
||||
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(
|
||||
@@ -82,6 +99,5 @@ export default function Gameboard({ state, setState }: StateProps) {
|
||||
}
|
||||
}, [state]);
|
||||
|
||||
// render
|
||||
return view
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, it, describe, vi } from 'vitest';
|
||||
import initializeBoard from '../../util/initializeBoard';
|
||||
import { initialState } from '../../util/stateSetters';
|
||||
import { initialState } from '../../hooks/stateSetters';
|
||||
import { AppState, setStateType } from '../../util/types';
|
||||
|
||||
describe('game config', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { v4 } from "uuid";
|
||||
import { NobleData, ResourceCost } from "../../util/types";
|
||||
import { StateProps } from "../../util/propTypes";
|
||||
import "./Nobles.css"
|
||||
import "../Nobles/Nobles.scss"
|
||||
|
||||
export default function Nobles({ state }: StateProps) {
|
||||
if (!state.gameboard.nobles.length) {
|
||||
@@ -1,5 +1,5 @@
|
||||
import getTotalBuyingPower from "./getTotalBuyingPower";
|
||||
import { NobleData, PlayerData, ResourceCost } from "./types";
|
||||
import getTotalBuyingPower from "../../util/getTotalBuyingPower";
|
||||
import { NobleData, PlayerData, ResourceCost } from "../../util/types";
|
||||
|
||||
export const canPickUpNoble = (player: PlayerData, noble: NobleData) => {
|
||||
const totalBuyingPower = getTotalBuyingPower(player);
|
||||
@@ -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', () => {})
|
||||
@@ -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 { AppState, CardData, PlayerCards, ResourceCost, setStateType } from "../../../util/types";
|
||||
import cardTierToKey from "../../../util/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;
|
||||
}
|
||||
}
|
||||
|
||||
// redistribute gold back into resource pool
|
||||
while (goldToReturn > 0) {
|
||||
updatedPlayer.inventory['gold'] && updatedPlayer.inventory['gold']--;
|
||||
newResourcePool['gold'] && newResourcePool['gold']++;
|
||||
goldToReturn--;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
// update player's held cards
|
||||
updatedPlayer.cards = {
|
||||
...updatedPlayer.cards,
|
||||
[card.gemValue]: [...updatedPlayer.cards[card.gemValue as keyof PlayerCards], card]
|
||||
}
|
||||
|
||||
// attempt to queue replacement card from full deck
|
||||
const typedCardTier = ["tierThree", "tierTwo", "tierOne"][2 - (card.tier-1)] as keyof FullDeck;
|
||||
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];
|
||||
newTargetCardRow = newTargetCardRow.filter((data: CardData) => data.resourceCost !== card.resourceCost);
|
||||
// push replacement card to face up card, if exists
|
||||
if (replacementCard) newTargetCardRow.push(replacementCard);
|
||||
|
||||
if (!reservedCardCheck) {
|
||||
const replacementCard = newFullDeckTargetTier.shift();
|
||||
newTargetCardRow = newTargetCardRow.filter((data: CardData) => data.resourceCost !== card.resourceCost);
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 { initialActions } from "../../../hooks/stateSetters";
|
||||
|
||||
export const hasMaxChips = (player: PlayerData | null): boolean => {
|
||||
if (!player) return true;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import cardTierToKey from "../../../util/cardTierToKey";
|
||||
import { initialActions } from "../../../util/stateSetters";
|
||||
import { initialActions } from "../../../hooks/stateSetters";
|
||||
import { turnOrderUtil } from "../../../util/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);
|
||||
|
||||
125
src/components/Player/ActionMethods/tests/buyCard.test.tsx
Normal file
125
src/components/Player/ActionMethods/tests/buyCard.test.tsx
Normal 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 {}
|
||||
@@ -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', () => {
|
||||
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
export default {}
|
||||
@@ -1,9 +0,0 @@
|
||||
.all-players {
|
||||
display: flex;
|
||||
background-color: rgb(237, 213, 156);
|
||||
color: black;
|
||||
}
|
||||
|
||||
.all-players p {
|
||||
margin: 1rem;
|
||||
}
|
||||
27
src/components/Player/AllPlayers.scss
Normal file
27
src/components/Player/AllPlayers.scss
Normal file
@@ -0,0 +1,27 @@
|
||||
@import '../../sass/helper/mixins';
|
||||
@import '../../sass/helper/placeholders';
|
||||
|
||||
.all-players {
|
||||
display: flex;
|
||||
background-color: rgb(237, 213, 156);
|
||||
color: black;
|
||||
.player-ui {
|
||||
p {
|
||||
margin: 1rem;
|
||||
}
|
||||
.subheader {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.resources {
|
||||
.player-chips-enum {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@include map-gem-values(".player-chip");
|
||||
p {
|
||||
@extend %chip-design;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { v4 } from "uuid";
|
||||
import Player from "./Player";
|
||||
import { PlayerData } from "../../util/types";
|
||||
import { StateProps } from "../../util/propTypes";
|
||||
import "./AllPlayers.css"
|
||||
import "./AllPlayers.scss"
|
||||
|
||||
export default function AllPlayers({ state, setState }: StateProps) {
|
||||
const playerPool = state.players?.map((player: PlayerData) => <Player key={v4()} player={player} state={state} setState={setState} />);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { setStateAwaitAction, setStateBuyCard, setStateGetChips, setStateReserveCard } from "../../util/stateSetters";
|
||||
import { setStateAwaitAction, setStateBuyCard, setStateGetChips, setStateReserveCard } from "../../hooks/stateSetters";
|
||||
import { useEffect, useState } from "react";
|
||||
import { PlayerProps } from "../../util/propTypes";
|
||||
import { CardData, PlayerData } from "../../util/types"
|
||||
import { hasMaxReserved } from "./ActionMethods/reserveCardActions";
|
||||
import { hasMaxChips } from "./ActionMethods/getChipsActions";
|
||||
import { v4 } from "uuid";
|
||||
import Card from "../Card/Card";
|
||||
|
||||
export default function Player({ player, state, setState }: PlayerProps) {
|
||||
const [dynamic, setDynamic] = useState<PlayerData>();
|
||||
@@ -17,24 +18,12 @@ export default function Player({ player, state, setState }: PlayerProps) {
|
||||
|
||||
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>
|
||||
)
|
||||
})
|
||||
}
|
||||
</>
|
||||
<div className="card-view">
|
||||
<p>Cards:</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(
|
||||
@@ -42,17 +31,7 @@ export default function Player({ player, state, setState }: PlayerProps) {
|
||||
<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>
|
||||
)
|
||||
return <Card key={v4()} data={data} state={state} setState={setState} />
|
||||
})
|
||||
}
|
||||
</>
|
||||
@@ -78,29 +57,46 @@ 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>
|
||||
<p className="subheader">Name: {player.name} {player.starter && "(round starter)"}</p>
|
||||
|
||||
{/* Dynamic data from state */}
|
||||
<section className="turn-and-action-based">
|
||||
<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>
|
||||
|
||||
{/* Player actions */}
|
||||
<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>
|
||||
<p className="subheader">{dynamic?.name}'s Resources</p>
|
||||
|
||||
<div className="player-chips">
|
||||
<p>Chips:</p>
|
||||
{ dynamic && Object.entries(dynamic.inventory).map(([key,value]) => {
|
||||
return value > 0 && <p key={v4()}>{key}: {value}</p>
|
||||
})}
|
||||
<div className="player-chips-enum">
|
||||
{ dynamic && Object.entries(dynamic.inventory).map(([key,value]) => {
|
||||
return value > 0 && (
|
||||
<p key={v4()} className={`player-chip-${key}`}>{value}</p>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="player-cards">
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
@import "../../sass/helper/mixins";
|
||||
|
||||
.available-chips {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
background-color: rgb(236, 238, 186);
|
||||
color: black;
|
||||
}
|
||||
|
||||
.available-chips p {
|
||||
margin: 1rem;
|
||||
p {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
@include map-gem-values(".chips");
|
||||
}
|
||||
@@ -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)}
|
||||
>
|
||||
{key}: {state.gameboard.tradingResources[key as keyof ResourceCost]}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
key={v4()}
|
||||
value={key}
|
||||
className={`chips-${key}`}
|
||||
// @ts-ignore
|
||||
disabled={state.gameboard.tradingResources[typedKey] <= 0}
|
||||
onClick={() => liftSelection(typedKey)}
|
||||
>
|
||||
{key}: {state.gameboard.tradingResources[typedKey]}
|
||||
</button>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
16
src/components/Resources/SelectionView.scss
Normal file
16
src/components/Resources/SelectionView.scss
Normal file
@@ -0,0 +1,16 @@
|
||||
@import "../../sass/helper/mixins";
|
||||
|
||||
.selection-view {
|
||||
.current-selections {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@include map-gem-values(".selection-value");
|
||||
|
||||
p {
|
||||
margin: 1rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 25%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { StateProps } from "../../util/propTypes";
|
||||
import { useCurrentPlayer } from "../../util/useCurrentPlayer";
|
||||
import { useCurrentPlayer } from "../../hooks/useCurrentPlayer";
|
||||
import { GetChipsHTML, ReserveCardHTML } from "./ViewHTML";
|
||||
import './SelectionView.scss';
|
||||
|
||||
export default function SelectionView({ state, setState }: StateProps) {
|
||||
const [currentPlayer, setCurrentPlayer] = useState(useCurrentPlayer(state));
|
||||
|
||||
const actionTypes = [
|
||||
state.actions.getChips,
|
||||
state.actions.buyCard,
|
||||
state.actions.reserveCard
|
||||
]
|
||||
|
||||
const [view, setView] = useState(<></>);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { v4 } from "uuid";
|
||||
import { useEffect, useState } from "react";
|
||||
import { setStateGetChips } from "../../util/stateSetters";
|
||||
import { v4 } from "uuid";
|
||||
import { setStateGetChips, setStateReserveCard, setStateReservePlusGold } from "../../hooks/stateSetters";
|
||||
import { useCurrentPlayer } from "../../hooks/useCurrentPlayer";
|
||||
import { StateProps } 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;
|
||||
|
||||
@@ -28,7 +28,7 @@ export const GetChipsHTML = ({ state, setState }: StateProps) => {
|
||||
<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}</p>)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
@@ -42,12 +42,21 @@ export const GetChipsHTML = ({ state, setState }: StateProps) => {
|
||||
}
|
||||
|
||||
export const ReserveCardHTML = ({ state, setState }: StateProps) => {
|
||||
const [takeGold, setTakeGold] = useState("");
|
||||
const [takeGold, setTakeGold] = useState(false);
|
||||
const currentPlayer = useCurrentPlayer(state);
|
||||
|
||||
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">
|
||||
@@ -60,8 +69,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 +79,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>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { AppState, CardData, NobleData, PlayerData, ResourceCost } from "./types";
|
||||
import { AppState, CardData, NobleData, PlayerData, ResourceCost } from "../util/types";
|
||||
import CardDeck from '../data/cards.json';
|
||||
import { useCurrentPlayer } from "./useCurrentPlayer";
|
||||
|
||||
export const initialActions = {
|
||||
buyCard: { active: false },
|
||||
@@ -79,17 +78,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;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AppState, PlayerData } from "./types";
|
||||
import { AppState, PlayerData } from "../util/types";
|
||||
|
||||
export const useCurrentPlayer = (state: AppState): PlayerData | null => {
|
||||
/**
|
||||
12
src/hooks/usePreviousPlayer.tsx
Normal file
12
src/hooks/usePreviousPlayer.tsx
Normal 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];
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
15
src/sass/App.scss
Normal file
15
src/sass/App.scss
Normal file
@@ -0,0 +1,15 @@
|
||||
@import './helper/placeholders';
|
||||
|
||||
#root {
|
||||
.hidden {
|
||||
display: none
|
||||
}
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
29
src/sass/helper/_mixins.scss
Normal file
29
src/sass/helper/_mixins.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
src/sass/helper/_placeholders.scss
Normal file
11
src/sass/helper/_placeholders.scss
Normal 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%;
|
||||
}
|
||||
0
src/sass/helper/_variables.scss
Normal file
0
src/sass/helper/_variables.scss
Normal file
@@ -11,13 +11,13 @@ export default function getTotalBuyingPower(currentPlayer: PlayerData) {
|
||||
}
|
||||
|
||||
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;
|
||||
for (let [gem, arr] of Object.entries(currentPlayer.cards)) {
|
||||
totalBuyingPower[gem as keyof ResourceCost] += arr.length;
|
||||
}
|
||||
|
||||
return totalBuyingPower;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,7 +84,7 @@ export interface FullDeck {
|
||||
}
|
||||
|
||||
export interface CardData {
|
||||
gemValue: GemValue | string
|
||||
gemValue: string
|
||||
tier: number
|
||||
points?: number
|
||||
resourceCost: ResourceCost
|
||||
@@ -96,12 +104,3 @@ export interface NobleData {
|
||||
points: number,
|
||||
resourceCost: ResourceCost
|
||||
}
|
||||
|
||||
export enum GemValue {
|
||||
ruby,
|
||||
sapphire,
|
||||
emerald,
|
||||
diamond,
|
||||
onyx,
|
||||
gold,
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import { describe, expect, test } from "vitest"
|
||||
import cardTierToKey from "./cardTierToKey";
|
||||
import { mockPlayerOne, mockState } from "./testUtils";
|
||||
import { turnOrderUtil } from "./turnOrderUtil";
|
||||
import { useCurrentPlayer } from "./useCurrentPlayer";
|
||||
import { useCurrentPlayer } from "../hooks/useCurrentPlayer";
|
||||
import getTotalBuyingPower from "./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 {}
|
||||
Reference in New Issue
Block a user