Sample data and initial work on player display
This commit is contained in:
parent
4d6ca264f7
commit
7f2405fd29
@ -1,12 +1,13 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import useKetchup from 'ketchup-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 './App.css';
|
||||||
import Game from '../Game/Game';
|
import Game from '../Game/Game';
|
||||||
import UserSelector from '../UserSelector/UserSelector';
|
import UserSelector from '../UserSelector/UserSelector';
|
||||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
import Container from 'react-bootstrap/Container';
|
import Container from 'react-bootstrap/Container';
|
||||||
import Row from 'react-bootstrap/Row'
|
import Row from 'react-bootstrap/Row'
|
||||||
|
import Col from 'react-bootstrap/Col'
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [state, dispatch] = useKetchup('ws://localhost:4000', reducer)
|
const [state, dispatch] = useKetchup('ws://localhost:4000', reducer)
|
||||||
@ -26,18 +27,38 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resetGame() {
|
function resetGame() {
|
||||||
|
if (window.confirm('Reset game?')) {
|
||||||
dispatch(resetAction())
|
dispatch(resetAction())
|
||||||
setUser(undefined)
|
setUser(undefined)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSample() {
|
||||||
|
if (window.confirm('Load sample game state?')) {
|
||||||
|
dispatch(loadSampleAction())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return <Container fluid>
|
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'} />
|
<UserSelector user={user} selectUser={selectUser} removeUser={removeUser} users={state?.players.map(p => p.name) ?? []} locked={state !== undefined && state.stage !== 'starting'} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
{state
|
{state
|
||||||
? <>
|
? <>
|
||||||
<Game state={state} dispatch={dispatch} playerName={user} />
|
<Row>
|
||||||
<button onClick={() => window.confirm('Reset game?') && resetGame()}> Reset</button>
|
<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>
|
</Container>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
.wonder-civ-outer {
|
.wonder-civ-outer {
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
border: black solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wonder-civ-struct {
|
.wonder-civ-struct {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { Player, playerStats, Resource, Science, sumMaps } from 'wonders-common';
|
import { Player, playerStats, Resource, Science, sumMaps } from 'wonders-common';
|
||||||
import fill from 'lodash/fill'
|
import fill from 'lodash/fill'
|
||||||
|
import './Civ.css'
|
||||||
|
|
||||||
const resEmojis = new Map<Resource, string>([
|
const resEmojis = new Map<Resource, string>([
|
||||||
['brick', '🧱'],
|
['brick', '🧱'],
|
||||||
@ -21,22 +22,20 @@ const wonderBgExts = ['jpg', 'webp']
|
|||||||
export default function Civ({ player, style }: { player: Player, style: 'player' | 'neighbor' | 'compact' }) {
|
export default function Civ({ player, style }: { player: Player, style: 'player' | 'neighbor' | 'compact' }) {
|
||||||
const pStats = useMemo(() => playerStats(player), [player])
|
const pStats = useMemo(() => playerStats(player), [player])
|
||||||
|
|
||||||
const outerStyle: React.CSSProperties = {
|
const outerStyle: React.CSSProperties = player.wonder === undefined ? {} : {
|
||||||
backgroundImage: player.wonder && wonderBgExts.map(ext => `url("/assets/wonders/${player.wonder}.${ext}")`).join(', '),
|
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(', '),
|
||||||
backgroundSize: player.wonder && 'cover',
|
|
||||||
backgroundPosition: player.wonder && 'center',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className='wonder-civ-outer' style={outerStyle}>
|
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.resources].flatMap(([res, n]) => fill(Array(n), resEmojis.get(res)!)).join('')}</div>
|
||||||
<div>{pStats.shields}🛡️</div>
|
<div>{pStats.shields}🛡️</div>
|
||||||
<div>${player.gold}</div>
|
<div>${player.gold}</div>
|
||||||
<div>{player.name}</div>
|
|
||||||
<div>{pStats.sciences.map(s => [...s].map(sci => sciEmojis.get(sci)!)).map(s =>
|
<div>{pStats.sciences.map(s => [...s].map(sci => sciEmojis.get(sci)!)).map(s =>
|
||||||
s.length === 1 ? s[0] : `(${s.join('/')})`
|
s.length === 1 ? s[0] : `(${s.join('/')})`
|
||||||
)}</div>
|
)}</div>
|
||||||
<div>{[...sumMaps(pStats.structObjs.map(s => new Map([[s.type, 1]])))].map(([type, n]) =>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,13 +44,13 @@ export default function Game({ state, playerName, dispatch }: { state: State, pl
|
|||||||
<Row>
|
<Row>
|
||||||
<Col><Civ player={started.left} style='neighbor' /></Col>
|
<Col><Civ player={started.left} style='neighbor' /></Col>
|
||||||
<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>
|
<div><button>Discards</button></div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col><Civ player={started.right} style='neighbor' /></Col>
|
<Col><Civ player={started.right} style='neighbor' /></Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Civ player={player} style='player' />
|
<Col><Civ player={player} style='player' /></Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>{player.hand?.map(s => structures.get(s)!)?.map(s =>
|
<Row>{player.hand?.map(s => structures.get(s)!)?.map(s =>
|
||||||
<Col>
|
<Col>
|
||||||
|
|||||||
@ -159,13 +159,13 @@ export function buildDeck(age: number, numPlayers: number) {
|
|||||||
throw new Error(`Unsupported player count ${numPlayers}`)
|
throw new Error(`Unsupported player count ${numPlayers}`)
|
||||||
}
|
}
|
||||||
const cards: Structure[] = [...structures.values()].flatMap(structure =>
|
const cards: Structure[] = [...structures.values()].flatMap(structure =>
|
||||||
structure.appears?.[age - 1]
|
structure.appears?.[age]
|
||||||
?.filter(minPlayers => numPlayers <= minPlayers)
|
?.filter(minPlayers => numPlayers <= minPlayers)
|
||||||
?.map(() => structure)
|
?.map(() => structure)
|
||||||
?? []
|
?? []
|
||||||
)
|
)
|
||||||
if (age === numAges - 1) {
|
if (age === numAges - 1) {
|
||||||
cards.push(...sampleSize(structuresGuilds, numPlayers + 2))
|
cards.push(...sampleSize(structuresGuilds, numPlayers + 2)) // BAD
|
||||||
}
|
}
|
||||||
return cards
|
return cards
|
||||||
}
|
}
|
||||||
@ -231,7 +231,7 @@ export function playerStats(p: Player) {
|
|||||||
return {
|
return {
|
||||||
structObjs,
|
structObjs,
|
||||||
resources: sumMaps(structObjs.map(s => s.resources)),
|
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)),
|
sciences: compact(structObjs.map(s => s.sciences)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -250,6 +250,134 @@ export const initial: State = {
|
|||||||
players: [],
|
players: [],
|
||||||
discard: [],
|
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 {
|
export interface BaseAction {
|
||||||
type: string
|
type: string
|
||||||
@ -378,7 +506,7 @@ export function countPoints(state: State, playerIdx: number): number {
|
|||||||
}
|
}
|
||||||
function beginAge(state: State): State {
|
function beginAge(state: State): State {
|
||||||
const age = state.age === undefined ? 0 : state.age + 1
|
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 handSize = Math.ceil(deck.length / state.players.length)
|
||||||
const hands = chunk(deck, handSize)
|
const hands = chunk(deck, handSize)
|
||||||
return {
|
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
|
return newState.players.every(p => p.turnPlan !== undefined) ? doTurn(newState) : newState
|
||||||
},
|
},
|
||||||
reset: () => initial,
|
'reset': () => initial,
|
||||||
'load sample': () => ({
|
'load sample': () => sampleState,
|
||||||
...initial, // TODO
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
export function reducer(state: State, action: Action): State {
|
export function reducer(state: State, action: Action): State {
|
||||||
return reducers[action.type](state, action)
|
return reducers[action.type](state, action)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user