Build out pregame interface
This commit is contained in:
parent
e96fbc27ba
commit
d1dc1db234
@ -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>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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}>
|
||||||
|
|||||||
@ -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 */}
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user