Skip to content

Commit

Permalink
Add ghost board
Browse files Browse the repository at this point in the history
  • Loading branch information
Oliveriver committed Aug 5, 2024
1 parent 2f869a6 commit f3af3f5
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 16 deletions.
1 change: 1 addition & 0 deletions client/src/components/GameRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const GameRoot = () => {
initialPositionX={initialOffsetX}
initialPositionY={initialOffsetY}
doubleClick={{ disabled: true }}
panning={{ excluded: ['input', 'select'] }}
>
{error && <WorldError error={error} retry={retry} isLoading={isLoading} />}
{!world && <WorldLoading />}
Expand Down
2 changes: 2 additions & 0 deletions client/src/components/context/OrderEntryContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export const OrderEntryContextProvider = ({ children }: PropsWithChildren) => {

useEffect(() => {
const handleKeyPress = (event: KeyboardEvent) => {
if (document.activeElement?.tagName === 'INPUT') return;

switch (event.key) {
case '1':
dispatch({ $type: OrderEntryActionType.SetMode, mode: InputMode.Hold });
Expand Down
18 changes: 6 additions & 12 deletions client/src/components/pages/JoinGamePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import Button from '../user-interface/common/Button';
import NationSelect from '../user-interface/NationSelect';
import GameContext from '../context/GameContext';
import Error from '../user-interface/common/Error';
import TextInput from '../user-interface/common/TextInput';
import { isInteger } from '../../utils/numberUtils';

type JoinGameProps = {
setViewOption: (option: SetupViewOption) => void;
Expand All @@ -17,15 +19,12 @@ const JoinGamePage = ({ setViewOption }: JoinGameProps) => {
const [player, setPlayer] = useState<Nation>();

const onGameIdChanged = (value: string) => {
const parsedValue = parseInt(value, 10);
if (
parsedValue.toString() !== value ||
Number.isNaN(parsedValue) ||
!Number.isInteger(parsedValue)
) {
if (!isInteger(value)) {
setGameId(undefined);
return;
}

const parsedValue = parseInt(value, 10);
setGameId(parsedValue);
};

Expand All @@ -42,12 +41,7 @@ const JoinGamePage = ({ setViewOption }: JoinGameProps) => {
return (
<div className="flex flex-col w-screen h-screen items-center gap-4 pt-24">
<img alt="Logo" src="./logo.png" className="w-64 pb-8" />
<input
type="text"
className="w-64 h-16 p-4 border-4 rounded-xl text-lg"
placeholder="Game ID"
onChange={(event) => onGameIdChanged(event.target.value)}
/>
<TextInput placeholder="Game ID" onChange={onGameIdChanged} />
<NationSelect selectedNation={player} setSelectedNation={setPlayer} />
<Button
text="Join"
Expand Down
15 changes: 15 additions & 0 deletions client/src/components/user-interface/common/TextInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
type TextInputProps = {
placeholder: string;
onChange: (value: string) => void;
};

const TextInput = ({ placeholder, onChange }: TextInputProps) => (
<input
type="text"
className="w-64 h-16 p-4 border-4 rounded-xl text-lg"
placeholder={placeholder}
onChange={(event) => onChange(event.target.value)}
/>
);

export default TextInput;
4 changes: 1 addition & 3 deletions client/src/components/world/boards/Board.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
minorBoardWidth,
} from '../../../utils/constants';
import Map from './Map';
import BoardData, { getBoardKey, getBoardName } from '../../../types/board';
import BoardData, { getBoardName } from '../../../types/board';
import Adjustment from './Adjustment';
import Nation, { getNationColour } from '../../../types/enums/nation';
import colours from '../../../utils/colours';
Expand All @@ -22,7 +22,6 @@ const Board = ({ board, isActive, winner }: BoardProps) => {
const showWinner = isActive && winner;

const width = phase === Phase.Winter ? minorBoardWidth : majorBoardWidth;
const key = getBoardKey(board);

return (
<div
Expand All @@ -34,7 +33,6 @@ const Board = ({ board, isActive, winner }: BoardProps) => {
}}
>
<div
id={key}
className="relative rounded-xl"
style={{
backgroundColor: colours.boardBackground,
Expand Down
118 changes: 118 additions & 0 deletions client/src/components/world/boards/BoardGhost.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { useState } from 'react';
import Board, { getBoardName } from '../../../types/board';
import Phase from '../../../types/enums/phase';
import colours from '../../../utils/colours';
import { boardBorderWidth, boardSeparation, majorBoardWidth } from '../../../utils/constants';
import Map from './Map';
import TextInput from '../../user-interface/common/TextInput';
import Select from '../../user-interface/common/Select';
import { isInteger } from '../../../utils/numberUtils';

type LocationInputProps = {
board: Board;
onTimelineEntered: (value: string) => void;
onYearEntered: (value: string) => void;
onPhaseEntered: (value: Phase) => void;
};

const LocationInput = ({
board,
onTimelineEntered,
onYearEntered,
onPhaseEntered,
}: LocationInputProps) => (
<div className="absolute -mt-20 flex flex-row gap-4 w-full justify-center">
<TextInput placeholder="Timeline" onChange={onTimelineEntered} />
<Select
options={[
{
text: 'Spring',
value: Phase.Spring,
},
{
text: 'Fall',
value: Phase.Fall,
},
]}
selectedValue={board.phase}
setValue={onPhaseEntered}
/>
<TextInput placeholder="Year" onChange={onYearEntered} />
</div>
);

type BoardGhostProps = {
initialTimeline: number;
initialYear: number;
initialPhase: Phase;
};

const BoardGhost = ({ initialTimeline, initialYear, initialPhase }: BoardGhostProps) => {
const [timeline, setTimeline] = useState(initialTimeline);
const [year, setYear] = useState(initialYear);
const [phase, setPhase] = useState(initialPhase);

const board = {
timeline,
year,
phase,
childTimelines: [],
centres: {},
units: {},
};

const onTimelineEntered = (value: string) => {
if (!isInteger(value, false)) {
setTimeline(initialTimeline);
return;
}

const parsedValue = parseInt(value, 10);
setTimeline(parsedValue);
};

const onYearEntered = (value: string) => {
if (!isInteger(value, false)) {
setYear(initialYear);
return;
}

const parsedValue = parseInt(value, 10);
setYear(parsedValue);
};

return (
<div
className="flex-col content-center relative"
style={{
minHeight: majorBoardWidth,
height: majorBoardWidth,
margin: boardSeparation / 2,
}}
>
<LocationInput
board={board}
onTimelineEntered={onTimelineEntered}
onYearEntered={onYearEntered}
onPhaseEntered={setPhase}
/>
<div
className="relative rounded-xl"
style={{
backgroundColor: colours.boardBackground,
minWidth: majorBoardWidth,
width: majorBoardWidth,
minHeight: majorBoardWidth,
height: majorBoardWidth,
borderWidth: boardBorderWidth,
borderColor: colours.boardBorder,
}}
>
<p className="text-md -mt-7">{getBoardName(board)}</p>
<Map board={board} isActive={false} />
</div>
</div>
);
};

export default BoardGhost;
18 changes: 17 additions & 1 deletion client/src/components/world/boards/BoardLayer.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { useContext } from 'react';
import { getNextPhase } from '../../../types/enums/phase';
import { getNextMajorPhase, getNextPhase } from '../../../types/enums/phase';
import { filterUnique } from '../../../utils/listUtils';
import Board from './Board';
import BoardSkip from './BoardSkip';
import WorldContext from '../../context/WorldContext';
import BoardGhost from './BoardGhost';
import OrderEntryContext from '../../context/OrderEntryContext';
import InputMode from '../../../types/enums/inputMode';

const BoardLayer = () => {
const { world } = useContext(WorldContext);
const { currentMode, currentOrder } = useContext(OrderEntryContext);
if (!world) return null;

const timelines = filterUnique(world.boards.map(({ timeline }) => timeline));
Expand All @@ -17,6 +21,17 @@ const BoardLayer = () => {
Object.values(board.units).some((unit) => unit.mustRetreat),
);

const selectedLocation = currentOrder?.location;
const showGhostBoard =
(currentMode === InputMode.Support || currentMode === InputMode.Convoy) && selectedLocation;
const ghostBoard = showGhostBoard && (
<BoardGhost
initialTimeline={selectedLocation.timeline}
initialYear={getNextMajorPhase(selectedLocation.year, selectedLocation.phase).year}
initialPhase={getNextMajorPhase(selectedLocation.year, selectedLocation.phase).phase}
/>
);

return (
<div className="flex flex-col w-screen h-screen">
{timelines.map((timeline) => (
Expand Down Expand Up @@ -50,6 +65,7 @@ const BoardLayer = () => {
<BoardSkip key={key} board={{ ...board, timeline }} />
);
})}
{timeline === selectedLocation?.timeline && ghostBoard}
</div>
))}
</div>
Expand Down
10 changes: 10 additions & 0 deletions client/src/types/enums/phase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ export const getNextPhase = (year: number, phase: Phase) => {
return { year: nextYear, phase: nextPhase };
};

export const getNextMajorPhase = (year: number, phase: Phase) => {
const nextYear = phase === Phase.Winter || phase === Phase.Fall ? year + 1 : year;
const nextPhase = {
[Phase.Spring]: Phase.Fall,
[Phase.Fall]: Phase.Spring,
[Phase.Winter]: Phase.Spring,
}[phase];
return { year: nextYear, phase: nextPhase };
};

export const getPhaseIndex = (phase: Phase) =>
({
[Phase.Spring]: 1,
Expand Down
13 changes: 13 additions & 0 deletions client/src/utils/numberUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* eslint-disable import/prefer-default-export */

export const isInteger = (value: string, allowNegative: boolean = true) => {
const parsedValue = parseInt(value, 10);

// None of these cover every case (e.g. whitespace), so use all of them
return (
parsedValue.toString() === value &&
!Number.isNaN(parsedValue) &&
Number.isInteger(parsedValue) &&
(allowNegative || parsedValue >= 0)
);
};

0 comments on commit f3af3f5

Please sign in to comment.