Skip to content

Commit

Permalink
Fix distribution of attribute ranges.
Browse files Browse the repository at this point in the history
GitOrigin-RevId: 8320e7255bd32f52cee372a924b1628385f8a276
  • Loading branch information
cpojer committed May 7, 2024
1 parent b71438e commit f66bbdc
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 57 deletions.
41 changes: 34 additions & 7 deletions athena/lib/getAttributeRange.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,61 @@
import maxBy from '@deities/hephaestus/maxBy.tsx';

export type AttributeRangeValue = 1 | 2 | 3 | 4 | 5;
export type AttributeRange = 1 | 2 | 3 | 4 | 5;
export type AttributeRangeWithZero = 0 | 1 | 2 | 3 | 4 | 5;
export type LargeAttributeRangeWithZero =
| AttributeRangeWithZero
| 6
| 7
| 8
| 9
| 10;

export function validateAttributeRange(
value?: number | null,
): value is AttributeRangeValue {
): value is AttributeRange {
return !!value && value >= 1 && value <= 5;
}

export default function getAttributeRange<T>(
list: ReadonlyArray<T>,
extract: (entry: T) => number,
min: number = 0,
length: number = 5,
) {
const entry = maxBy(list, extract);
if (!entry) {
return [];
}

const step = (extract(entry) - min) / 4;
const step = (extract(entry) - min) / (length - 1);
return Array.from(
{ length: 5 },
{ length },
(_, index) => min + index * step - (index > 0 ? step / 2 : 0),
);
}

export function getAttributeRangeValue(
range: ReadonlyArray<number>,
value: number,
): AttributeRangeValue {
const index = range.findIndex((item) => item >= value);
return (index === -1 ? range.length : index + 1) as AttributeRangeValue;
) {
if (value < range[0]) {
return 0;
}

const index = range.findLastIndex((item) => item <= value);
return (index === -1 ? range.length : index + 1) as AttributeRangeWithZero;
}

