Build out pregame interface

This commit is contained in:
Kenneth Allen 2021-10-17 02:35:47 +11:00
parent e96fbc27ba
commit d1dc1db234
4 changed files with 94 additions and 36 deletions

View File

@ -25,12 +25,17 @@ export default function App() {
} }
} }
function resetGame() {
dispatch(resetAction())
setUser(undefined)
}
return <Container fluid> return <Container fluid>
<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'} />
{state {state
? <> ? <>
<Game state={state} dispatch={dispatch} playerName={user} /> <Game state={state} dispatch={dispatch} playerName={user} />
<button onClick={() => window.confirm('Reset game?') && dispatch(resetAction())}> Reset</button> <button onClick={() => window.confirm('Reset game?') && resetGame()}> Reset</button>
</> </>
: <Row>Loading...</Row> : <Row>Loading...</Row>
} }

View File

@ -21,8 +21,10 @@ 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 = { const outerStyle: React.CSSProperties = {
backgroundImage: player.wonder && wonderBgExts.map(ext => `url(/assets/wonders/${player.wonder}.${ext})`).join(', '), backgroundImage: player.wonder && 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}>

View File

@ -1,10 +1,11 @@
/* eslint-disable react/style-prop-object */ /* eslint-disable react/style-prop-object */
import { Dispatch } from 'ketchup-react' import { Dispatch } from 'ketchup-react'
import { Action, Player, State, structures } from 'wonders-common' import { Action, chooseWonderAction, maxPlayers, minPlayers, Player, startGameAction, State, structures, wonders } from 'wonders-common'
import Civ from '../Civ/Civ' import Civ from '../Civ/Civ'
import Card from '../Card/Card' import Card from '../Card/Card'
import Row from 'react-bootstrap/Row' import Row from 'react-bootstrap/Row'
import Col from 'react-bootstrap/Col' import Col from 'react-bootstrap/Col'
import { ErrorMessage, Field, Form, Formik } from 'formik'
function getDistant<T>(arr: T[], idx: number) { function getDistant<T>(arr: T[], idx: number) {
switch (idx) { switch (idx) {
@ -17,12 +18,12 @@ function getDistant<T>(arr: T[], idx: number) {
function DistantCivs({ players }: { players: Player[] }) { function DistantCivs({ players }: { players: Player[] }) {
return <Row>{players.map(p => return <Row>{players.map(p =>
<Col key={p.name}> <Col key={p.name}>
<Civ player={p} style='compact'/> <Civ player={p} style='compact' />
</Col> </Col>
)}</Row> )}</Row>
} }
export default function Game({ state, playerName }: { state: State, playerName?: string, dispatch: Dispatch<Action> }) { export default function Game({ state, playerName, dispatch }: { state: State, playerName?: string, dispatch: Dispatch<Action> }) {
const playerIdx: number | undefined = state.players.findIndex(p => p.name === playerName) const playerIdx: number | undefined = state.players.findIndex(p => p.name === playerName)
const player: Player | undefined = state.players[playerIdx] const player: Player | undefined = state.players[playerIdx]
const started = state.stage !== 'play' ? undefined : { const started = state.stage !== 'play' ? undefined : {
@ -31,28 +32,74 @@ export default function Game({ state, playerName }: { state: State, playerName?:
distant: getDistant(state.players, playerIdx), distant: getDistant(state.players, playerIdx),
} }
if (started && player) { function isWonderInUse(wonder: string) {
return state.players.filter(p => p.name !== playerName).map(p => p.wonder).includes(wonder)
}
if (!player) {
return <DistantCivs players={state.players} />
} else if (started) {
return <> return <>
<DistantCivs players={started.distant}/> <DistantCivs players={started.distant} />
<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'/> <Civ player={player} style='player' />
</Row> </Row>
<Row>{player.hand.map(s => structures.get(s)!).map(s => <Row>{player.hand?.map(s => structures.get(s)!)?.map(s =>
<Col> <Col>
<Card structure={s}/> <Card structure={s} />
</Col> </Col>
)}</Row> )}</Row>
</> </>
} else { } else {
return <DistantCivs players={state.players}/> return <>
// TODO Pick wonder <Row>
<DistantCivs players={state.players} />
</Row>
<Row>
<Formik
initialValues={{ wonder: player.wonder ?? '' }}
validate={values => {
const errors: Partial<typeof values> = {}
if (values.wonder.length === 0) {
errors.wonder = 'Wonder not selected.'
}
if (isWonderInUse(values.wonder)) {
errors.wonder = 'Wonder already in use.'
}
return errors
}}
onSubmit={({ wonder }) => dispatch(chooseWonderAction(player.name, wonder))}
>{() => <Form>
<button type="submit">Choose wonder</button>
{' '}
<Field as="select" name="wonder">
{['', ...wonders.keys()].map(w =>
<option key={w} disabled={w.length === 0 || isWonderInUse(w)}>{w}</option>
)}
</Field>
{' '}
<ErrorMessage name="wonder" />
</Form>}
</Formik>
</Row>
<Row>
<Col><button onClick={() => dispatch(startGameAction(state.players.length))}
disabled={started !== undefined
|| state.players.length < minPlayers
|| state.players.length > maxPlayers
|| state.players.some(p => p.wonder === undefined)}>
Start Game
</button></Col>
</Row>
{/* TODO Pick wonder */}
</>
} }
} }

View File

@ -2,6 +2,7 @@ import { ErrorMessage, Field, Form, Formik } from 'formik';
import { Dispatch, useState } from 'react'; import { Dispatch, useState } from 'react';
import sample from 'lodash/sample' import sample from 'lodash/sample'
import Row from 'react-bootstrap/Row'; import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
const namePlaceholders = [ const namePlaceholders = [
'Alexander the Great', 'Alexander the Great',
@ -21,7 +22,7 @@ export default function UserSelector({ users, user, selectUser, removeUser, lock
const [namePlaceholder] = useState(() => sample(namePlaceholders)) const [namePlaceholder] = useState(() => sample(namePlaceholders))
return <Row> return <Row>
{user {user
? <>Playing as {user} <button onClick={() => selectUser(undefined)}>Change</button> <button onClick={removeUser} disabled={locked}>Remove</button></> ? <Col>Playing as {user} <button onClick={() => selectUser(undefined)}>Change</button> <button onClick={removeUser} disabled={locked}>Remove</button></Col>
: <Formik : <Formik
initialValues={{ newUser: '' }} initialValues={{ newUser: '' }}
validate={values => { validate={values => {
@ -36,7 +37,10 @@ export default function UserSelector({ users, user, selectUser, removeUser, lock
}} }}
>{() => <Form> >{() => <Form>
<button type="submit">Play as</button> <button type="submit">Play as</button>
<Field type="text" name="newUser" placeholder={namePlaceholder}/> <ErrorMessage name="newUser"/> {' '}
<Field type="text" name="newUser" placeholder={namePlaceholder} />
{' '}
<ErrorMessage name="newUser" />
</Form>} </Form>}
</Formik> </Formik>
} }