Compare commits
2 Commits
cc23ca5594
...
bb4ca8ae40
| Author | SHA1 | Date | |
|---|---|---|---|
| bb4ca8ae40 | |||
| 65195289bd |
122
src/components/BattleTable.tsx
Normal file
122
src/components/BattleTable.tsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
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(() => {
|
||||||
|
const cache = localStorage.getItem('match-result-map');
|
||||||
|
const cacheEntries: [string, { winer: string, score: number }][] = cache ? Object.entries(JSON.parse(cache)) : [];
|
||||||
|
return new Map<string, {
|
||||||
|
winer: string,
|
||||||
|
score: number,
|
||||||
|
}>(cacheEntries);
|
||||||
|
}, []);
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
const cache = JSON.stringify(Object.fromEntries(resultMap.entries()));
|
||||||
|
localStorage.setItem('match-result-map', cache);
|
||||||
|
}
|
||||||
|
}, [resultMap]);
|
||||||
|
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]);
|
||||||
|
});
|
||||||
|
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;
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { Button, Card, Divider, Drawer, Flex, Space, Table } from "antd";
|
import { Button, Card, Drawer, Table } from "antd";
|
||||||
import type { BasePlayer } from "../types";
|
import type { BasePlayer } from "../types";
|
||||||
import { getRoundTable } from "../utils/common";
|
|
||||||
import User from "./User";
|
import User from "./User";
|
||||||
|
import BattleTable from "./BattleTable";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
index: number;
|
index: number;
|
||||||
@ -45,44 +45,8 @@ export const GroupMember: React.FC<Props> = props => {
|
|||||||
size={'80vh'}
|
size={'80vh'}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
>
|
>
|
||||||
<BattleTable
|
<BattleTable players={props.players ?? []} />
|
||||||
nameList={
|
|
||||||
props
|
|
||||||
.players
|
|
||||||
?.map((e, i) => e.name ? `(${i + 1}) ${e.name}(${e.score})` : '-')
|
|
||||||
?? []
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BattleTable({ nameList }: {
|
|
||||||
nameList: string[]
|
|
||||||
}) {
|
|
||||||
const list = nameList.length % 2 === 0 ? nameList : [...nameList, '-'];
|
|
||||||
return (
|
|
||||||
<Flex vertical gap={12} justify="center" align="center">
|
|
||||||
{new Array(list.length - 1).fill(0).map((_, i) => {
|
|
||||||
const [left, right] = getRoundTable(list, i);
|
|
||||||
return (
|
|
||||||
<Card size="small" key={`round-${i}`} title={`Round: ${i + 1}`}>
|
|
||||||
{left?.map((e, r) => {
|
|
||||||
return (
|
|
||||||
<div key={r}>
|
|
||||||
<Space style={{ textAlign: "center" }}>
|
|
||||||
<div style={{ width: 120 }}>{e}</div>
|
|
||||||
<span>VS</span>
|
|
||||||
<div style={{ width: 120 }}>{right?.[r]}</div>
|
|
||||||
</Space>
|
|
||||||
{r < left.length && <Divider size="small" />}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -22,8 +22,8 @@ export function sneckGroup(size: number, groupLen: number) {
|
|||||||
return newGroups;
|
return newGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRoundTable<T>(nameList: T[], round: number) {
|
export function getRoundTable<T>(dataList: T[], round: number) {
|
||||||
const list = [...nameList];
|
const list = [...dataList];
|
||||||
const half = list.length / 2;
|
const half = list.length / 2;
|
||||||
if (round > list.length - 1) {
|
if (round > list.length - 1) {
|
||||||
const left = [...list].slice(0, half);
|
const left = [...list].slice(0, half);
|
||||||
@ -44,4 +44,43 @@ export function getRoundTable<T>(nameList: T[], round: number) {
|
|||||||
|
|
||||||
export function createGameID(matchId: string, user1: string, user2: string) {
|
export function createGameID(matchId: string, user1: string, user2: string) {
|
||||||
return [matchId, user1, user2].join('-');
|
return [matchId, user1, user2].join('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function higherWin(absScore: number) {
|
||||||
|
if (absScore <= 12) return 8;
|
||||||
|
if (absScore <= 37) return 7;
|
||||||
|
if (absScore <= 62) return 6;
|
||||||
|
if (absScore <= 87) return 5;
|
||||||
|
if (absScore <= 112) return 4;
|
||||||
|
if (absScore <= 137) return 3;
|
||||||
|
if (absScore <= 162) return 2;
|
||||||
|
if (absScore <= 187) return 2;
|
||||||
|
if (absScore <= 212) return 1;
|
||||||
|
if (absScore <= 237) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function lowerWin(absScore: number) {
|
||||||
|
if (absScore <= 12) return 8;
|
||||||
|
if (absScore <= 37) return 10;
|
||||||
|
if (absScore <= 62) return 13;
|
||||||
|
if (absScore <= 87) return 16;
|
||||||
|
if (absScore <= 112) return 20;
|
||||||
|
if (absScore <= 137) return 25;
|
||||||
|
if (absScore <= 162) return 30;
|
||||||
|
if (absScore <= 187) return 35;
|
||||||
|
if (absScore <= 212) return 40;
|
||||||
|
if (absScore <= 237) return 45;
|
||||||
|
return 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calculate(winerScore: number, loserScore: number) {
|
||||||
|
const absScore = Math.abs(winerScore - loserScore);
|
||||||
|
if (winerScore >= loserScore) {
|
||||||
|
console.debug('higherWin', absScore, { winerScore, loserScore });
|
||||||
|
return higherWin(absScore);
|
||||||
|
} else {
|
||||||
|
console.debug('lowerWin', absScore, { winerScore, loserScore });
|
||||||
|
return lowerWin(absScore);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user