diff --git a/packages/wonders-client/src/components/App/App.tsx b/packages/wonders-client/src/components/App/App.tsx index 3a75fc8..ece6acc 100644 --- a/packages/wonders-client/src/components/App/App.tsx +++ b/packages/wonders-client/src/components/App/App.tsx @@ -1,12 +1,13 @@ import { useState } from 'react'; import useKetchup from 'ketchup-react' -import { addPlayerAction, reducer, removePlayerAction, resetAction } from 'wonders-common' +import { addPlayerAction, loadSampleAction, reducer, removePlayerAction, resetAction } from 'wonders-common' import './App.css'; import Game from '../Game/Game'; import UserSelector from '../UserSelector/UserSelector'; import 'bootstrap/dist/css/bootstrap.min.css'; import Container from 'react-bootstrap/Container'; import Row from 'react-bootstrap/Row' +import Col from 'react-bootstrap/Col' export default function App() { const [state, dispatch] = useKetchup('ws://localhost:4000', reducer) @@ -26,18 +27,38 @@ export default function App() { } function resetGame() { - dispatch(resetAction()) - setUser(undefined) + if (window.confirm('Reset game?')) { + dispatch(resetAction()) + setUser(undefined) + } + } + + function loadSample() { + if (window.confirm('Load sample game state?')) { + dispatch(loadSampleAction()) + } } return - p.name) ?? []} locked={state !== undefined && state.stage !== 'starting'} /> + + + p.name) ?? []} locked={state !== undefined && state.stage !== 'starting'} /> + + {state ? <> - - + + + + + + + {' '} + + + - : Loading... + : Loading… } } diff --git a/packages/wonders-client/src/components/Civ/Civ.css b/packages/wonders-client/src/components/Civ/Civ.css index 5d7bb5b..cf8f505 100644 --- a/packages/wonders-client/src/components/Civ/Civ.css +++ b/packages/wonders-client/src/components/Civ/Civ.css @@ -1,6 +1,7 @@ .wonder-civ-outer { background-position: center; background-size: cover; + border: black solid; } .wonder-civ-struct { diff --git a/packages/wonders-client/src/components/Civ/Civ.tsx b/packages/wonders-client/src/components/Civ/Civ.tsx index 4768487..34d3894 100644 --- a/packages/wonders-client/src/components/Civ/Civ.tsx +++ b/packages/wonders-client/src/components/Civ/Civ.tsx @@ -1,6 +1,7 @@ import { useMemo } from 'react'; import { Player, playerStats, Resource, Science, sumMaps } from 'wonders-common'; import fill from 'lodash/fill' +import './Civ.css' const resEmojis = new Map([ ['brick', '🧱'], @@ -21,22 +22,20 @@ const wonderBgExts = ['jpg', 'webp'] export default function Civ({ player, style }: { player: Player, style: 'player' | 'neighbor' | 'compact' }) { const pStats = useMemo(() => playerStats(player), [player]) - const outerStyle: React.CSSProperties = { - backgroundImage: player.wonder && wonderBgExts.map(ext => `url("/assets/wonders/${player.wonder}.${ext}")`).join(', '), - backgroundSize: player.wonder && 'cover', - backgroundPosition: player.wonder && 'center', + const outerStyle: React.CSSProperties = player.wonder === undefined ? {} : { + backgroundImage: ['linear-gradient(to left, rgba(255, 255, 255, 0.25), rgba(255, 255, 255, 0.75))', ...wonderBgExts.map(ext => `url("/assets/wonders/${player.wonder}.${ext}")`)].join(', '), } return
+
{player.name}{player.wonder && ` β€’ ${player.wonder}`}
{[...pStats.resources].flatMap(([res, n]) => fill(Array(n), resEmojis.get(res)!)).join('')}
{pStats.shields}πŸ›‘οΈ
${player.gold}
-
{player.name}
{pStats.sciences.map(s => [...s].map(sci => sciEmojis.get(sci)!)).map(s => s.length === 1 ? s[0] : `(${s.join('/')})` )}
{[...sumMaps(pStats.structObjs.map(s => new Map([[s.type, 1]])))].map(([type, n]) => - n + {n} )}
} diff --git a/packages/wonders-client/src/components/Game/Game.tsx b/packages/wonders-client/src/components/Game/Game.tsx index 321f072..7c5ee79 100644 --- a/packages/wonders-client/src/components/Game/Game.tsx +++ b/packages/wonders-client/src/components/Game/Game.tsx @@ -44,13 +44,13 @@ export default function Game({ state, playerName, dispatch }: { state: State, pl -
{state?.age === undefined ? '' : `Age ${state.age + 1} ${state.age % 2 === 0 ? 'πŸ”ƒ' : 'πŸ”„'}`}
+
{state.age === undefined ? '' : `Age ${state.age + 1} ${state.age % 2 === 0 ? 'πŸ”ƒ' : 'πŸ”„'}`}
- + {player.hand?.map(s => structures.get(s)!)?.map(s => diff --git a/packages/wonders-common/index.ts b/packages/wonders-common/index.ts index ebb7dcd..ae5b09c 100644 --- a/packages/wonders-common/index.ts +++ b/packages/wonders-common/index.ts @@ -159,13 +159,13 @@ export function buildDeck(age: number, numPlayers: number) { throw new Error(`Unsupported player count ${numPlayers}`) } const cards: Structure[] = [...structures.values()].flatMap(structure => - structure.appears?.[age - 1] + structure.appears?.[age] ?.filter(minPlayers => numPlayers <= minPlayers) ?.map(() => structure) ?? [] ) if (age === numAges - 1) { - cards.push(...sampleSize(structuresGuilds, numPlayers + 2)) + cards.push(...sampleSize(structuresGuilds, numPlayers + 2)) // BAD } return cards } @@ -231,7 +231,7 @@ export function playerStats(p: Player) { return { structObjs, resources: sumMaps(structObjs.map(s => s.resources)), - shields: sum(structObjs.map(s => s.shields)), + shields: sum(structObjs.map(s => s.shields)) ?? 0, sciences: compact(structObjs.map(s => s.sciences)), } } @@ -250,6 +250,134 @@ export const initial: State = { players: [], discard: [], } +export const sampleState: State = { + stage: 'play', + age: 2, + turnsRemaining: 4, + discard: [ + 'Scriptorium', 'Scriptorium', 'Guard Tower', 'Baths', 'Stone Pit', + 'Caravansery', 'Loom', 'Loom', 'Stables', 'Courthouse', + 'Craftsmens Guild', 'Strategists Guild', 'Pantheon', 'Palace', 'Chamber of Commerce' + ], + players: [ + { + name: 'Player A', + gold: 2, + leftBattles: [1, 3], + rightBattles: [1, 3], + wonder: 'Mausoleum at Halicarnassus B', + hand: ['Study', 'Arsenal', 'Haven', 'Haven'], + structures: [ + { age: 0, structure: 'Stockade' }, + { age: 0, structure: 'Barracks' }, + { age: 0, structure: 'Barracks' }, + { age: 0, structure: 'Glassworks' }, + { age: 0, structure: 'Stone Pit' }, + { age: 0, structure: 'Lumber Yard' }, + { age: 1, structure: 'Walls' }, + { age: 1, structure: 'Bazar' }, + { age: 1, structure: 'Caravansery' }, + { age: 1, structure: 'Forum' }, + { age: 1, structure: 'Aqueduct' }, + { age: 1, structure: 'Quarry' }, + { age: 2, structure: 'Arena' }, + { age: 2, structure: 'Fortifications' }, + ] + }, { + name: 'Player B', + gold: 5, + leftBattles: [-1, -1], + rightBattles: [-1, -1], + wonder: 'Temple of Artemis at Ephesus B', + hand: ['Town Hall', 'University', 'Arsenal', 'Circus'], + structures: [ + { age: 0, structure: 'Apothecary' }, + { age: 0, structure: 'Pawnshop' }, + { age: 0, structure: 'Timber Yard' }, + { age: 0, structure: 'Press' }, + { age: 0, structure: 'Ore Vein' }, + { age: 0, structure: 'Clay Pool' }, + { age: 1, structure: 'Courthouse' }, + { age: 1, structure: 'Statue' }, + { age: 1, structure: 'Laboratory' }, + { age: 1, structure: 'Library' }, + { age: 1, structure: 'Sawmill' }, + { age: 1, structure: 'Foundry' }, + { age: 2, structure: 'Lighthouse' }, + { age: 2, structure: 'Builders Guild' }, + ] + }, { + name: 'Player C', + gold: 3, + leftBattles: [1, 3], + rightBattles: [1, 3], + wonder: 'Hanging Gardens of Babylon B', + hand: ['Town Hall', 'Magistrates Guild', 'Scientists Guild', 'Workers Guild'], + structures: [ + { age: 0, structure: 'Clay Pool' }, + { age: 0, structure: 'Forest Cave' }, + { age: 0, structure: 'Loom' }, + { age: 0, structure: 'Ore Vein' }, + { age: 0, structure: 'Guard Tower' }, + { age: 0, structure: 'East Trading Post' }, + { age: 1, structure: 'Laboratory' }, + { age: 1, structure: 'Archery Range' }, + { age: 1, structure: 'Dispensary' }, + { age: 1, structure: 'Glassworks' }, + { age: 1, structure: 'Press' }, + { age: 1, structure: 'Glassworks' }, + { age: 2, structure: 'Siege Workshop' }, + { age: 2, structure: 'Philosophers Guild' }, + ] + }, { + name: 'Player D', + gold: 10, + leftBattles: [-1, -1], + rightBattles: [], + wonder: 'Colossus of Rhodes A', + hand: ['Senate', 'Study', 'Senate', 'Arena'], + structures: [ + { age: 0, structure: 'Excavation' }, + { age: 0, structure: 'Lumber Yard' }, + { age: 0, structure: 'Clay Pit' }, + { age: 0, structure: 'Workshop' }, + { age: 0, structure: 'Tavern' }, + { age: 0, structure: 'West Trading Post' }, + { age: 1, structure: 'Temple' }, + { age: 1, structure: 'Vineyard' }, + { age: 1, structure: 'Stables' }, + { age: 1, structure: 'Press' }, + { age: 1, structure: 'Quarry' }, + { age: 1, structure: 'Brickyard' }, + { age: 2, structure: 'Gardens' }, + { age: 2, structure: 'Circus' }, + ] + }, { + name: 'Player E', + gold: 0, + leftBattles: [], + rightBattles: [-1, -1], + wonder: 'Great Pyramid of Giza A', + hand: ['Observatory', 'Academy', 'University', 'Lodge'], + structures: [ + { age: 0, structure: 'Apothecary' }, + { age: 0, structure: 'Tavern' }, + { age: 0, structure: 'Altar' }, + { age: 0, structure: 'Theater' }, + { age: 0, structure: 'Marketplace' }, + { age: 0, structure: 'Altar' }, + { age: 1, structure: 'Sawmill' }, + { age: 1, structure: 'Foundry' }, + { age: 1, structure: 'Brickyard' }, + { age: 1, structure: 'Dispensary' }, + { age: 1, structure: 'Training Ground' }, + { age: 1, structure: 'School' }, + { age: 2, structure: 'Siege Workshop' }, + { age: 2, structure: 'Gardens' }, + ] + }, + ], +} export interface BaseAction { type: string @@ -378,7 +506,7 @@ export function countPoints(state: State, playerIdx: number): number { } function beginAge(state: State): State { const age = state.age === undefined ? 0 : state.age + 1 - const deck = shuffle(buildDeck(age, state.players.length).map(s => s.name)) + const deck = shuffle(buildDeck(age, state.players.length).map(s => s.name)) // BAD const handSize = Math.ceil(deck.length / state.players.length) const hands = chunk(deck, handSize) return { @@ -528,10 +656,8 @@ const reducers: { [type: string]: (state: State, action: any) => State } = { } return newState.players.every(p => p.turnPlan !== undefined) ? doTurn(newState) : newState }, - reset: () => initial, - 'load sample': () => ({ - ...initial, // TODO - }), + 'reset': () => initial, + 'load sample': () => sampleState, } export function reducer(state: State, action: Action): State { return reducers[action.type](state, action)