export function getLargeAttributeRangeValue(
range: ReadonlyArray<number>,
value: number,
) {
if (value < range[0]) {
return 0;
}

const index = range.findLastIndex((item) => item <= value);
return (
index === -1 ? range.length : index + 1
) as LargeAttributeRangeWithZero;
}
4 changes: 2 additions & 2 deletions hera/campaign/CampaignEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Scenario } from '@deities/apollo/Effects.tsx';
import {
AttributeRangeValue,
AttributeRangeWithZero,
validateAttributeRange,
} from '@deities/athena/lib/getAttributeRange.tsx';
import { DoubleSize, TileSize } from '@deities/athena/map/Configuration.tsx';
Expand Down Expand Up @@ -101,7 +101,7 @@ export default function CampaignEditor({
const [, startTransition] = useTransition();
const [hasChanges, setHasChanges] = useState(!data.id);
const [mapHasChanges, setMapHasChanges] = useState(false);
const [difficulty, setDifficulty] = useState<AttributeRangeValue | 0>(
const [difficulty, setDifficulty] = useState<AttributeRangeWithZero>(
validateAttributeRange(data.difficulty) ? data.difficulty : 0,
);
const [description, setDescription] = useState<string>(
Expand Down
9 changes: 6 additions & 3 deletions hera/campaign/panels/CampaignEditorControlPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { AttributeRangeValue } from '@deities/athena/lib/getAttributeRange.tsx';
import {
AttributeRange,
AttributeRangeWithZero,
} from '@deities/athena/lib/getAttributeRange.tsx';
import UnknownTypeError from '@deities/hephaestus/UnknownTypeError.tsx';
import { TypeaheadDataSource } from '@deities/ui/Typeahead.tsx';
import BottomDrawer from '../../bottom-drawer/BottomDrawer.tsx';
Expand Down Expand Up @@ -32,14 +35,14 @@ export default function CampaignEditorControlPanel({
campaignExists: boolean;
campaignName: string;
description: string;
difficulty: AttributeRangeValue | 0;
difficulty: AttributeRangeWithZero;
exportMaps: () => void;
isAdmin?: boolean;
playStyle: PlayStyleType | null;
saveCampaign: (type?: 'Export') => void;
setCampaignName: (name: string) => void;
setDescription: (description: string) => void;
setDifficulty: (rating: AttributeRangeValue) => void;
setDifficulty: (rating: AttributeRange) => void;
setPlayStyle: (playStyle: PlayStyleType | null) => void;
setTags: (tags: ReadonlyArray<string>) => void;
setUsers: (users: ReadonlyArray<UserNode>) => void;
Expand Down
9 changes: 6 additions & 3 deletions hera/campaign/panels/CampaignEditorSettingsPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { AttributeRangeValue } from '@deities/athena/lib/getAttributeRange.tsx';
import {
AttributeRange,
AttributeRangeWithZero,
} from '@deities/athena/lib/getAttributeRange.tsx';
import { DoubleSize } from '@deities/athena/map/Configuration.tsx';
import Box from '@deities/ui/Box.tsx';
import InlineLink from '@deities/ui/InlineLink.tsx';
Expand Down Expand Up @@ -49,14 +52,14 @@ export default function CampaignEditorSettingsPanel({
campaignExists: boolean;
campaignName: string;
description: string;
difficulty: AttributeRangeValue | 0;
difficulty: AttributeRangeWithZero;
exportMaps: () => void;
isAdmin?: boolean;
playStyle: PlayStyleType | null;
saveCampaign: (type?: 'Export') => void;
setCampaignName: (name: string) => void;
setDescription: (description: string) => void;
setDifficulty: (rating: AttributeRangeValue) => void;
setDifficulty: (rating: AttributeRange) => void;
setPlayStyle: (playStyle: PlayStyleType | null) => void;
setTags: (tags: ReadonlyArray<string>) => void;
setUsers: (users: ReadonlyArray<UserNode>) => void;
Expand Down
7 changes: 5 additions & 2 deletions hera/card/BuildingCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ const costRange = getAttributeRange(
extractCost,
minBy(createableBuildings, extractCost)?.configuration.cost || 0,
);
const defenseRange = getAttributeRange(buildings, ({ defense }) => defense);
const defenseRange = getAttributeRange(
buildings,
({ defense }) => defense,
0.01,
);

export default memo(function BuildingCard({
biome,
Expand Down Expand Up @@ -161,7 +165,6 @@ export default memo(function BuildingCard({
<Range
end
value={getAttributeRangeValue(defenseRange, info.defense)}
zero
/>
{limit > 0 ? (
<>
Expand Down
106 changes: 83 additions & 23 deletions hera/card/Range.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { AttributeRangeValue } from '@deities/athena/lib/getAttributeRange.tsx';
import {
AttributeRange,
AttributeRangeWithZero,
LargeAttributeRangeWithZero,
} from '@deities/athena/lib/getAttributeRange.tsx';
import Breakpoints from '@deities/ui/Breakpoints.tsx';
import { SquareButtonStyle } from '@deities/ui/Button.tsx';
import { applyVar } from '@deities/ui/cssVar.tsx';
Expand Down Expand Up @@ -26,6 +30,16 @@ const StarEmpty = (
/>
);

const StarHalf = (
<Icon
icon={{
body: `<path fill="currentColor" d="M11 1h2v1h-2V1ZM13 2h1v2h-1V2ZM10 2h2v2h-2V2ZM4 10h8v2H4v-2ZM6 12h6v6H6v-6ZM4 18h8v2H4v-2ZM14 4h1v2h-1V4Z"/><path fill="currentColor" d="M9 4h3v15H9V4ZM15 6h1v2h-1V6ZM8 6h1v2H8V6Z"/><path fill="currentColor" d="M2 8h10v2H2V8ZM16 8h6v1h-6V8ZM4 20h6v1H4v-1ZM1 9h1v1H1V9ZM2 10h1v1H2v-1ZM3 11h1v1H3v-1ZM4 12h1v1H4v-1ZM5 13h1v1H5v-1ZM18 13h1v1h-1v-1ZM19 12h1v1h-1v-1ZM20 11h1v1h-1v-1ZM20 10h1v1h-1v-1ZM18 12h1v1h-1v-1ZM5 12h1v1H5v-1ZM3 10h1v1H3v-1ZM21 10h1v1h-1v-1ZM22 9h1v1h-1V9ZM6 14h1v2H6v-2ZM5 16h1v2H5v-2ZM4 18h1v2H4v-2ZM19 18h1v2h-1v-2ZM18 16h1v2h-1v-2ZM17 14h1v2h-1v-2ZM3 20h1v3H3v-3ZM20 20h1v3h-1v-3ZM4 22h2v1H4v-1ZM6 21h2v1H6v-1ZM4 21h2v1H4v-1Z"/><path fill="currentColor" d="M8 20h2v1H8v-1ZM14 20h2v1h-2v-1ZM16 21h2v1h-2v-1ZM18 22h2v1h-2v-1ZM10 19h4v1h-4v-1Z"/>`,
height: 24,
width: 24,
}}
/>
);

const StarFull = (
<Icon
icon={{
Expand All @@ -36,27 +50,38 @@ const StarFull = (
/>
);

const toValue = (value: AttributeRangeWithZero) => (value === 0 ? 1 : value);

const starIcon = (value: number, boundary: number) =>
value >= boundary ? StarFull : StarEmpty;

const halfStarIcon = (value: number, boundary: number) =>
value >= boundary ? (value >= boundary + 1 ? StarFull : StarHalf) : StarEmpty;

export default memo(function Range({
end,
invert,
invertColors,
value,
wide,
zero,
}: {
end?: true;
invert?: true;
invertColors?: true;
value: AttributeRangeValue;
value: AttributeRangeWithZero;
wide?: true;
zero?: true;
}) {
if (invert) {
value = (6 - value) as AttributeRangeValue;
value = (6 - toValue(value)) as AttributeRange;
}

const style = {
color: colors[invertColors ? ((6 - value) as AttributeRangeValue) : value],
color:
colors[
(invertColors ? 6 - toValue(value) : toValue(value)) as AttributeRange
],
};

return (
<Stack
alignEnd
Expand All @@ -68,19 +93,56 @@ export default memo(function Range({
start
>
<div className={rangeStyle} style={style}>
{value >= 1 && (!zero || value > 1) ? StarFull : StarEmpty}
{starIcon(value, 1)}
</div>
<div className={rangeStyle} style={style}>
{starIcon(value, 2)}
</div>
<div className={rangeStyle} style={style}>
{starIcon(value, 3)}
</div>
<div className={rangeStyle} style={style}>
{starIcon(value, 4)}
</div>
<div className={rangeStyle} style={style}>
{starIcon(value, 5)}
</div>
</Stack>
);
});

export const LargeRange = memo(function LargeRange({
end,
value,
}: {
end?: true;
value: LargeAttributeRangeWithZero;
}) {
const style = {
color: colors[toValue(Math.ceil(value / 2) as AttributeRange)],
};

return (
<Stack
alignEnd
className={cx(containerStyle, end && endStyle)}
nowrap
start
>
<div className={rangeStyle} style={style}>
{halfStarIcon(value, 1)}
</div>
<div className={rangeStyle} style={style}>
{value >= 2 ? StarFull : StarEmpty}
{halfStarIcon(value, 3)}
</div>
<div className={rangeStyle} style={style}>
{value >= 3 ? StarFull : StarEmpty}
{halfStarIcon(value, 5)}
</div>
<div className={rangeStyle} style={style}>
{value >= 4 ? StarFull : StarEmpty}
{halfStarIcon(value, 7)}
</div>
<div className={rangeStyle} style={style}>
{value >= 5 ? StarFull : StarEmpty}
{halfStarIcon(value, 9)}
</div>
</Stack>
);
Expand All @@ -92,15 +154,13 @@ export const RangeSelector = memo(function RangeSelector({
value: initialValue,
}: {
invert?: true;
onSelect: (value: AttributeRangeValue) => void;
value?: AttributeRangeValue | 0;
onSelect: (value: AttributeRange) => void;
value?: AttributeRangeWithZero;
}) {
const [isActive, setIsActive] = useState(false);
const [value, setValue] = useState<AttributeRangeValue | 0>(
initialValue || 0,
);
const color = (value: AttributeRangeValue) =>
colors[invert ? ((6 - value) as AttributeRangeValue) : value];
const [value, setValue] = useState<AttributeRangeWithZero>(initialValue || 0);
const color = (value: AttributeRange) =>
colors[invert ? ((6 - value) as AttributeRange) : value];

return (
<Stack
Expand All @@ -125,7 +185,7 @@ export const RangeSelector = memo(function RangeSelector({
onPointerEnter={() => setValue(1)}
style={{ color: color(1) }}
>
{value >= 1 ? StarFull : StarEmpty}
{starIcon(value, 1)}
</div>
<div
className={cx(
Expand All @@ -138,7 +198,7 @@ export const RangeSelector = memo(function RangeSelector({
onPointerEnter={() => setValue(2)}
style={{ color: color(2) }}
>
{value >= 2 ? StarFull : StarEmpty}
{starIcon(value, 2)}
</div>
<div
className={cx(
Expand All @@ -151,7 +211,7 @@ export const RangeSelector = memo(function RangeSelector({
onPointerEnter={() => setValue(3)}
style={{ color: color(3) }}
>
{value >= 3 ? StarFull : StarEmpty}
{starIcon(value, 3)}
</div>
<div
className={cx(
Expand All @@ -164,7 +224,7 @@ export const RangeSelector = memo(function RangeSelector({
onPointerEnter={() => setValue(4)}
style={{ color: color(4) }}
>
{value >= 4 ? StarFull : StarEmpty}
{starIcon(value, 4)}
</div>
<div
className={cx(
Expand All @@ -177,7 +237,7 @@ export const RangeSelector = memo(function RangeSelector({
onPointerEnter={() => setValue(5)}
style={{ color: color(5) }}
>
{value >= 5 ? StarFull : StarEmpty}
{starIcon(value, 5)}
</div>
</Stack>
);
Expand Down
4 changes: 2 additions & 2 deletions hera/card/TileCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import CardTitle, { CardInfoHeading } from './CardTitle.tsx';
import CoverRange from './lib/CoverRange.tsx';
import tileFieldHasDecorator from './lib/tileFieldHasDecorator.tsx';
import MovementBox from './MovementBox.tsx';
import Range from './Range.tsx';
import Range, { LargeRange } from './Range.tsx';
import TilePreview from './TilePreview.tsx';

const tiles = getAllTiles();
Expand Down Expand Up @@ -120,7 +120,7 @@ export default memo(function TileCard({
{coverName ? `${coverName}: ${cover}` : cover}
</span>
</div>
<Range end value={getAttributeRangeValue(CoverRange, cover)} zero />
<LargeRange end value={getAttributeRangeValue(CoverRange, cover)} />

{vision === -1 ? (
<>
Expand Down
8 changes: 2 additions & 6 deletions hera/card/UnitCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ const visionRange = getAttributeRange(
({ configuration: { vision } }) => vision,
1,
);
const defenseRange = getAttributeRange(units, ({ defense }) => defense);
const defenseRange = getAttributeRange(units, ({ defense }) => defense, 0.01);
const supplyRange = getAttributeRange(
units,
extractFuel,
Expand Down Expand Up @@ -345,11 +345,7 @@ export default memo(function UnitCard({
<fbt desc="Label for defense">Defense</fbt>
</Stack>
<div>{defense}</div>
<Range
end
value={getAttributeRangeValue(defenseRange, defense)}
zero
/>
<Range end value={getAttributeRangeValue(defenseRange, defense)} />
<Stack nowrap start>
<Icon horizontalFlip icon={Reply} />
<fbt desc="Label for movement radius">Movement</fbt>
Expand Down
Loading

0 comments on commit f66bbdc

Please sign in to comment.