Skip to content

Commit

Permalink
Update player list to reflect new victory condition
Browse files Browse the repository at this point in the history
  • Loading branch information
Oliveriver committed Aug 1, 2024
1 parent d80edd5 commit 1ab0294
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 66 deletions.
4 changes: 1 addition & 3 deletions client/src/components/interface/OrderList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@ const OrderList = () => {
const timelines = filterUnique(orders.map(({ location }) => location.timeline)).sort();

const maxHeight = window.innerHeight - 168;
const scrollBehaviour =
scrollRef.current && scrollRef.current.scrollHeight > maxHeight ? 'scroll' : 'hidden';

return (
<div
ref={scrollRef}
className="absolute right-10 bottom-32 flex flex-col gap-4 items-end"
style={{
maxHeight,
overflowY: scrollBehaviour,
overflowY: 'auto',
}}
>
{timelines.map((timeline) => (
Expand Down
94 changes: 48 additions & 46 deletions client/src/components/interface/PlayerList.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,67 @@
import { useContext } from 'react';
import regions from '../../data/regions';
import Nation, { getNationColour } from '../../types/enums/nation';
import { getLatestPhase } from '../../types/enums/phase';
import { useContext, useRef, useState } from 'react';
import Nation from '../../types/enums/nation';
import colours from '../../utils/colours';
import { filterUnique } from '../../utils/listUtils';
import WorldContext from '../context/WorldContext';
import GameDetails from './GameDetails';
import { getActiveBoards } from '../../types/board';
import { filterUnique } from '../../utils/listUtils';
import PlayerListItem from './PlayerListItem';

const PlayerList = () => {
const { world } = useContext(WorldContext);
if (!world) return null;

const timelines = filterUnique(world.boards.map(({ timeline }) => timeline));
const activeBoards = timelines.map((timeline) =>
world.boards
.filter((board) => board.timeline === timeline)
.reduce((board1, board2) => {
if (board1.year > board2.year) return board1;
if (board2.year > board1.year) return board2;
const scrollRef = useRef<HTMLDivElement>(null);
const [expandedPlayers, setExpandedPlayers] = useState<Nation[]>([]);

return getLatestPhase(board1.phase, board2.phase) === board1.phase ? board1 : board2;
}),
);

// TODO sort out new win condition
const totalCentres =
activeBoards.length * Object.values(regions).filter((region) => region.isSupplyCentre).length;
if (!world) return null;
const { winner } = world;

const winPercentages = Object.values(Nation)
.map((nation) => {
const centreCount = activeBoards.flatMap((board) =>
Object.values(board.centres).filter((owner) => owner === nation),
).length;
const activeBoards = getActiveBoards(world.boards);
const playerCentres = Object.values(Nation)
.map((nation) => ({
player: nation,
centres: filterUnique(
activeBoards.flatMap((board) =>
Object.keys(board.centres)
.filter((region) => board.centres[region] === nation)
.sort(),
),
),
}))
.sort((player1, player2) => player2.centres.length - player1.centres.length);

return {
nation,
percentage: 100 * (centreCount / totalCentres),
};
})
.sort((player1, player2) => player2.percentage - player1.percentage);
const maxHeight = window.innerHeight - 272;

return (
<div className="absolute left-10 top-10 flex flex-col gap-4">
<GameDetails />
<div
ref={scrollRef}
className="flex flex-col gap-2 p-4 rounded w-max"
style={{ backgroundColor: colours.uiOverlay }}
style={{
backgroundColor: colours.uiOverlay,
maxHeight,
overflowY: 'auto',
}}
>
{winPercentages.map(({ nation, percentage }) => (
<div
key={nation}
className="text-lg flex"
style={{
opacity: percentage > 0 ? 1 : 0.3,
color: getNationColour(nation),
}}
>
<p className="min-w-14 font-bold">{`${Math.round(percentage)}%`}</p>
<p>{nation}</p>
</div>
))}
{playerCentres.map(({ player, centres }) => {
const isExpanded = expandedPlayers.includes(player);
return (
<PlayerListItem
key={player}
player={player}
centres={centres}
winner={winner}
isExpanded={isExpanded}
toggleExpand={() =>
setExpandedPlayers(
isExpanded
? expandedPlayers.filter((nation) => nation !== player)
: [...expandedPlayers, player],
)
}
/>
);
})}
</div>
</div>
);
Expand Down
49 changes: 49 additions & 0 deletions client/src/components/interface/PlayerListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Nation, { getNationColour } from '../../types/enums/nation';
import { victoryRequiredCentreCount } from '../../utils/constants';
import ExpandButton from './common/ExpandButton';

type PlayerListItemProps = {
player: Nation;
centres: string[];
winner: Nation | null;
isExpanded: boolean;
toggleExpand: () => void;
};

const PlayerListItem = ({
player,
centres,
winner,
isExpanded,
toggleExpand,
}: PlayerListItemProps) => {
const centreCount = centres.length;
const isEliminated = centreCount === 0 || (winner && player !== winner);
const colour = getNationColour(player);

return (
<>
<div
className="text-lg flex items-center"
style={{
opacity: isEliminated ? 0.3 : 1,
color: colour,
}}
>
<p className="min-w-20 text-start">{player}</p>
<p className="min-w-16 font-bold text-end">
{`${centreCount}/${victoryRequiredCentreCount}`}
</p>
<ExpandButton colour={colour} isExpanded={isExpanded} toggleExpand={toggleExpand} />
</div>
{isExpanded &&
centres.map((centre) => (
<p className="text-sm ml-2 -mt-1" style={{ color: colour }}>
{centre}
</p>
))}
</>
);
};

export default PlayerListItem;
28 changes: 28 additions & 0 deletions client/src/components/interface/common/ExpandButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import colours from '../../../utils/colours';

type ExpandButtonProps = {
colour: string;
isExpanded: boolean;
toggleExpand: () => void;
};

const ExpandButton = ({ colour, isExpanded, toggleExpand }: ExpandButtonProps) => (
<button
type="button"
onClick={toggleExpand}
className="relative w-3.5 h-3.5 rounded-full ml-4 opacity-30 hover:opacity-100 cursor-pointer"
style={{ backgroundColor: colour }}
aria-label="Expand item"
>
<div
className={`absolute h-0.5 w-[5px] top-1.5 left-[3px] rounded ${isExpanded ? '-rotate-45' : 'rotate-45'}`}
style={{ backgroundColor: colours.iconForeground }}
/>
<div
className={`absolute h-0.5 w-[5px] top-1.5 right-[3px] rounded ${isExpanded ? 'rotate-45' : '-rotate-45'}`}
style={{ backgroundColor: colours.iconForeground }}
/>
</button>
);

export default ExpandButton;
2 changes: 1 addition & 1 deletion client/src/components/interface/common/RemoveButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const RemoveButton = ({ isDisabled, remove }: RemoveButtonProps) => (
onClick={remove}
className="relative w-3.5 h-3.5 rounded-full ml-3 opacity-30 hover:opacity-100 cursor-pointer"
style={{ backgroundColor: colours.iconDelete, pointerEvents: isDisabled ? 'none' : 'all' }}
aria-label="Delete order"
aria-label="Delete item"
disabled={isDisabled}
>
<div
Expand Down
1 change: 0 additions & 1 deletion client/src/hooks/useRegionSvg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ import War from '../assets/map/War.svg?react';
import WES from '../assets/map/WES.svg?react';
import Yor from '../assets/map/Yor.svg?react';

// TODO add license for use
const useRegionSvg = (id: string) =>
({
ADR,
Expand Down
16 changes: 3 additions & 13 deletions client/src/hooks/useSetAvailableInputModes.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useContext, useEffect } from 'react';
import Phase, { getLatestPhase } from '../types/enums/phase';
import { filterUnique } from '../utils/listUtils';
import Phase from '../types/enums/phase';
import InputMode from '../types/enums/inputMode';
import { OrderEntryActionType } from '../types/context/orderEntryAction';
import OrderEntryContext from '../components/context/OrderEntryContext';
import WorldContext from '../components/context/WorldContext';
import { getActiveBoards } from '../types/board';

const useSetAvailableInputModes = () => {
const { world, isLoading, error } = useContext(WorldContext);
Expand All @@ -13,17 +13,7 @@ const useSetAvailableInputModes = () => {
const boards = world && !isLoading && !error ? world.boards : [];
const winner = world?.winner;

const timelines = filterUnique(boards.map(({ timeline }) => timeline));
const activeBoards = timelines.map((timeline) =>
boards
.filter((board) => board.timeline === timeline)
.reduce((board1, board2) => {
if (board1.year > board2.year) return board1;
if (board2.year > board1.year) return board2;

return getLatestPhase(board1.phase, board2.phase) === board1.phase ? board1 : board2;
}),
);
const activeBoards = getActiveBoards(boards);

const hasMajorBoard = !winner && activeBoards.some((board) => board.phase !== Phase.Winter);
const hasMinorBoard = !winner && activeBoards.some((board) => board.phase === Phase.Winter);
Expand Down
16 changes: 16 additions & 0 deletions client/src/types/board.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Nation from './enums/nation';
import Unit from './unit';
import Location, { getLocationKey } from './location';
import { filterUnique } from '../utils/listUtils';
import { getLatestPhase } from './enums/phase';

type Board = Omit<Location, 'region'> & {
childTimelines: number[];
Expand All @@ -13,4 +15,18 @@ export const getBoardKey = (board: Board | Omit<Location, 'region'>) => {
return getLocationKey({ timeline, year, phase, region: '' });
};

export const getActiveBoards = (boards: Board[]) => {
const timelines = filterUnique(boards.map(({ timeline }) => timeline));
return timelines.map((timeline) =>
boards
.filter((board) => board.timeline === timeline)
.reduce((board1, board2) => {
if (board1.year > board2.year) return board1;
if (board2.year > board1.year) return board2;

return getLatestPhase(board1.phase, board2.phase) === board1.phase ? board1 : board2;
}),
);
};

export default Board;
9 changes: 7 additions & 2 deletions client/src/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
// Gameplay

export const startYear = 1901;
export const victoryRequiredCentreCount = 18;

// UI

export const initialScale = 0.8;
export const orderFocusScale = 1.5;

export const majorBoardWidth = 1000; // TODO stop everything breaking if this changes
export const minorBoardWidth = 650;
export const boardSeparation = 400;
export const boardBorderWidth = 32; // TODO stop everything breaking if this changes

export const unitWidth = 28;
export const boardArrowWidth = 200;
export const orderArrowStartSeparation = 20;
export const orderArrowEndSeparation = 10;

// API

// TODO change both of these for production
export const refetchInterval = 2000;
export const refetchAttempts = 3;
1 change: 1 addition & 0 deletions server/Repositories/WorldRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public async Task AddOrders(int gameId, Nation[] players, IEnumerable<Order> ord
var newPlayersSubmitted = players.Where(p => !game.PlayersSubmitted.Contains(p));
game.PlayersSubmitted = [.. game.PlayersSubmitted, .. newPlayersSubmitted];

// TODO figure out how to deal with player elimination (and resurrection?)
if (game.PlayersSubmitted.Count == Constants.Nations.Count)
{
logger.LogInformation("Adjudicating game {GameId}", gameId);
Expand Down

0 comments on commit 1ab0294

Please sign in to comment.