mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-30 15:17:43 -05:00
Team Splitter first public version
This commit is contained in:
@@ -1,162 +0,0 @@
|
||||
import { useReducer } from "react";
|
||||
import { shuffleArray } from "utils/arrays";
|
||||
import { createNewMatch, teamsShouldChange } from "./helpers";
|
||||
|
||||
const MU_DEFAULT_VALUE = 0;
|
||||
const SIGMA_DEFAULT_VALUE = 0;
|
||||
const DEFAULT_AMOUNT_OF_ROUNDS_WITH_SAME_TEAMS = 2;
|
||||
|
||||
export const useEightsPage = () => {
|
||||
const [state, dispatch] = useReducer(
|
||||
(oldState: State, action: Action) => {
|
||||
switch (action.type) {
|
||||
case "SET_PLAYER":
|
||||
const newPlayers = [...oldState.players];
|
||||
newPlayers[action.number - 1] = {
|
||||
...newPlayers[action.number - 1],
|
||||
name: action.name,
|
||||
};
|
||||
|
||||
return { ...oldState, players: newPlayers as State["players"] };
|
||||
case "SET_AMOUNT_OF_ROUNDS_WITH_SAME_TEAMS":
|
||||
return {
|
||||
...oldState,
|
||||
amountOfRoundsWithSameTeams: action.amountOfRoundsWithSameTeams,
|
||||
};
|
||||
case "CREATE_FIRST_MATCH":
|
||||
const newMatch = createNewMatch({
|
||||
players: oldState.players,
|
||||
previousMatches: oldState.matches,
|
||||
sittingOutCounts: new Map(),
|
||||
});
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
|
||||
return { ...oldState, matches: [...oldState.matches, newMatch] };
|
||||
case "SET_WINNER":
|
||||
const matches = [...oldState.matches];
|
||||
matches[matches.length - 1].winner = action.winner;
|
||||
|
||||
const alphaPlayersSet = new Set(matches[matches.length - 1].alpha);
|
||||
const bravoPlayersSet = new Set(matches[matches.length - 1].bravo);
|
||||
|
||||
const players = [...oldState.players].map((player) => {
|
||||
let winCount = player.winCount;
|
||||
if (alphaPlayersSet.has(player.name) && action.winner === "alpha")
|
||||
winCount++;
|
||||
else if (
|
||||
bravoPlayersSet.has(player.name) &&
|
||||
action.winner === "bravo"
|
||||
)
|
||||
winCount++;
|
||||
|
||||
let lossCount = player.lossCount;
|
||||
if (alphaPlayersSet.has(player.name) && action.winner === "bravo")
|
||||
lossCount++;
|
||||
else if (
|
||||
bravoPlayersSet.has(player.name) &&
|
||||
action.winner === "alpha"
|
||||
)
|
||||
lossCount++;
|
||||
return {
|
||||
...player,
|
||||
winCount,
|
||||
lossCount,
|
||||
};
|
||||
});
|
||||
|
||||
const newMatchAfterSettingWinner = teamsShouldChange({
|
||||
amountOfRoundsWithSameTeams: oldState.amountOfRoundsWithSameTeams,
|
||||
matchesLength: oldState.matches.length,
|
||||
})
|
||||
? createNewMatch({
|
||||
players,
|
||||
previousMatches: matches,
|
||||
sittingOutCounts: new Map(),
|
||||
})
|
||||
: {
|
||||
...oldState.matches[oldState.matches.length - 1],
|
||||
winner: undefined,
|
||||
};
|
||||
|
||||
matches.push(newMatchAfterSettingWinner);
|
||||
|
||||
return { ...oldState, matches, players };
|
||||
default:
|
||||
return oldState;
|
||||
}
|
||||
},
|
||||
{
|
||||
players: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Sendou",
|
||||
mu: MU_DEFAULT_VALUE,
|
||||
sigma: SIGMA_DEFAULT_VALUE,
|
||||
winCount: 0,
|
||||
lossCount: 0,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Kiver",
|
||||
mu: MU_DEFAULT_VALUE,
|
||||
sigma: SIGMA_DEFAULT_VALUE,
|
||||
winCount: 0,
|
||||
lossCount: 0,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Brian",
|
||||
mu: MU_DEFAULT_VALUE,
|
||||
sigma: SIGMA_DEFAULT_VALUE,
|
||||
winCount: 0,
|
||||
lossCount: 0,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Sorin",
|
||||
mu: MU_DEFAULT_VALUE,
|
||||
sigma: SIGMA_DEFAULT_VALUE,
|
||||
winCount: 0,
|
||||
lossCount: 0,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "DUDE",
|
||||
mu: MU_DEFAULT_VALUE,
|
||||
sigma: SIGMA_DEFAULT_VALUE,
|
||||
winCount: 0,
|
||||
lossCount: 0,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "Grey",
|
||||
mu: MU_DEFAULT_VALUE,
|
||||
sigma: SIGMA_DEFAULT_VALUE,
|
||||
winCount: 0,
|
||||
lossCount: 0,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "plontro",
|
||||
mu: MU_DEFAULT_VALUE,
|
||||
sigma: SIGMA_DEFAULT_VALUE,
|
||||
winCount: 0,
|
||||
lossCount: 0,
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "Kaldemar",
|
||||
mu: MU_DEFAULT_VALUE,
|
||||
sigma: SIGMA_DEFAULT_VALUE,
|
||||
winCount: 0,
|
||||
lossCount: 0,
|
||||
},
|
||||
],
|
||||
matches: [],
|
||||
amountOfRoundsWithSameTeams: DEFAULT_AMOUNT_OF_ROUNDS_WITH_SAME_TEAMS,
|
||||
}
|
||||
);
|
||||
|
||||
return { state, dispatch };
|
||||
};
|
||||
@@ -1,16 +1,29 @@
|
||||
import { shuffleArray } from "utils/arrays";
|
||||
|
||||
const choosePlayersToSitOut = ({
|
||||
players,
|
||||
sittingOutCounts,
|
||||
}: {
|
||||
players: Players;
|
||||
sittingOutCounts: SittingOutCounts;
|
||||
}): PlayersTeam => {
|
||||
if (players.length === 8) return players as PlayersTeam;
|
||||
const choosePlayersToSitOut = (
|
||||
players: Player[]
|
||||
): [playingRoster: Player[], spectators: Player[]] => {
|
||||
if (players.length === 8) return [players, []];
|
||||
|
||||
// TODO: cut down to 8
|
||||
return players as PlayersTeam;
|
||||
console.log("players", players);
|
||||
|
||||
const playersCopySorted = [...players].sort(
|
||||
(a, b) => a.sittingOutCount - b.sittingOutCount
|
||||
);
|
||||
|
||||
const playingRoster: Player[] = [];
|
||||
const spectators: Player[] = [];
|
||||
|
||||
for (const [i, player] of playersCopySorted.entries()) {
|
||||
if (i === 0) spectators.push(player);
|
||||
else if (i === 1 && playersCopySorted.length === 10) {
|
||||
spectators.push(player);
|
||||
} else {
|
||||
playingRoster.push(player);
|
||||
}
|
||||
}
|
||||
|
||||
return [playingRoster, spectators];
|
||||
};
|
||||
|
||||
const getAllSubsetsOfSizeFour = <T>(theArray: T[]): T[][] =>
|
||||
@@ -66,8 +79,6 @@ const removeTeamsThatAlreadyPlayed = ({
|
||||
}): Player[][] => {
|
||||
const result: Player[][] = [];
|
||||
|
||||
console.log("previousMatches", previousMatches);
|
||||
|
||||
for (const team of teams) {
|
||||
let isNewTeam = true;
|
||||
const teamSortedStringified = team
|
||||
@@ -92,6 +103,39 @@ const removeTeamsThatAlreadyPlayed = ({
|
||||
return result;
|
||||
};
|
||||
|
||||
const removeTeamsWherePlayersShouldNotBeInTheSameTeam = ({
|
||||
teams,
|
||||
noPlacingToSameTeam,
|
||||
players,
|
||||
}: {
|
||||
teams: Player[][];
|
||||
noPlacingToSameTeam: [playerOneId?: number, playerTwoId?: number];
|
||||
players: Player[];
|
||||
}): Player[][] => {
|
||||
if (noPlacingToSameTeam.length !== 2) return teams;
|
||||
|
||||
// Check if one or both of the unpreferred players are spectating
|
||||
if (
|
||||
players.reduce(
|
||||
(count, player) =>
|
||||
noPlacingToSameTeam.includes(player.id) ? count + 1 : count,
|
||||
0
|
||||
) !== 2
|
||||
) {
|
||||
return teams;
|
||||
}
|
||||
// If count is not one it means the unpreferred players are either both in this team (count 2)
|
||||
// or in the other team (count 2)
|
||||
return teams.filter(
|
||||
(t) =>
|
||||
t.reduce(
|
||||
(count, player) =>
|
||||
noPlacingToSameTeam.includes(player.id) ? count + 1 : count,
|
||||
0
|
||||
) === 1
|
||||
);
|
||||
};
|
||||
|
||||
const selectAlphaTeamOfBestProjectedMatchUp = ({
|
||||
teams,
|
||||
players,
|
||||
@@ -135,19 +179,24 @@ const selectAlphaTeamOfBestProjectedMatchUp = ({
|
||||
export const createNewMatch = ({
|
||||
players,
|
||||
previousMatches,
|
||||
sittingOutCounts,
|
||||
noPlacingToSameTeam,
|
||||
}: {
|
||||
players: Players;
|
||||
players: Player[];
|
||||
previousMatches: Match[];
|
||||
sittingOutCounts: SittingOutCounts;
|
||||
noPlacingToSameTeam: [playerOneId?: number, playerTwoId?: number];
|
||||
}): Match => {
|
||||
// TODO: who sits out has to also be recorded
|
||||
const playersForMatch = shuffleArray(
|
||||
choosePlayersToSitOut({ players, sittingOutCounts })
|
||||
const [playersForMatch, spectators] = choosePlayersToSitOut(
|
||||
shuffleArray(players)
|
||||
);
|
||||
let teams = getAllSubsetsOfSizeFour(playersForMatch);
|
||||
teams = removeDuplicates({ teams, players: playersForMatch });
|
||||
teams = removeTeamsThatAlreadyPlayed({ teams, previousMatches });
|
||||
teams = removeTeamsWherePlayersShouldNotBeInTheSameTeam({
|
||||
teams,
|
||||
noPlacingToSameTeam,
|
||||
players: playersForMatch,
|
||||
});
|
||||
|
||||
const alpha = selectAlphaTeamOfBestProjectedMatchUp({
|
||||
teams,
|
||||
@@ -156,7 +205,7 @@ export const createNewMatch = ({
|
||||
const bravo = playersForMatch
|
||||
.filter((player) => !alpha.includes(player.name))
|
||||
.map((p) => p.name);
|
||||
return { alpha, bravo };
|
||||
return { alpha, bravo, spectators: spectators.map((s) => s.name) };
|
||||
};
|
||||
|
||||
export const teamsShouldChange = ({
|
||||
@@ -168,3 +217,16 @@ export const teamsShouldChange = ({
|
||||
}) => {
|
||||
return matchesLength % amountOfRoundsWithSameTeams === 0;
|
||||
};
|
||||
|
||||
export const errorWithPlayers = (players: Player[]) => {
|
||||
const playerNames = players
|
||||
.map((p) => p.name.trim())
|
||||
.filter((name) => name !== "");
|
||||
if (playerNames.length < 8) return "Please enter at least 8 player names";
|
||||
|
||||
const uniqueNames = new Set(playerNames.map((name) => name.toUpperCase()));
|
||||
if (uniqueNames.size !== playerNames.length)
|
||||
return "Every name has to be unique";
|
||||
|
||||
return "";
|
||||
};
|
||||
171
hooks/team-splitter/index.ts
Normal file
171
hooks/team-splitter/index.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { useEffect, useReducer } from "react";
|
||||
import { createNewMatch, teamsShouldChange, errorWithPlayers } from "./helpers";
|
||||
|
||||
const DEFAULT_AMOUNT_OF_ROUNDS_WITH_SAME_TEAMS = 2;
|
||||
|
||||
const getInitialState = (ignoreLocalStorage: boolean = false): State => {
|
||||
const stateFromLocalStorage = JSON.parse(
|
||||
window.localStorage.getItem("team-splitter-state") ?? "{}"
|
||||
);
|
||||
if (
|
||||
!ignoreLocalStorage &&
|
||||
stateFromLocalStorage &&
|
||||
Object.keys(stateFromLocalStorage).length > 0
|
||||
) {
|
||||
return stateFromLocalStorage;
|
||||
}
|
||||
return {
|
||||
players: new Array(10).fill(null).map((_, i) => ({
|
||||
id: i + 1,
|
||||
name: "",
|
||||
winCount: 0,
|
||||
lossCount: 0,
|
||||
sittingOutCount: 0,
|
||||
})),
|
||||
matches: [],
|
||||
amountOfRoundsWithSameTeams: DEFAULT_AMOUNT_OF_ROUNDS_WITH_SAME_TEAMS,
|
||||
errorWithPlayers: "",
|
||||
noPlacingToSameTeam: [],
|
||||
};
|
||||
};
|
||||
|
||||
export const useTeamSplitterPage = () => {
|
||||
const [state, dispatch] = useReducer((oldState: State, action: Action) => {
|
||||
switch (action.type) {
|
||||
case "SET_PLAYER":
|
||||
const newPlayers = [...oldState.players];
|
||||
newPlayers[action.number - 1] = {
|
||||
...newPlayers[action.number - 1],
|
||||
name: action.name,
|
||||
};
|
||||
|
||||
return {
|
||||
...oldState,
|
||||
players: newPlayers as State["players"],
|
||||
errorWithPlayers: errorWithPlayers(newPlayers),
|
||||
};
|
||||
case "SET_NO_PLACING_TO_SAME_TEAM":
|
||||
return {
|
||||
...oldState,
|
||||
noPlacingToSameTeam: action.checked
|
||||
? oldState.noPlacingToSameTeam.concat(action.id)
|
||||
: (oldState.noPlacingToSameTeam.filter(
|
||||
(id) => id !== action.id
|
||||
) as any),
|
||||
};
|
||||
case "SET_AMOUNT_OF_ROUNDS_WITH_SAME_TEAMS":
|
||||
return {
|
||||
...oldState,
|
||||
amountOfRoundsWithSameTeams: action.amountOfRoundsWithSameTeams,
|
||||
};
|
||||
case "CREATE_FIRST_MATCH":
|
||||
const playersCleanedUp = oldState.players
|
||||
.filter((p) => p.name !== "")
|
||||
.map((p) => ({ ...p, name: p.name.trim() }));
|
||||
const newMatch = createNewMatch({
|
||||
players: playersCleanedUp,
|
||||
previousMatches: oldState.matches,
|
||||
noPlacingToSameTeam: oldState.noPlacingToSameTeam,
|
||||
});
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
|
||||
return {
|
||||
...oldState,
|
||||
matches: [...oldState.matches, newMatch],
|
||||
players: playersCleanedUp,
|
||||
};
|
||||
case "SET_WINNER":
|
||||
const matches = [...oldState.matches];
|
||||
matches[matches.length - 1].winner = action.winner;
|
||||
|
||||
const alphaPlayersSet = new Set(matches[matches.length - 1].alpha);
|
||||
const bravoPlayersSet = new Set(matches[matches.length - 1].bravo);
|
||||
|
||||
const players = [...oldState.players].map((player) => {
|
||||
let winCount = player.winCount;
|
||||
if (
|
||||
!alphaPlayersSet.has(player.name) &&
|
||||
!bravoPlayersSet.has(player.name)
|
||||
) {
|
||||
return { ...player, sittingOutCount: player.sittingOutCount + 1 };
|
||||
}
|
||||
if (alphaPlayersSet.has(player.name) && action.winner === "alpha")
|
||||
winCount++;
|
||||
else if (
|
||||
bravoPlayersSet.has(player.name) &&
|
||||
action.winner === "bravo"
|
||||
)
|
||||
winCount++;
|
||||
|
||||
let lossCount = player.lossCount;
|
||||
if (alphaPlayersSet.has(player.name) && action.winner === "bravo")
|
||||
lossCount++;
|
||||
else if (
|
||||
bravoPlayersSet.has(player.name) &&
|
||||
action.winner === "alpha"
|
||||
)
|
||||
lossCount++;
|
||||
return {
|
||||
...player,
|
||||
winCount,
|
||||
lossCount,
|
||||
};
|
||||
});
|
||||
|
||||
const newMatchAfterSettingWinner = teamsShouldChange({
|
||||
amountOfRoundsWithSameTeams: oldState.amountOfRoundsWithSameTeams,
|
||||
matchesLength: oldState.matches.length,
|
||||
})
|
||||
? createNewMatch({
|
||||
players,
|
||||
previousMatches: matches,
|
||||
noPlacingToSameTeam: oldState.noPlacingToSameTeam,
|
||||
})
|
||||
: {
|
||||
...oldState.matches[oldState.matches.length - 1],
|
||||
winner: undefined,
|
||||
};
|
||||
|
||||
matches.push(newMatchAfterSettingWinner);
|
||||
|
||||
return { ...oldState, matches, players };
|
||||
case "UNDO_LATEST_MATCH":
|
||||
// oldState.matches[oldState.matches.length-1] is the match that is currently in progress
|
||||
const lastFinishedMatch = oldState.matches[oldState.matches.length - 2];
|
||||
const adjustedPlayers = oldState.players.map((p) => {
|
||||
if (lastFinishedMatch.spectators.includes(p.name)) {
|
||||
return { ...p, sittingOutCount: p.sittingOutCount - 1 };
|
||||
}
|
||||
if (lastFinishedMatch[lastFinishedMatch.winner!].includes(p.name)) {
|
||||
return { ...p, winCount: p.winCount - 1 };
|
||||
}
|
||||
|
||||
return { ...p, lossCount: p.lossCount - 1 };
|
||||
});
|
||||
|
||||
const adjustedMatches = oldState.matches
|
||||
.slice(0, oldState.matches.length - 1)
|
||||
.map((match, i, arr) =>
|
||||
i === arr.length - 1 ? { ...match, winner: undefined } : match
|
||||
);
|
||||
|
||||
return {
|
||||
...oldState,
|
||||
players: adjustedPlayers,
|
||||
matches: adjustedMatches,
|
||||
};
|
||||
case "RESET":
|
||||
window.scroll(0, 0);
|
||||
return getInitialState(true);
|
||||
default:
|
||||
return oldState;
|
||||
}
|
||||
}, getInitialState());
|
||||
|
||||
useEffect(() => {
|
||||
window.localStorage.setItem("team-splitter-state", JSON.stringify(state));
|
||||
}, [state]);
|
||||
|
||||
return { state, dispatch };
|
||||
};
|
||||
@@ -1,33 +1,24 @@
|
||||
interface Player {
|
||||
id: number;
|
||||
name: string;
|
||||
mu: number;
|
||||
sigma: number;
|
||||
winCount: number;
|
||||
lossCount: number;
|
||||
sittingOutCount: number;
|
||||
}
|
||||
|
||||
type Players = Player[];
|
||||
|
||||
type PlayersTeam = [
|
||||
Player,
|
||||
Player,
|
||||
Player,
|
||||
Player,
|
||||
Player,
|
||||
Player,
|
||||
Player,
|
||||
Player
|
||||
];
|
||||
|
||||
type SittingOutCounts = Map<string, number>;
|
||||
|
||||
type Match = { alpha: string[]; bravo: string[]; winner?: "alpha" | "bravo" };
|
||||
type Match = {
|
||||
alpha: string[];
|
||||
bravo: string[];
|
||||
spectators: string[];
|
||||
winner?: "alpha" | "bravo";
|
||||
};
|
||||
|
||||
interface State {
|
||||
players: Players;
|
||||
players: Player[];
|
||||
matches: Match[];
|
||||
amountOfRoundsWithSameTeams: number;
|
||||
errorWithPlayers: string;
|
||||
noPlacingToSameTeam: [playerOneId?: number, playerTwoId?: number];
|
||||
}
|
||||
|
||||
// TODO: names must be unique
|
||||
@@ -43,4 +34,7 @@ type Action =
|
||||
| {
|
||||
type: "SET_AMOUNT_OF_ROUNDS_WITH_SAME_TEAMS";
|
||||
amountOfRoundsWithSameTeams: number;
|
||||
};
|
||||
}
|
||||
| { type: "UNDO_LATEST_MATCH" }
|
||||
| { type: "RESET" }
|
||||
| { type: "SET_NO_PLACING_TO_SAME_TEAM"; id: number; checked: boolean };
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Button } from "@chakra-ui/button";
|
||||
import { Checkbox } from "@chakra-ui/checkbox";
|
||||
import { Input } from "@chakra-ui/input";
|
||||
import { Box, Grid, Stack, Text } from "@chakra-ui/layout";
|
||||
import { Box, Flex, Grid, Stack, Text } from "@chakra-ui/layout";
|
||||
import { Radio, RadioGroup } from "@chakra-ui/radio";
|
||||
import NewTable from "components/common/NewTable";
|
||||
import { useEightsPage } from "hooks/8s";
|
||||
import { useTeamSplitterPage } from "hooks/team-splitter";
|
||||
import { Fragment } from "react";
|
||||
|
||||
const winLossRatio = ({
|
||||
@@ -21,15 +22,15 @@ const winLossRatio = ({
|
||||
return [`${prefix}${ratio}`, ratio];
|
||||
};
|
||||
|
||||
const EightsPage = () => {
|
||||
const { state, dispatch } = useEightsPage();
|
||||
const TeamSplitterPage = () => {
|
||||
const { state, dispatch } = useTeamSplitterPage();
|
||||
|
||||
return (
|
||||
<>
|
||||
{state.matches.length === 0 ? (
|
||||
<>
|
||||
<Box maxW={80}>
|
||||
{new Array(8).fill(null).map((_, i) => {
|
||||
{new Array(10).fill(null).map((_, i) => {
|
||||
return (
|
||||
<Box key={i} my={4}>
|
||||
<Text
|
||||
@@ -83,17 +84,73 @@ const EightsPage = () => {
|
||||
</Stack>
|
||||
</RadioGroup>
|
||||
</Box>
|
||||
<Button onClick={() => dispatch({ type: "CREATE_FIRST_MATCH" })}>
|
||||
Start
|
||||
</Button>
|
||||
<Box my={6}>
|
||||
<Text
|
||||
as="label"
|
||||
display="block"
|
||||
mb="8px"
|
||||
fontSize="sm"
|
||||
fontWeight="bold"
|
||||
>
|
||||
Choose 2 players that should never be placed in the same team
|
||||
(optional)
|
||||
</Text>
|
||||
<RadioGroup
|
||||
onChange={(value) =>
|
||||
dispatch({
|
||||
type: "SET_AMOUNT_OF_ROUNDS_WITH_SAME_TEAMS",
|
||||
amountOfRoundsWithSameTeams: Number(value),
|
||||
})
|
||||
}
|
||||
value={String(state.amountOfRoundsWithSameTeams)}
|
||||
>
|
||||
<Flex flexWrap="wrap">
|
||||
{state.players
|
||||
.filter((p) => p.name !== "")
|
||||
.map((p) => (
|
||||
<Checkbox
|
||||
mr={4}
|
||||
key={p.id}
|
||||
isChecked={state.noPlacingToSameTeam.includes(p.id)}
|
||||
isDisabled={
|
||||
!state.noPlacingToSameTeam.includes(p.id) &&
|
||||
state.noPlacingToSameTeam.length === 2
|
||||
}
|
||||
onChange={(e) =>
|
||||
dispatch({
|
||||
type: "SET_NO_PLACING_TO_SAME_TEAM",
|
||||
id: p.id,
|
||||
checked: e.target.checked,
|
||||
})
|
||||
}
|
||||
>
|
||||
{p.name}
|
||||
</Checkbox>
|
||||
))}
|
||||
</Flex>
|
||||
</RadioGroup>
|
||||
</Box>
|
||||
<Flex alignItems="center">
|
||||
<Button
|
||||
onClick={() => dispatch({ type: "CREATE_FIRST_MATCH" })}
|
||||
disabled={Boolean(state.errorWithPlayers)}
|
||||
mr={4}
|
||||
>
|
||||
Start
|
||||
</Button>
|
||||
<Text fontSize="sm" color="red.500">
|
||||
{state.errorWithPlayers}
|
||||
</Text>
|
||||
</Flex>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Grid
|
||||
gridTemplateColumns="1fr 1fr"
|
||||
maxW={80}
|
||||
gridTemplateColumns="1fr 1fr 1fr"
|
||||
maxW="32rem"
|
||||
gridColumnGap="2rem"
|
||||
gridRowGap="0.5rem"
|
||||
mx="auto"
|
||||
mb={8}
|
||||
>
|
||||
<Text
|
||||
@@ -112,6 +169,14 @@ const EightsPage = () => {
|
||||
>
|
||||
Bravo
|
||||
</Text>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
fontWeight="bold"
|
||||
borderBottom="2px solid"
|
||||
borderColor="gray.500"
|
||||
>
|
||||
Spectators
|
||||
</Text>
|
||||
{state.matches[state.matches.length - 1].alpha.map((player, i) => {
|
||||
return (
|
||||
<Fragment key={player}>
|
||||
@@ -119,12 +184,19 @@ const EightsPage = () => {
|
||||
<Text>
|
||||
{state.matches[state.matches.length - 1].bravo[i]}
|
||||
</Text>
|
||||
<Text>
|
||||
{state.matches[state.matches.length - 1].spectators[i] ??
|
||||
""}
|
||||
</Text>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
<Button
|
||||
size="sm"
|
||||
backgroundColor="#F63778"
|
||||
fontSize="xs"
|
||||
_hover={{ backgroundColor: "#F63778" }}
|
||||
_active={{ backgroundColor: "#F63778" }}
|
||||
onClick={() => dispatch({ type: "SET_WINNER", winner: "alpha" })}
|
||||
>
|
||||
Alpha won
|
||||
@@ -132,11 +204,44 @@ const EightsPage = () => {
|
||||
<Button
|
||||
size="sm"
|
||||
backgroundColor="#01E262"
|
||||
fontSize="xs"
|
||||
_hover={{ backgroundColor: "#01E262" }}
|
||||
_active={{ backgroundColor: "#01E262" }}
|
||||
onClick={() => dispatch({ type: "SET_WINNER", winner: "bravo" })}
|
||||
>
|
||||
Bravo won
|
||||
</Button>
|
||||
{state.matches.length > 1 ? (
|
||||
<Button
|
||||
size="sm"
|
||||
fontSize="xs"
|
||||
onClick={() => dispatch({ type: "UNDO_LATEST_MATCH" })}
|
||||
colorScheme="gray"
|
||||
>
|
||||
Undo last
|
||||
</Button>
|
||||
) : null}
|
||||
</Grid>
|
||||
<Button
|
||||
my={4}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
state.players
|
||||
.sort((a, b) => winLossRatio(b)[1] - winLossRatio(a)[1])
|
||||
.map(
|
||||
(player) =>
|
||||
`${player.name} - ${winLossRatio(player)[0]} (${
|
||||
player.winCount
|
||||
}W/${player.lossCount}L)`
|
||||
)
|
||||
.join("\n")
|
||||
);
|
||||
}}
|
||||
>
|
||||
Copy leaderboards to clipboard
|
||||
</Button>
|
||||
<NewTable
|
||||
caption="Leaderboards"
|
||||
headers={[
|
||||
@@ -155,10 +260,21 @@ const EightsPage = () => {
|
||||
ratio: winLossRatio(player)[0],
|
||||
}))}
|
||||
/>
|
||||
<Button
|
||||
display="block"
|
||||
size="sm"
|
||||
mx="auto"
|
||||
mt={6}
|
||||
variant="outline"
|
||||
colorScheme="red"
|
||||
onClick={() => dispatch({ type: "RESET" })}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EightsPage;
|
||||
export default TeamSplitterPage;
|
||||
BIN
public/layout/team-splitter.png
Normal file
BIN
public/layout/team-splitter.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -47,6 +47,7 @@ export const navItems: {
|
||||
{ code: "t", name: "Teams" },
|
||||
{ code: "plans", name: "Plans" },
|
||||
{ code: "maps", name: "Map Lists" },
|
||||
{ code: "team-splitter", name: "Team Splitter" },
|
||||
{
|
||||
code: "plus",
|
||||
name: "Plus Server",
|
||||
|
||||
Reference in New Issue
Block a user