Sample data and initial work on player display

This commit is contained in:
Kenneth Allen 2021-10-20 18:58:10 +11:00
parent 4d6ca264f7
commit 7f2405fd29
5 changed files with 170 additions and 23 deletions

View File

@ -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() {
if (window.confirm('Reset game?')) {
dispatch(resetAction())
setUser(undefined)
}
}
function loadSample() {
if (window.confirm('Load sample game state?')) {
dispatch(loadSampleAction())
}
}
return <Container fluid>
<Row>
<Col>
<UserSelector user={user} selectUser={selectUser} removeUser={removeUser} users={state?.players.map(p => p.name) ?? []} locked={state !== undefined && state.stage !== 'starting'} />
</Col>
</Row>
{state
? <>
<Game state={state} dispatch={dispatch} playerName={user} />
<button onClick={() => window.confirm('Reset game?') && resetGame()}> Reset</button>
<Row>
<Col><Game state={state} dispatch={dispatch} playerName={user} /></Col>
</Row>
<Row>
<Col>
<button onClick={loadSample}>Load sample</button>
{' '}
<button onClick={resetGame}>Reset</button>
</Col>
</Row>
</>
: <Row>Loading...</Row>
: <Row><Col>Loading</Col></Row>
}
</Container>
}

View File

@ -1,6 +1,7 @@
.wonder-civ-outer {
background-position: center;
background-size: cover;
border: black solid;
}
.wonder-civ-struct {

View File

@ -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<Resource, string>([
['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 <div className='wonder-civ-outer' style={outerStyle}>
<div>{player.name}{player.wonder && `${player.wonder}`}</div>
<div>{[...pStats.resources].flatMap(([res, n]) => fill(Array(n), resEmojis.get(res)!)).join('')}</div>
<div>{pStats.shields}🛡</div>
<div>${player.gold}</div>
<div>{player.name}</div>
<div>{pStats.sciences.map(s => [...s].map(sci => sciEmojis.get(sci)!)).map(s =>
s.length === 1 ? s[0] : `(${s.join('/')})`
)}</div>
<div>{[...sumMaps(pStats.structObjs.map(s => new Map([[s.type, 1]])))].map(([type, n]) =>
<span className={`wonder-civ-struct wonder-civ-struct-type-${type.replaceAll(' ', '-')}`}>n</span>
<span className={`wonder-civ-struct wonder-civ-struct-type-${type.replaceAll(' ', '-')}`}>{n}</span>
)}</div>
</div>
}

View File

@ -44,13 +44,13 @@ export default function Game({ state, playerName, dispatch }: { state: State, pl
<Row>
<Col><Civ player={started.left} style='neighbor' /></Col>
<Col>
<div>{state?.age === undefined ? '' : `Age ${state.age + 1} ${state.age % 2 === 0 ? '🔃' : '🔄'}`}</div>
<div>{state.age === undefined ? '' : `Age ${state.age + 1} ${state.age % 2 === 0 ? '🔃' : '🔄'}`}</div>
<div><button>Discards</button></div>
</Col>
<Col><Civ player={started.right} style='neighbor' /></Col>
</Row>
<Row>
<Civ player={player} style='player' />
<Col><Civ player={player} style='player' /></Col>
</Row>
<Row>{player.hand?.map(s => structures.get(s)!)?.map(s =>
<Col>

View File

@ -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)