- Move `BattleTable` logic from `GroupMember` to its own `BattleTable.tsx` component. - Clean up `GroupMember` by removing unused imports (`Divider`, `Flex`, `Space`, `Table`, `getRoundTable`). - Add new utility functions in `src/utils/common.ts`: - `higherWin` and `lowerWin` to calculate scores based on point difference thresholds. - `calculate` to determine the final score return value based on win/loss status.
113 lines
4.0 KiB
TypeScript
113 lines
4.0 KiB
TypeScript
import type { BasePlayer } from "../types";
|
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
import { calculate, getRoundTable } from "../utils/common";
|
|
import { Card, Divider, Flex, Space } from "antd";
|
|
import styled from "styled-components";
|
|
import { cloneDeep } from "lodash";
|
|
|
|
const StyledContainer = styled(Flex)`
|
|
.winer {
|
|
color: red;
|
|
position: relative;
|
|
::after {
|
|
content: '胜';
|
|
font-size: 0.8em;
|
|
background-color: red;
|
|
color: white;
|
|
padding: 0;
|
|
border-radius: 50%;
|
|
position: absolute;
|
|
}
|
|
}
|
|
`;
|
|
|
|
function BattleTable({
|
|
players: list,
|
|
}: {
|
|
players: BasePlayer[];
|
|
}) {
|
|
const playerList = useMemo(() => list.length % 2 === 0 ? list : [...list, {
|
|
uid: '',
|
|
score: '',
|
|
name: '-',
|
|
}], [list]);
|
|
const resultMap = useMemo(() => new Map<string, {
|
|
winer: string,
|
|
score: number,
|
|
}>(), []);
|
|
const buildMatchGroupTable = useCallback((list: (BasePlayer | null)[]) => {
|
|
const roundNum = list.length - 1;
|
|
const dataList = cloneDeep(list);
|
|
const roundTable = new Array(roundNum).fill(0).map((_, i) => {
|
|
const [left, right] = getRoundTable(dataList, i);
|
|
left?.forEach((l, i) => {
|
|
const r = right?.[i];
|
|
const key = `${l?.uid}-${r?.uid}`;
|
|
const winer = resultMap.get(key)?.winer;
|
|
const score = resultMap.get(key)?.score;
|
|
if (winer && l && r) {
|
|
if (winer === l?.uid) {
|
|
l.score = (+l.score + (score ?? 0)).toFixed();
|
|
r.score = (+r.score - (score ?? 0)).toFixed();
|
|
} else {
|
|
r.score = (+r.score + (score ?? 0)).toFixed();
|
|
l.score = (+l.score - (score ?? 0)).toFixed();
|
|
}
|
|
}
|
|
});
|
|
return cloneDeep([left, right]);
|
|
});
|
|
console.debug(roundTable);
|
|
return roundTable.filter(Boolean);
|
|
}, []);
|
|
const [matchGroupTable, setMatchGroupTable] = useState<ReturnType<typeof buildMatchGroupTable>>([]);
|
|
|
|
const handleWiner = useCallback((l?: BasePlayer, r?: BasePlayer, winer?: BasePlayer, loser?: BasePlayer) => {
|
|
if (!l?.uid || !r?.uid || !winer?.uid || !loser?.uid) return;
|
|
const key = `${l.uid}-${r.uid}`;
|
|
const score = calculate(+winer.score, +loser.score);
|
|
if (resultMap.get(key)?.winer) {
|
|
resultMap.delete(key);
|
|
} else {
|
|
resultMap.set(key, { winer: winer?.uid!, score });
|
|
}
|
|
setMatchGroupTable(buildMatchGroupTable(playerList));
|
|
}, []);
|
|
useEffect(() => {
|
|
const roundTable = buildMatchGroupTable(playerList);
|
|
setMatchGroupTable(roundTable);
|
|
}, [playerList]);
|
|
// console.debug('matchGroupTable', matchGroupTable, gameResultMap);
|
|
return (
|
|
<StyledContainer vertical gap={12} justify="center" align="center">
|
|
{matchGroupTable.map(([left, right], i) => {
|
|
return (
|
|
<Card size="small" key={`round-${i}`} title={`Round: ${i + 1}`}>
|
|
{left?.map((l, i) => {
|
|
const r = right?.[i];
|
|
const key = `${l?.uid}-${r?.uid}`;
|
|
const winer = resultMap.get(key)?.winer;
|
|
const score = resultMap.get(key)?.score;
|
|
const resultL = winer ? winer === l?.uid ? ` +${score}` : `-${score}` : ''
|
|
const resultR = winer ? winer === r?.uid ? ` +${score}` : `-${score}` : ''
|
|
const nameL = l?.uid ? `${l?.name}(${l?.score})${resultL}` : '-';
|
|
const nameR = r?.uid ? `${r?.name}(${r?.score})${resultR}` : '-';
|
|
return (
|
|
<div key={key}>
|
|
<Space style={{ textAlign: "center" }}>
|
|
<div className={winer === l?.uid ? 'winer' : ''} onClick={() => handleWiner(l!, r!, l!, r!)} style={{ width: 120 }}>{nameL}</div>
|
|
<span>VS</span>
|
|
<div className={winer === r?.uid ? 'winer' : ''} onClick={() => handleWiner(l!, r!, r!, l!)} style={{ width: 120 }}>{nameR}</div>
|
|
</Space>
|
|
{i < left.length - 1 && <Divider size="small" />}
|
|
</div>
|
|
);
|
|
})}
|
|
</Card>
|
|
);
|
|
})}
|
|
</StyledContainer>
|
|
);
|
|
}
|
|
|
|
export default BattleTable; |