simulation
This commit is contained in:
@@ -65,11 +65,13 @@ function Tie({
|
|||||||
|
|
||||||
export default function Bracket({
|
export default function Bracket({
|
||||||
matches, teams, tables, thirds, assignment, annexResolved,
|
matches, teams, tables, thirds, assignment, annexResolved,
|
||||||
|
preResolved,
|
||||||
}: {
|
}: {
|
||||||
matches: Match[]; teams: Team[]; tables: GroupTable[];
|
matches: Match[]; teams: Team[]; tables: GroupTable[];
|
||||||
thirds: ThirdPlaceRow[]; assignment: ThirdAssignment | null; annexResolved: boolean;
|
thirds: ThirdPlaceRow[]; assignment: ThirdAssignment | null; annexResolved: boolean;
|
||||||
|
preResolved?: { r32: ResolvedTie[]; later: Record<number, ResolvedTie> };
|
||||||
}) {
|
}) {
|
||||||
const { r32: r32Array, later } = resolveBracket(
|
const { r32: r32Array, later } = preResolved ?? resolveBracket(
|
||||||
matches, teams, tables, thirds, assignment, annexResolved,
|
matches, teams, tables, thirds, assignment, annexResolved,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
267
app/components/Simulation.tsx
Normal file
267
app/components/Simulation.tsx
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useMemo, useCallback, useEffect } from "react";
|
||||||
|
import { Match, Team } from "@/lib/types";
|
||||||
|
import { computeGroupTables, computeThirdPlaceTable } from "@/lib/standings";
|
||||||
|
import { qualifiedThirdGroups, resolveAnnexC } from "@/lib/bracket";
|
||||||
|
import { resolveBracket, ResolvedTie } from "@/lib/resolve-bracket";
|
||||||
|
import {
|
||||||
|
loadOverrides, saveOverrides, buildSimMatches,
|
||||||
|
propagateSimWinners, defaultScore, SimOverrides,
|
||||||
|
} from "@/lib/simulation";
|
||||||
|
import Bracket from "./Bracket";
|
||||||
|
import Flag from "./Flag";
|
||||||
|
|
||||||
|
function teamById(teams: Team[], id: string | null) {
|
||||||
|
return id ? teams.find((t) => t.id === id) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fmtDate(iso: string): string {
|
||||||
|
const d = new Date(iso);
|
||||||
|
return d.toLocaleString("de-DE", {
|
||||||
|
weekday: "short", day: "2-digit", month: "2-digit",
|
||||||
|
hour: "2-digit", minute: "2-digit",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Label für die Spielphase in der Liste
|
||||||
|
function phaseLabel(match: Match): string {
|
||||||
|
if (match.stage === "GROUP" && match.group) return `Gruppe ${match.group}`;
|
||||||
|
if (match.stage === "R32") return "Sechzehntelfinale";
|
||||||
|
if (match.stage === "R16") return "Achtelfinale";
|
||||||
|
if (match.stage === "QF") return "Viertelfinale";
|
||||||
|
if (match.stage === "SF") return "Halbfinale";
|
||||||
|
if (match.stage === "3RD") return "Spiel um Platz 3";
|
||||||
|
if (match.stage === "FINAL") return "Finale";
|
||||||
|
return match.stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Simulation({ teams, matches }: { teams: Team[]; matches: Match[] }) {
|
||||||
|
const [overrides, setOverrides] = useState<SimOverrides>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOverrides(loadOverrides());
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleScore = useCallback((matchId: string, homeScore: number, awayScore: number) => {
|
||||||
|
setOverrides((prev) => {
|
||||||
|
const next = { ...prev, [matchId]: { homeScore, awayScore } };
|
||||||
|
saveOverrides(next);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleReset = useCallback(() => {
|
||||||
|
setOverrides({});
|
||||||
|
saveOverrides({});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleClear = useCallback((matchId: string) => {
|
||||||
|
setOverrides((prev) => {
|
||||||
|
const next = { ...prev };
|
||||||
|
delete next[matchId];
|
||||||
|
saveOverrides(next);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// ---- Simulations-Pipeline ----
|
||||||
|
const simMatches = useMemo(() => buildSimMatches(matches, overrides), [matches, overrides]);
|
||||||
|
const simTables = useMemo(() => computeGroupTables(teams, simMatches), [teams, simMatches]);
|
||||||
|
const simThirds = useMemo(() => computeThirdPlaceTable(simTables), [simTables]);
|
||||||
|
const simQGroups = useMemo(() => qualifiedThirdGroups(simThirds), [simThirds]);
|
||||||
|
const simAnnex = useMemo(
|
||||||
|
() => (simQGroups.length === 8 ? resolveAnnexC(simQGroups) : null),
|
||||||
|
[simQGroups],
|
||||||
|
);
|
||||||
|
const simAnnexResolved = simAnnex != null;
|
||||||
|
|
||||||
|
const { r32, later: laterRaw } = useMemo(
|
||||||
|
() => resolveBracket(simMatches, teams, simTables, simThirds, simAnnex, simAnnexResolved),
|
||||||
|
[simMatches, teams, simTables, simThirds, simAnnex, simAnnexResolved],
|
||||||
|
);
|
||||||
|
const later = useMemo(() => propagateSimWinners(r32, laterRaw), [r32, laterRaw]);
|
||||||
|
const preResolved = useMemo(() => ({ r32, later }), [r32, later]);
|
||||||
|
|
||||||
|
// ---- Eingabeliste: offene Spiele aus dem echten Feed ----
|
||||||
|
const openMatches = useMemo(
|
||||||
|
() =>
|
||||||
|
matches
|
||||||
|
.filter((m) => m.status !== "FINISHED")
|
||||||
|
.sort((a, b) => +new Date(a.utcDate) - +new Date(b.utcDate)),
|
||||||
|
[matches],
|
||||||
|
);
|
||||||
|
|
||||||
|
const groupPhase = openMatches.filter((m) => m.stage === "GROUP");
|
||||||
|
const koPhase = openMatches.filter((m) => m.stage !== "GROUP");
|
||||||
|
|
||||||
|
const hasChanges = Object.keys(overrides).length > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="notice" style={{ marginBottom: 20 }}>
|
||||||
|
Simulation — Ergebnisse frei wählbar, beeinflusst nicht die echten Daten.
|
||||||
|
Alle ungespielten Spiele sind vorbelegt mit dem Polymarket-Favoriten.
|
||||||
|
Eingaben werden im Browser gespeichert.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ---- Eingabeliste ---- */}
|
||||||
|
<div style={{ marginBottom: 28 }}>
|
||||||
|
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 14 }}>
|
||||||
|
<h3 style={{ fontFamily: "var(--font-display)", fontSize: 15, textTransform: "uppercase", letterSpacing: "0.06em", color: "var(--ink-dim)", margin: 0 }}>
|
||||||
|
Offene Spiele ({openMatches.length})
|
||||||
|
</h3>
|
||||||
|
{hasChanges && (
|
||||||
|
<button
|
||||||
|
onClick={handleReset}
|
||||||
|
style={{
|
||||||
|
fontFamily: "var(--font-mono)", fontSize: 11,
|
||||||
|
color: "var(--ink-faint)", background: "var(--bg-card)",
|
||||||
|
border: "1px solid var(--line)", borderRadius: "var(--radius-sm)",
|
||||||
|
padding: "5px 12px", cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Auf Polymarket-Defaults zurücksetzen
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{groupPhase.length > 0 && (
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<div style={{ fontFamily: "var(--font-display)", fontSize: 12, textTransform: "uppercase", letterSpacing: "0.06em", color: "var(--ink-faint)", marginBottom: 8 }}>
|
||||||
|
Gruppenphase
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||||
|
{groupPhase.map((m) => (
|
||||||
|
<SimRow key={m.id} match={m} teams={teams} overrides={overrides} onScore={handleScore} onClear={handleClear} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{koPhase.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<div style={{ fontFamily: "var(--font-display)", fontSize: 12, textTransform: "uppercase", letterSpacing: "0.06em", color: "var(--ink-faint)", marginBottom: 8 }}>
|
||||||
|
K.o.-Phase
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||||
|
{koPhase.map((m) => (
|
||||||
|
<SimRow key={m.id} match={m} teams={teams} overrides={overrides} onScore={handleScore} onClear={handleClear} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ---- Simulierter K.o.-Baum ---- */}
|
||||||
|
<h3 style={{ fontFamily: "var(--font-display)", fontSize: 15, textTransform: "uppercase", letterSpacing: "0.06em", color: "var(--ink-dim)", margin: "0 0 12px" }}>
|
||||||
|
Simulierter K.o.-Baum
|
||||||
|
</h3>
|
||||||
|
<Bracket
|
||||||
|
matches={simMatches}
|
||||||
|
teams={teams}
|
||||||
|
tables={simTables}
|
||||||
|
thirds={simThirds}
|
||||||
|
assignment={simAnnex}
|
||||||
|
annexResolved={simAnnexResolved}
|
||||||
|
preResolved={preResolved}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Einzelne Eingabezeile ----
|
||||||
|
function SimRow({
|
||||||
|
match, teams, overrides, onScore, onClear,
|
||||||
|
}: {
|
||||||
|
match: Match; teams: Team[]; overrides: SimOverrides; onScore: (id: string, h: number, a: number) => void;
|
||||||
|
onClear: (id: string) => void;
|
||||||
|
}) {
|
||||||
|
const home = teamById(teams, match.homeTeamId);
|
||||||
|
const away = teamById(teams, match.awayTeamId);
|
||||||
|
const def = defaultScore(match);
|
||||||
|
const ov = overrides[match.id];
|
||||||
|
const homeVal = ov?.homeScore ?? def?.homeScore;
|
||||||
|
const awayVal = ov?.awayScore ?? def?.awayScore;
|
||||||
|
const homeChanged = ov != null;
|
||||||
|
const isKO = match.stage !== "GROUP";
|
||||||
|
|
||||||
|
const inputStyle = (changed: boolean): React.CSSProperties => ({
|
||||||
|
width: 36,
|
||||||
|
height: 28,
|
||||||
|
textAlign: "center",
|
||||||
|
fontFamily: "var(--font-mono)",
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: changed ? "var(--turf)" : "var(--ink)",
|
||||||
|
background: "var(--bg-raised)",
|
||||||
|
border: `1px solid ${changed ? "var(--turf)" : "var(--line)"}`,
|
||||||
|
borderRadius: "var(--radius-sm)",
|
||||||
|
outline: "none",
|
||||||
|
MozAppearance: "textfield",
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
display: "flex", alignItems: "center", gap: 10,
|
||||||
|
padding: "6px 12px",
|
||||||
|
background: "var(--bg-card)", border: "1px solid var(--line-soft)",
|
||||||
|
borderRadius: "var(--radius-sm)",
|
||||||
|
fontSize: 13,
|
||||||
|
}}>
|
||||||
|
<span style={{ fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--ink-faint)", minWidth: 75 }}>
|
||||||
|
{phaseLabel(match)}
|
||||||
|
</span>
|
||||||
|
<span style={{ display: "flex", alignItems: "center", gap: 6, minWidth: 0, flex: 1, justifyContent: "flex-end" }}>
|
||||||
|
<Flag team={home} size={18} />
|
||||||
|
<span style={{ fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", maxWidth: 140 }}>
|
||||||
|
{home?.name ?? "—"}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
style={inputStyle(homeChanged)}
|
||||||
|
value={homeVal ?? ""}
|
||||||
|
placeholder="–"
|
||||||
|
onChange={(e) => {
|
||||||
|
const v = parseInt(e.target.value, 10);
|
||||||
|
if (!isNaN(v) && v >= 0) {
|
||||||
|
onScore(match.id, v, awayVal ?? (isKO ? 0 : 1));
|
||||||
|
} else if (e.target.value === "") {
|
||||||
|
// Bei Leerung: Standard wiederherstellen
|
||||||
|
const fallback = def;
|
||||||
|
if (fallback) onScore(match.id, fallback.homeScore, fallback.awayScore);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span style={{ fontFamily: "var(--font-mono)", fontWeight: 700, color: "var(--ink-faint)" }}>:</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
style={inputStyle(homeChanged)}
|
||||||
|
value={awayVal ?? ""}
|
||||||
|
placeholder="–"
|
||||||
|
onChange={(e) => {
|
||||||
|
const v = parseInt(e.target.value, 10);
|
||||||
|
if (!isNaN(v) && v >= 0) {
|
||||||
|
onScore(match.id, homeVal ?? (isKO ? 0 : 1), v);
|
||||||
|
} else if (e.target.value === "") {
|
||||||
|
const fallback = def;
|
||||||
|
if (fallback) onScore(match.id, fallback.homeScore, fallback.awayScore);
|
||||||
|
else onClear(match.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span style={{ display: "flex", alignItems: "center", gap: 6, minWidth: 0, flex: 1 }}>
|
||||||
|
<span style={{ fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", maxWidth: 140 }}>
|
||||||
|
{away?.name ?? "—"}
|
||||||
|
</span>
|
||||||
|
<Flag team={away} size={18} />
|
||||||
|
</span>
|
||||||
|
<span style={{ fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--ink-faint)", minWidth: 100, textAlign: "right" }}>
|
||||||
|
{fmtDate(match.utcDate)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import Groups from "./components/Groups";
|
|||||||
import ThirdPlace from "./components/ThirdPlace";
|
import ThirdPlace from "./components/ThirdPlace";
|
||||||
import Bracket from "./components/Bracket";
|
import Bracket from "./components/Bracket";
|
||||||
import Fixtures from "./components/Fixtures";
|
import Fixtures from "./components/Fixtures";
|
||||||
|
import Simulation from "./components/Simulation";
|
||||||
|
|
||||||
interface ApiData {
|
interface ApiData {
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
@@ -19,7 +20,7 @@ interface ApiData {
|
|||||||
annexResolved: boolean;
|
annexResolved: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tab = "groups" | "fixtures" | "thirds" | "bracket";
|
type Tab = "groups" | "fixtures" | "thirds" | "bracket" | "sim";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [data, setData] = useState<ApiData | null>(null);
|
const [data, setData] = useState<ApiData | null>(null);
|
||||||
@@ -90,6 +91,9 @@ export default function Home() {
|
|||||||
<button className={`tab ${tab === "bracket" ? "active" : ""}`} onClick={() => setTab("bracket")}>
|
<button className={`tab ${tab === "bracket" ? "active" : ""}`} onClick={() => setTab("bracket")}>
|
||||||
K.o.-Baum
|
K.o.-Baum
|
||||||
</button>
|
</button>
|
||||||
|
<button className={`tab ${tab === "sim" ? "active" : ""}`} onClick={() => setTab("sim")}>
|
||||||
|
Simulation
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -133,6 +137,9 @@ export default function Home() {
|
|||||||
annexResolved={data.annexResolved}
|
annexResolved={data.annexResolved}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{data && tab === "sim" && (
|
||||||
|
<Simulation teams={data.teams} matches={data.matches} />
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
110
lib/simulation.ts
Normal file
110
lib/simulation.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import { Match, Team, GroupTable, ThirdPlaceRow } from "./types";
|
||||||
|
import { ThirdAssignment, LATER_ROUNDS } from "./bracket";
|
||||||
|
import { ResolvedSide, ResolvedTie } from "./resolve-bracket";
|
||||||
|
|
||||||
|
export type SimOverrides = Record<string, { homeScore: number; awayScore: number }>;
|
||||||
|
|
||||||
|
const STORAGE_KEY = "wm2026-sim-overrides";
|
||||||
|
|
||||||
|
export function loadOverrides(): SimOverrides {
|
||||||
|
if (typeof window === "undefined") return {};
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(STORAGE_KEY);
|
||||||
|
return raw ? JSON.parse(raw) : {};
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveOverrides(overrides: SimOverrides): void {
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
|
try {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(overrides));
|
||||||
|
} catch { /* quota exceeded */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plausibles Standardergebnis aus Polymarket-Wahrscheinlichkeiten.
|
||||||
|
// Favorit gewinnt 1:0; bei hoher W'keit (>=0.65) 2:0.
|
||||||
|
// Ohne prob: Gruppe → 1:1, K.o. → null (Nutzer muss selbst eingeben).
|
||||||
|
export function defaultScore(match: Match): { homeScore: number; awayScore: number } | null {
|
||||||
|
const prob = match.prob;
|
||||||
|
if (!prob) {
|
||||||
|
if (match.stage === "GROUP") return { homeScore: 1, awayScore: 1 };
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const homeFav = prob.home >= prob.away;
|
||||||
|
const favProb = Math.max(prob.home, prob.away);
|
||||||
|
const goals = favProb >= 0.65 ? 2 : 1;
|
||||||
|
if (homeFav) return { homeScore: goals, awayScore: 0 };
|
||||||
|
return { homeScore: 0, awayScore: goals };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Baut die simulierte Match-Liste: gespielte Spiele unverändert,
|
||||||
|
// offene Spiele mit Override oder Polymarket-Default, status = "FINISHED".
|
||||||
|
export function buildSimMatches(realMatches: Match[], overrides: SimOverrides): Match[] {
|
||||||
|
return realMatches.map((m) => {
|
||||||
|
if (m.status === "FINISHED") return m;
|
||||||
|
const override = overrides[m.id];
|
||||||
|
if (override) {
|
||||||
|
return { ...m, homeScore: override.homeScore, awayScore: override.awayScore, status: "FINISHED" as const };
|
||||||
|
}
|
||||||
|
const def = defaultScore(m);
|
||||||
|
if (def) {
|
||||||
|
return { ...m, homeScore: def.homeScore, awayScore: def.awayScore, status: "FINISHED" as const };
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ermittelt die Gewinner-Seite eines aufgelösten Ties anhand der Scores.
|
||||||
|
function winningSide(tie: ResolvedTie): ResolvedSide | null {
|
||||||
|
if (tie.home.score == null || tie.away.score == null) return null;
|
||||||
|
if (tie.home.score > tie.away.score) return tie.home;
|
||||||
|
if (tie.away.score > tie.home.score) return tie.away;
|
||||||
|
return tie.home; // unentschieden → Heimseite gewinnt
|
||||||
|
}
|
||||||
|
|
||||||
|
function losingSide(tie: ResolvedTie): ResolvedSide | null {
|
||||||
|
if (tie.home.score == null || tie.away.score == null) return null;
|
||||||
|
if (tie.home.score > tie.away.score) return tie.away;
|
||||||
|
if (tie.away.score > tie.home.score) return tie.home;
|
||||||
|
return tie.away;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Propagiert Gewinner aus R32 durch alle Folgerunden (R16→QF→SF→Finale).
|
||||||
|
// Füllt teamId/label/code in den späteren Runden mit den Daten des Gewinners.
|
||||||
|
export function propagateSimWinners(
|
||||||
|
r32: ResolvedTie[],
|
||||||
|
later: Record<number, ResolvedTie>,
|
||||||
|
): Record<number, ResolvedTie> {
|
||||||
|
const winners = new Map<number, { teamId: string; label: string; code: string }>();
|
||||||
|
const losers = new Map<number, { teamId: string; label: string; code: string }>();
|
||||||
|
|
||||||
|
function update(tie: ResolvedTie) {
|
||||||
|
const w = winningSide(tie);
|
||||||
|
const l = losingSide(tie);
|
||||||
|
if (w?.teamId) winners.set(tie.matchNumber, { teamId: w.teamId, label: w.label, code: w.code });
|
||||||
|
if (l?.teamId) losers.set(tie.matchNumber, { teamId: l.teamId, label: l.label, code: l.code });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const tie of r32) update(tie);
|
||||||
|
|
||||||
|
const result: Record<number, ResolvedTie> = {};
|
||||||
|
for (const km of LATER_ROUNDS) {
|
||||||
|
const tie = later[km.matchNumber];
|
||||||
|
if (!tie) continue;
|
||||||
|
const src = km.losers ? losers : winners;
|
||||||
|
const hw = src.get(km.fromHome);
|
||||||
|
const aw = src.get(km.fromAway);
|
||||||
|
const home = hw
|
||||||
|
? { ...tie.home, teamId: hw.teamId, label: hw.label, code: hw.code, isWinner: false, provisional: false }
|
||||||
|
: tie.home;
|
||||||
|
const away = aw
|
||||||
|
? { ...tie.away, teamId: aw.teamId, label: aw.label, code: aw.code, isWinner: false, provisional: false }
|
||||||
|
: tie.away;
|
||||||
|
const newTie: ResolvedTie = { ...tie, home, away };
|
||||||
|
update(newTie);
|
||||||
|
result[km.matchNumber] = newTie;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user