"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { de } from "date-fns/locale"; import { isEmpty, isNil, isNumber } from "lodash"; import { useTranslations } from "next-intl"; import { FormProvider, useFieldArray, useForm } from "react-hook-form"; import { IoAdd, IoCloseOutline } from "react-icons/io5"; import { twMerge } from "tailwind-merge"; import { z } from "zod"; import { SelectField } from "@/components/form/SelectField"; import { TextField } from "@/components/form/TextField"; const getNewRating = ( rating: number, opponentRating: number, result: 1 | 0 | 0.5, kFactor: number, ) => { const myChanceToWin = 1 / (1 + Math.pow(10, (opponentRating - rating) / 400)); const delta = Math.round((kFactor * (result - myChanceToWin) + Number.EPSILON) * 100) / 100; return { delta, newRating: Math.round((rating + delta + Number.EPSILON) * 100) / 100, }; }; const resultsSchema = z.object({ currentElo: z.number().int().positive(), kFactor: z.enum(["40", "30", "20", "15", "10"]), games: z.array( z.object({ opponentElo: z.number().int().positive(), result: z.enum(["win", "draw", "loss"]), }), ), }); type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial; } : T; type EloFormValues = DeepPartial>; export default function Elo() { const t = useTranslations("Elo"); const form = useForm({ resolver: zodResolver(resultsSchema), defaultValues: { kFactor: "20", games: [{}], }, }); const { fields: gameFields, append: appendGame, remove: removeGame, } = useFieldArray({ control: form.control, name: "games", }); const onSubmit = async (data: EloFormValues) => {}; const [currentElo, kFactor, games] = form.watch([ "currentElo", "kFactor", "games", ]); type Deltas = { rating: number; deltas: { rating: number; delta: number | undefined }[]; }; const calculations = !Number.isNaN(currentElo) ? (games ?? []).reduce( (acc, game) => { if (!Number.isNaN(game?.opponentElo) && game?.result) { const result = game?.result === "win" ? 1 : game?.result === "loss" ? 0 : 0.5; const { delta, newRating } = getNewRating( acc.rating, game.opponentElo!, result, parseInt(kFactor!, 10), ); return { rating: newRating, deltas: [...acc.deltas, { rating: newRating, delta }], }; } return { rating: acc.rating, deltas: [...acc.deltas, { rating: acc.rating, delta: undefined }], }; }, { rating: currentElo!, deltas: [] }, ) : { rating: currentElo!, deltas: [] }; const deltas = calculations.deltas; return (

{t("title")}

{t("info")}

({ value: k, label: k, }))} required />

{t("resultsTitle")}

{gameFields.map((game, i) => { return (
({ value: result, label: t("gameResult", { result }), }))} required />
{gameFields.length > 1 && ( )}
{deltas[i]?.delta !== undefined && (
1 && "mr-10", i === deltas.length - 1 && "font-bold", )} > {Math.round(deltas[i]!.rating)} ( 0 && "text-success", deltas[i].delta! < 0 && "text-error", )} > {deltas[i]!.delta! >= 0 ? "+" : ""} {deltas[i].delta!} )
)}
); })}
); }