aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/app/components/Gaming
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-03-06 02:18:40 +0900
committernsfisis <nsfisis@gmail.com>2026-03-06 02:18:40 +0900
commit46f9ba5d8c295454381655e6ec02ad3cf8bd79db (patch)
treec54719cb129ee05f96c4898219588062f71daa36 /frontend/app/components/Gaming
parent27f509ccf4fbfeaa1bc2580ae2251461dc44ebfa (diff)
downloadphperkaigi-2026-albatross-46f9ba5d8c295454381655e6ec02ad3cf8bd79db.tar.gz
phperkaigi-2026-albatross-46f9ba5d8c295454381655e6ec02ad3cf8bd79db.tar.zst
phperkaigi-2026-albatross-46f9ba5d8c295454381655e6ec02ad3cf8bd79db.zip
style: switch from tab to space indentation in frontend and worker/php
Update biome.json indentStyle from "tab" to "space" and reformat all files in both workspaces. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'frontend/app/components/Gaming')
-rw-r--r--frontend/app/components/Gaming/CodeBlock.tsx94
-rw-r--r--frontend/app/components/Gaming/CodePopover.tsx62
-rw-r--r--frontend/app/components/Gaming/DataTable.test.tsx104
-rw-r--r--frontend/app/components/Gaming/DataTable.tsx64
-rw-r--r--frontend/app/components/Gaming/ExecStatusIndicatorIcon.tsx72
-rw-r--r--frontend/app/components/Gaming/InlineCode.tsx12
-rw-r--r--frontend/app/components/Gaming/LeftTime.test.tsx58
-rw-r--r--frontend/app/components/Gaming/LeftTime.tsx38
-rw-r--r--frontend/app/components/Gaming/ProblemColumn.tsx34
-rw-r--r--frontend/app/components/Gaming/ProblemColumnContent.tsx146
-rw-r--r--frontend/app/components/Gaming/RankingTable.tsx70
-rw-r--r--frontend/app/components/Gaming/Score.tsx48
-rw-r--r--frontend/app/components/Gaming/ScoreBar.tsx48
13 files changed, 425 insertions, 425 deletions
diff --git a/frontend/app/components/Gaming/CodeBlock.tsx b/frontend/app/components/Gaming/CodeBlock.tsx
index 2107f94..f048a38 100644
--- a/frontend/app/components/Gaming/CodeBlock.tsx
+++ b/frontend/app/components/Gaming/CodeBlock.tsx
@@ -4,60 +4,60 @@ import { JSX, useLayoutEffect, useState } from "react";
import { type BundledLanguage, highlight } from "../../highlight";
type Props = {
- code: string;
- language: BundledLanguage;
+ code: string;
+ language: BundledLanguage;
};
function Plaintext({ code }: { code: string }) {
- const lines = code.split("\n");
- return (
- <pre>
- <code>
- {lines.map((line, i) => (
- <span key={i} className="line">
- {line}
- {i < lines.length - 1 ? "\n" : ""}
- </span>
- ))}
- </code>
- </pre>
- );
+ const lines = code.split("\n");
+ return (
+ <pre>
+ <code>
+ {lines.map((line, i) => (
+ <span key={i} className="line">
+ {line}
+ {i < lines.length - 1 ? "\n" : ""}
+ </span>
+ ))}
+ </code>
+ </pre>
+ );
}
export default function CodeBlock({ code, language }: Props) {
- const [nodes, setNodes] = useState<JSX.Element | null>(null);
- const [showCopied, setShowCopied] = useState(false);
+ const [nodes, setNodes] = useState<JSX.Element | null>(null);
+ const [showCopied, setShowCopied] = useState(false);
- useLayoutEffect(() => {
- highlight(code, language)
- .then(setNodes)
- .catch(() => setNodes(null));
- }, [code, language]);
+ useLayoutEffect(() => {
+ highlight(code, language)
+ .then(setNodes)
+ .catch(() => setNodes(null));
+ }, [code, language]);
- const handleCopy = () => {
- navigator.clipboard.writeText(code).then(() => {
- setShowCopied(true);
- setTimeout(() => setShowCopied(false), 3000);
- });
- };
+ const handleCopy = () => {
+ navigator.clipboard.writeText(code).then(() => {
+ setShowCopied(true);
+ setTimeout(() => setShowCopied(false), 3000);
+ });
+ };
- return (
- <div className="relative">
- {code !== "" && (
- <button
- onClick={handleCopy}
- className="absolute top-2 right-2 z-10 px-2 py-1 bg-white border border-gray-300 rounded shadow-md hover:bg-gray-100 transition-colors"
- title="コードをコピーする"
- >
- <FontAwesomeIcon icon={faCopy} className="text-gray-600" />
- {showCopied && (
- <span className="ml-1 text-xs text-brand-600">Copied!</span>
- )}
- </button>
- )}
- <div className="shiki h-full w-full p-2 pr-12 bg-white rounded-lg border border-gray-300 whitespace-pre-wrap break-words">
- {nodes ?? <Plaintext code={code} />}
- </div>
- </div>
- );
+ return (
+ <div className="relative">
+ {code !== "" && (
+ <button
+ onClick={handleCopy}
+ className="absolute top-2 right-2 z-10 px-2 py-1 bg-white border border-gray-300 rounded shadow-md hover:bg-gray-100 transition-colors"
+ title="コードをコピーする"
+ >
+ <FontAwesomeIcon icon={faCopy} className="text-gray-600" />
+ {showCopied && (
+ <span className="ml-1 text-xs text-brand-600">Copied!</span>
+ )}
+ </button>
+ )}
+ <div className="shiki h-full w-full p-2 pr-12 bg-white rounded-lg border border-gray-300 whitespace-pre-wrap break-words">
+ {nodes ?? <Plaintext code={code} />}
+ </div>
+ </div>
+ );
}
diff --git a/frontend/app/components/Gaming/CodePopover.tsx b/frontend/app/components/Gaming/CodePopover.tsx
index 91245df..c4065ed 100644
--- a/frontend/app/components/Gaming/CodePopover.tsx
+++ b/frontend/app/components/Gaming/CodePopover.tsx
@@ -7,39 +7,39 @@ import BorderedContainer from "../BorderedContainer";
import CodeBlock from "../Gaming/CodeBlock";
type Props = {
- code: string;
- language: SupportedLanguage;
+ code: string;
+ language: SupportedLanguage;
};
export default function CodePopover({ code, language }: Props) {
- const codeSize = calcCodeSize(code, language);
+ const codeSize = calcCodeSize(code, language);
- return (
- <Popover.Root>
- <Popover.Trigger>
- <FontAwesomeIcon icon={faCode} fixedWidth />
- </Popover.Trigger>
- <Popover.Portal>
- <Popover.Positioner>
- <Popover.Popup>
- <BorderedContainer className="grow flex flex-col gap-4">
- <div className="flex flex-row gap-2 items-center">
- <div className="grow font-semibold text-lg">
- コードサイズ: {codeSize}
- </div>
- <Popover.Close className="p-1 bg-gray-50 border-1 border-gray-300 rounded-sm">
- <FontAwesomeIcon
- icon={faXmark}
- fixedWidth
- className="text-gray-500"
- />
- </Popover.Close>
- </div>
- <CodeBlock code={code} language={language} />
- </BorderedContainer>
- </Popover.Popup>
- </Popover.Positioner>
- </Popover.Portal>
- </Popover.Root>
- );
+ return (
+ <Popover.Root>
+ <Popover.Trigger>
+ <FontAwesomeIcon icon={faCode} fixedWidth />
+ </Popover.Trigger>
+ <Popover.Portal>
+ <Popover.Positioner>
+ <Popover.Popup>
+ <BorderedContainer className="grow flex flex-col gap-4">
+ <div className="flex flex-row gap-2 items-center">
+ <div className="grow font-semibold text-lg">
+ コードサイズ: {codeSize}
+ </div>
+ <Popover.Close className="p-1 bg-gray-50 border-1 border-gray-300 rounded-sm">
+ <FontAwesomeIcon
+ icon={faXmark}
+ fixedWidth
+ className="text-gray-500"
+ />
+ </Popover.Close>
+ </div>
+ <CodeBlock code={code} language={language} />
+ </BorderedContainer>
+ </Popover.Popup>
+ </Popover.Positioner>
+ </Popover.Portal>
+ </Popover.Root>
+ );
}
diff --git a/frontend/app/components/Gaming/DataTable.test.tsx b/frontend/app/components/Gaming/DataTable.test.tsx
index 2a4446c..08c7336 100644
--- a/frontend/app/components/Gaming/DataTable.test.tsx
+++ b/frontend/app/components/Gaming/DataTable.test.tsx
@@ -6,65 +6,65 @@ import { afterEach, describe, expect, test } from "vitest";
import DataTable, { DataTableCell, formatUnixTimestamp } from "./DataTable";
afterEach(() => {
- cleanup();
+ cleanup();
});
describe("DataTable", () => {
- test("renders headers", () => {
- render(
- <DataTable headers={["A", "B", "C"]}>
- <tr>
- <DataTableCell>1</DataTableCell>
- <DataTableCell>2</DataTableCell>
- <DataTableCell>3</DataTableCell>
- </tr>
- </DataTable>,
- );
- expect(screen.getByText("A")).toBeDefined();
- expect(screen.getByText("B")).toBeDefined();
- expect(screen.getByText("C")).toBeDefined();
- });
+ test("renders headers", () => {
+ render(
+ <DataTable headers={["A", "B", "C"]}>
+ <tr>
+ <DataTableCell>1</DataTableCell>
+ <DataTableCell>2</DataTableCell>
+ <DataTableCell>3</DataTableCell>
+ </tr>
+ </DataTable>,
+ );
+ expect(screen.getByText("A")).toBeDefined();
+ expect(screen.getByText("B")).toBeDefined();
+ expect(screen.getByText("C")).toBeDefined();
+ });
- test("renders body cells", () => {
- render(
- <DataTable headers={["H"]}>
- <tr>
- <DataTableCell>cell content</DataTableCell>
- </tr>
- </DataTable>,
- );
- expect(screen.getByText("cell content")).toBeDefined();
- });
+ test("renders body cells", () => {
+ render(
+ <DataTable headers={["H"]}>
+ <tr>
+ <DataTableCell>cell content</DataTableCell>
+ </tr>
+ </DataTable>,
+ );
+ expect(screen.getByText("cell content")).toBeDefined();
+ });
- test("renders multiple rows", () => {
- render(
- <DataTable headers={["Name"]}>
- <tr>
- <DataTableCell>Alice</DataTableCell>
- </tr>
- <tr>
- <DataTableCell>Bob</DataTableCell>
- </tr>
- </DataTable>,
- );
- expect(screen.getByText("Alice")).toBeDefined();
- expect(screen.getByText("Bob")).toBeDefined();
- });
+ test("renders multiple rows", () => {
+ render(
+ <DataTable headers={["Name"]}>
+ <tr>
+ <DataTableCell>Alice</DataTableCell>
+ </tr>
+ <tr>
+ <DataTableCell>Bob</DataTableCell>
+ </tr>
+ </DataTable>,
+ );
+ expect(screen.getByText("Alice")).toBeDefined();
+ expect(screen.getByText("Bob")).toBeDefined();
+ });
});
describe("formatUnixTimestamp", () => {
- test("formats timestamp correctly", () => {
- // 2026-03-01 12:30 JST (UTC+9) = 2026-03-01 03:30 UTC
- const timestamp = Date.UTC(2026, 2, 1, 3, 30, 0) / 1000;
- const result = formatUnixTimestamp(timestamp);
- // Result depends on local timezone; just check the format pattern
- expect(result).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/);
- });
+ test("formats timestamp correctly", () => {
+ // 2026-03-01 12:30 JST (UTC+9) = 2026-03-01 03:30 UTC
+ const timestamp = Date.UTC(2026, 2, 1, 3, 30, 0) / 1000;
+ const result = formatUnixTimestamp(timestamp);
+ // Result depends on local timezone; just check the format pattern
+ expect(result).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/);
+ });
- test("pads single-digit months and days", () => {
- // Use a date where month and day are single digits
- const timestamp = Date.UTC(2026, 0, 5, 0, 0, 0) / 1000;
- const result = formatUnixTimestamp(timestamp);
- expect(result).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/);
- });
+ test("pads single-digit months and days", () => {
+ // Use a date where month and day are single digits
+ const timestamp = Date.UTC(2026, 0, 5, 0, 0, 0) / 1000;
+ const result = formatUnixTimestamp(timestamp);
+ expect(result).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/);
+ });
});
diff --git a/frontend/app/components/Gaming/DataTable.tsx b/frontend/app/components/Gaming/DataTable.tsx
index 098f4a2..f909b1e 100644
--- a/frontend/app/components/Gaming/DataTable.tsx
+++ b/frontend/app/components/Gaming/DataTable.tsx
@@ -1,45 +1,45 @@
import type React from "react";
type Props = {
- headers: React.ReactNode[];
- children: React.ReactNode;
+ headers: React.ReactNode[];
+ children: React.ReactNode;
};
export default function DataTable({ headers, children }: Props) {
- return (
- <div className="overflow-x-auto border-2 border-brand-600 rounded-xl">
- <table className="min-w-full divide-y divide-gray-400 border-collapse">
- <thead className="bg-gray-50">
- <tr>
- {headers.map((header, i) => (
- <th
- key={i}
- scope="col"
- className="px-6 py-3 text-left font-medium text-gray-800"
- >
- {header}
- </th>
- ))}
- </tr>
- </thead>
- <tbody className="bg-white divide-y divide-gray-300">{children}</tbody>
- </table>
- </div>
- );
+ return (
+ <div className="overflow-x-auto border-2 border-brand-600 rounded-xl">
+ <table className="min-w-full divide-y divide-gray-400 border-collapse">
+ <thead className="bg-gray-50">
+ <tr>
+ {headers.map((header, i) => (
+ <th
+ key={i}
+ scope="col"
+ className="px-6 py-3 text-left font-medium text-gray-800"
+ >
+ {header}
+ </th>
+ ))}
+ </tr>
+ </thead>
+ <tbody className="bg-white divide-y divide-gray-300">{children}</tbody>
+ </table>
+ </div>
+ );
}
export function DataTableCell({ children }: { children: React.ReactNode }) {
- return (
- <td className="px-6 py-4 whitespace-nowrap text-gray-900">{children}</td>
- );
+ return (
+ <td className="px-6 py-4 whitespace-nowrap text-gray-900">{children}</td>
+ );
}
export function formatUnixTimestamp(timestamp: number): string {
- const date = new Date(timestamp * 1000);
- const year = date.getFullYear();
- const month = (date.getMonth() + 1).toString().padStart(2, "0");
- const day = date.getDate().toString().padStart(2, "0");
- const hours = date.getHours().toString().padStart(2, "0");
- const minutes = date.getMinutes().toString().padStart(2, "0");
- return `${year}-${month}-${day} ${hours}:${minutes}`;
+ const date = new Date(timestamp * 1000);
+ const year = date.getFullYear();
+ const month = (date.getMonth() + 1).toString().padStart(2, "0");
+ const day = date.getDate().toString().padStart(2, "0");
+ const hours = date.getHours().toString().padStart(2, "0");
+ const minutes = date.getMinutes().toString().padStart(2, "0");
+ return `${year}-${month}-${day} ${hours}:${minutes}`;
}
diff --git a/frontend/app/components/Gaming/ExecStatusIndicatorIcon.tsx b/frontend/app/components/Gaming/ExecStatusIndicatorIcon.tsx
index e4260ec..2bf088b 100644
--- a/frontend/app/components/Gaming/ExecStatusIndicatorIcon.tsx
+++ b/frontend/app/components/Gaming/ExecStatusIndicatorIcon.tsx
@@ -1,46 +1,46 @@
import {
- faCircle,
- faCircleCheck,
- faCircleExclamation,
- faRotate,
+ faCircle,
+ faCircleCheck,
+ faCircleExclamation,
+ faRotate,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import type { components } from "../../api/schema";
type Props = {
- status: components["schemas"]["ExecutionStatus"];
+ status: components["schemas"]["ExecutionStatus"];
};
export default function ExecStatusIndicatorIcon({ status }: Props) {
- switch (status) {
- case "none":
- return (
- <FontAwesomeIcon icon={faCircle} fixedWidth className="text-gray-400" />
- );
- case "running":
- return (
- <FontAwesomeIcon
- icon={faRotate}
- spin
- fixedWidth
- className="text-gray-700"
- />
- );
- case "success":
- return (
- <FontAwesomeIcon
- icon={faCircleCheck}
- fixedWidth
- className="text-brand-500"
- />
- );
- default:
- return (
- <FontAwesomeIcon
- icon={faCircleExclamation}
- fixedWidth
- className="text-red-500"
- />
- );
- }
+ switch (status) {
+ case "none":
+ return (
+ <FontAwesomeIcon icon={faCircle} fixedWidth className="text-gray-400" />
+ );
+ case "running":
+ return (
+ <FontAwesomeIcon
+ icon={faRotate}
+ spin
+ fixedWidth
+ className="text-gray-700"
+ />
+ );
+ case "success":
+ return (
+ <FontAwesomeIcon
+ icon={faCircleCheck}
+ fixedWidth
+ className="text-brand-500"
+ />
+ );
+ default:
+ return (
+ <FontAwesomeIcon
+ icon={faCircleExclamation}
+ fixedWidth
+ className="text-red-500"
+ />
+ );
+ }
}
diff --git a/frontend/app/components/Gaming/InlineCode.tsx b/frontend/app/components/Gaming/InlineCode.tsx
index c90cad4..0b5e061 100644
--- a/frontend/app/components/Gaming/InlineCode.tsx
+++ b/frontend/app/components/Gaming/InlineCode.tsx
@@ -1,11 +1,11 @@
type Props = {
- code: string;
+ code: string;
};
export default function InlineCode({ code }: Props) {
- return (
- <code className="bg-gray-50 rounded-lg border border-gray-300 p-1">
- {code}
- </code>
- );
+ return (
+ <code className="bg-gray-50 rounded-lg border border-gray-300 p-1">
+ {code}
+ </code>
+ );
}
diff --git a/frontend/app/components/Gaming/LeftTime.test.tsx b/frontend/app/components/Gaming/LeftTime.test.tsx
index 742d8eb..28f2fc4 100644
--- a/frontend/app/components/Gaming/LeftTime.test.tsx
+++ b/frontend/app/components/Gaming/LeftTime.test.tsx
@@ -6,42 +6,42 @@ import { afterEach, describe, expect, test } from "vitest";
import LeftTime from "./LeftTime";
afterEach(() => {
- cleanup();
+ cleanup();
});
describe("LeftTime", () => {
- test("renders MM:SS format for short durations", () => {
- render(<LeftTime sec={65} />);
- expect(screen.getByText("01:05")).toBeDefined();
- });
+ test("renders MM:SS format for short durations", () => {
+ render(<LeftTime sec={65} />);
+ expect(screen.getByText("01:05")).toBeDefined();
+ });
- test("renders 00:00 for zero seconds", () => {
- render(<LeftTime sec={0} />);
- expect(screen.getByText("00:00")).toBeDefined();
- });
+ test("renders 00:00 for zero seconds", () => {
+ render(<LeftTime sec={0} />);
+ expect(screen.getByText("00:00")).toBeDefined();
+ });
- test("renders MM:SS with leading zeros", () => {
- render(<LeftTime sec={5} />);
- expect(screen.getByText("00:05")).toBeDefined();
- });
+ test("renders MM:SS with leading zeros", () => {
+ render(<LeftTime sec={5} />);
+ expect(screen.getByText("00:05")).toBeDefined();
+ });
- test("renders 59:59 for max MM:SS range", () => {
- render(<LeftTime sec={3599} />);
- expect(screen.getByText("59:59")).toBeDefined();
- });
+ test("renders 59:59 for max MM:SS range", () => {
+ render(<LeftTime sec={3599} />);
+ expect(screen.getByText("59:59")).toBeDefined();
+ });
- test("renders long format with hours", () => {
- render(<LeftTime sec={3661} />);
- expect(screen.getByText("1h 1m 1s")).toBeDefined();
- });
+ test("renders long format with hours", () => {
+ render(<LeftTime sec={3661} />);
+ expect(screen.getByText("1h 1m 1s")).toBeDefined();
+ });
- test("renders long format with days", () => {
- render(<LeftTime sec={90061} />);
- expect(screen.getByText("1d 1h 1m 1s")).toBeDefined();
- });
+ test("renders long format with days", () => {
+ render(<LeftTime sec={90061} />);
+ expect(screen.getByText("1d 1h 1m 1s")).toBeDefined();
+ });
- test("renders long format omitting zero day and minute", () => {
- render(<LeftTime sec={3605} />);
- expect(screen.getByText("1h 5s")).toBeDefined();
- });
+ test("renders long format omitting zero day and minute", () => {
+ render(<LeftTime sec={3605} />);
+ expect(screen.getByText("1h 5s")).toBeDefined();
+ });
});
diff --git a/frontend/app/components/Gaming/LeftTime.tsx b/frontend/app/components/Gaming/LeftTime.tsx
index 5013c76..a7678d6 100644
--- a/frontend/app/components/Gaming/LeftTime.tsx
+++ b/frontend/app/components/Gaming/LeftTime.tsx
@@ -1,26 +1,26 @@
type Props = {
- sec: number;
+ sec: number;
};
export default function LeftTime({ sec }: Props) {
- const s = sec % 60;
- const m = Math.floor(sec / 60) % 60;
- const h = Math.floor(sec / 3600) % 24;
- const d = Math.floor(sec / 86400);
+ const s = sec % 60;
+ const m = Math.floor(sec / 60) % 60;
+ const h = Math.floor(sec / 3600) % 24;
+ const d = Math.floor(sec / 86400);
- let leftTime = "";
- if (d > 0 || h > 0) {
- // 1d 2h 3m 4s
- leftTime = [
- d > 0 ? `${d}d` : "",
- h > 0 ? `${h}h` : "",
- m > 0 ? `${m}m` : "",
- `${s}s`,
- ].join(" ");
- } else {
- // 03:04
- leftTime = `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
- }
+ let leftTime = "";
+ if (d > 0 || h > 0) {
+ // 1d 2h 3m 4s
+ leftTime = [
+ d > 0 ? `${d}d` : "",
+ h > 0 ? `${h}h` : "",
+ m > 0 ? `${m}m` : "",
+ `${s}s`,
+ ].join(" ");
+ } else {
+ // 03:04
+ leftTime = `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
+ }
- return <div className="text-2xl md:text-3xl">{leftTime}</div>;
+ return <div className="text-2xl md:text-3xl">{leftTime}</div>;
}
diff --git a/frontend/app/components/Gaming/ProblemColumn.tsx b/frontend/app/components/Gaming/ProblemColumn.tsx
index a355ac4..40d0716 100644
--- a/frontend/app/components/Gaming/ProblemColumn.tsx
+++ b/frontend/app/components/Gaming/ProblemColumn.tsx
@@ -3,25 +3,25 @@ import TitledColumn from "../TitledColumn";
import ProblemColumnContent from "./ProblemColumnContent";
type Props = {
- title: string;
- description: string;
- language: SupportedLanguage;
- sampleCode: string;
+ title: string;
+ description: string;
+ language: SupportedLanguage;
+ sampleCode: string;
};
export default function ProblemColumn({
- title,
- description,
- language,
- sampleCode,
+ title,
+ description,
+ language,
+ sampleCode,
}: Props) {
- return (
- <TitledColumn title={title}>
- <ProblemColumnContent
- description={description}
- sampleCode={sampleCode}
- language={language}
- />
- </TitledColumn>
- );
+ return (
+ <TitledColumn title={title}>
+ <ProblemColumnContent
+ description={description}
+ sampleCode={sampleCode}
+ language={language}
+ />
+ </TitledColumn>
+ );
}
diff --git a/frontend/app/components/Gaming/ProblemColumnContent.tsx b/frontend/app/components/Gaming/ProblemColumnContent.tsx
index 1a7cb36..bc6b43a 100644
--- a/frontend/app/components/Gaming/ProblemColumnContent.tsx
+++ b/frontend/app/components/Gaming/ProblemColumnContent.tsx
@@ -4,87 +4,87 @@ import CodeBlock from "./CodeBlock";
import InlineCode from "./InlineCode";
function PhpNotice() {
- return (
- <FoldableBorderedContainerWithCaption caption="スコア計算・PHP 環境">
- <div className="text-gray-700 flex flex-col gap-2">
- <p>
- スコアはコード中の全 ASCII
- 空白文字を除去した後のバイト数です。また、先頭や末尾に置かれた PHP
- タグ (<InlineCode code="<?php" />、<InlineCode code="<?" />、
- <InlineCode code="?>" />) はカウントされません。
- </p>
- <p>
- 同じスコアを出した場合、より提出が早かったプレイヤーの勝ちとなります。
- </p>
- <p>
- この環境の PHP バージョンは{" "}
- <strong className="font-bold">8.5.3</strong> です。 mbstring
- を除くほとんどの拡張は無効化されています。
- また、ファイルやネットワークアクセスはできません。
- </p>
- <p>
- テストの成否は、標準出力へ出力された文字列を比較して判定されます。
- 末尾の改行はあってもなくても構いません。
- 標準エラー出力の内容は無視されますが、fatal error
- 等で実行が中断された場合は失敗扱いとなります。
- </p>
- <p>
- なお、
- <InlineCode code="error_reporting" /> は{" "}
- <InlineCode code="E_ALL &amp; ~E_WARNING &amp; ~E_NOTICE &amp; ~E_DEPRECATED" />{" "}
- に設定されています。
- </p>
- </div>
- </FoldableBorderedContainerWithCaption>
- );
+ return (
+ <FoldableBorderedContainerWithCaption caption="スコア計算・PHP 環境">
+ <div className="text-gray-700 flex flex-col gap-2">
+ <p>
+ スコアはコード中の全 ASCII
+ 空白文字を除去した後のバイト数です。また、先頭や末尾に置かれた PHP
+ タグ (<InlineCode code="<?php" />、<InlineCode code="<?" />、
+ <InlineCode code="?>" />) はカウントされません。
+ </p>
+ <p>
+ 同じスコアを出した場合、より提出が早かったプレイヤーの勝ちとなります。
+ </p>
+ <p>
+ この環境の PHP バージョンは{" "}
+ <strong className="font-bold">8.5.3</strong> です。 mbstring
+ を除くほとんどの拡張は無効化されています。
+ また、ファイルやネットワークアクセスはできません。
+ </p>
+ <p>
+ テストの成否は、標準出力へ出力された文字列を比較して判定されます。
+ 末尾の改行はあってもなくても構いません。
+ 標準エラー出力の内容は無視されますが、fatal error
+ 等で実行が中断された場合は失敗扱いとなります。
+ </p>
+ <p>
+ なお、
+ <InlineCode code="error_reporting" /> は{" "}
+ <InlineCode code="E_ALL &amp; ~E_WARNING &amp; ~E_NOTICE &amp; ~E_DEPRECATED" />{" "}
+ に設定されています。
+ </p>
+ </div>
+ </FoldableBorderedContainerWithCaption>
+ );
}
function SwiftNotice() {
- return (
- <FoldableBorderedContainerWithCaption caption="スコア計算・Swift 環境">
- <div className="text-gray-700 flex flex-col gap-2">
- <p>スコアはコード中の全 ASCII 空白文字を除去した後のバイト数です。</p>
- <p>
- 同じスコアを出した場合、より提出が早かったプレイヤーの勝ちとなります。
- </p>
- <p>
- この環境の Swift バージョンは{" "}
- <strong className="font-bold">6.1.2</strong> です。
- ファイルアクセスやネットワークアクセスはできません。
- </p>
- <p>
- テストの成否は、標準出力へ出力された文字列を比較して判定されます。
- 末尾の改行はあってもなくても構いません。
- 標準エラー出力の内容は無視されますが、fatal error
- 等で実行が中断された場合は失敗扱いとなります。
- </p>
- </div>
- </FoldableBorderedContainerWithCaption>
- );
+ return (
+ <FoldableBorderedContainerWithCaption caption="スコア計算・Swift 環境">
+ <div className="text-gray-700 flex flex-col gap-2">
+ <p>スコアはコード中の全 ASCII 空白文字を除去した後のバイト数です。</p>
+ <p>
+ 同じスコアを出した場合、より提出が早かったプレイヤーの勝ちとなります。
+ </p>
+ <p>
+ この環境の Swift バージョンは{" "}
+ <strong className="font-bold">6.1.2</strong> です。
+ ファイルアクセスやネットワークアクセスはできません。
+ </p>
+ <p>
+ テストの成否は、標準出力へ出力された文字列を比較して判定されます。
+ 末尾の改行はあってもなくても構いません。
+ 標準エラー出力の内容は無視されますが、fatal error
+ 等で実行が中断された場合は失敗扱いとなります。
+ </p>
+ </div>
+ </FoldableBorderedContainerWithCaption>
+ );
}
type Props = {
- description: string;
- language: SupportedLanguage;
- sampleCode: string;
+ description: string;
+ language: SupportedLanguage;
+ sampleCode: string;
};
export default function ProblemColumnContent({
- description,
- language,
- sampleCode,
+ description,
+ language,
+ sampleCode,
}: Props) {
- return (
- <>
- <FoldableBorderedContainerWithCaption caption="問題">
- <pre className="text-gray-700 whitespace-pre-wrap break-words">
- {description}
- </pre>
- </FoldableBorderedContainerWithCaption>
- <FoldableBorderedContainerWithCaption caption="サンプルコード">
- <CodeBlock code={sampleCode} language={language} />
- </FoldableBorderedContainerWithCaption>
- {language === "php" ? <PhpNotice /> : <SwiftNotice />}
- </>
- );
+ return (
+ <>
+ <FoldableBorderedContainerWithCaption caption="問題">
+ <pre className="text-gray-700 whitespace-pre-wrap break-words">
+ {description}
+ </pre>
+ </FoldableBorderedContainerWithCaption>
+ <FoldableBorderedContainerWithCaption caption="サンプルコード">
+ <CodeBlock code={sampleCode} language={language} />
+ </FoldableBorderedContainerWithCaption>
+ {language === "php" ? <PhpNotice /> : <SwiftNotice />}
+ </>
+ );
}
diff --git a/frontend/app/components/Gaming/RankingTable.tsx b/frontend/app/components/Gaming/RankingTable.tsx
index 60f4808..b0a6116 100644
--- a/frontend/app/components/Gaming/RankingTable.tsx
+++ b/frontend/app/components/Gaming/RankingTable.tsx
@@ -5,43 +5,43 @@ import CodePopover from "./CodePopover";
import DataTable, { DataTableCell, formatUnixTimestamp } from "./DataTable";
type Props = {
- problemLanguage: SupportedLanguage;
+ problemLanguage: SupportedLanguage;
};
export default function RankingTable({ problemLanguage }: Props) {
- const ranking = useAtomValue(rankingAtom);
- const showCode = ranking.some((entry) => entry.code != null);
+ const ranking = useAtomValue(rankingAtom);
+ const showCode = ranking.some((entry) => entry.code != null);
- return (
- <DataTable
- headers={[
- "順位",
- "プレイヤー",
- "スコア",
- "提出時刻",
- ...(showCode ? ["コード"] : []),
- ]}
- >
- {ranking.map((entry, index) => (
- <tr key={entry.player.user_id}>
- <DataTableCell>{index + 1}</DataTableCell>
- <DataTableCell>
- {entry.player.display_name}
- {entry.player.label && ` (${entry.player.label})`}
- </DataTableCell>
- <DataTableCell>{entry.score}</DataTableCell>
- <DataTableCell>
- {formatUnixTimestamp(entry.submitted_at)}
- </DataTableCell>
- {showCode && (
- <DataTableCell>
- {entry.code && (
- <CodePopover code={entry.code} language={problemLanguage} />
- )}
- </DataTableCell>
- )}
- </tr>
- ))}
- </DataTable>
- );
+ return (
+ <DataTable
+ headers={[
+ "順位",
+ "プレイヤー",
+ "スコア",
+ "提出時刻",
+ ...(showCode ? ["コード"] : []),
+ ]}
+ >
+ {ranking.map((entry, index) => (
+ <tr key={entry.player.user_id}>
+ <DataTableCell>{index + 1}</DataTableCell>
+ <DataTableCell>
+ {entry.player.display_name}
+ {entry.player.label && ` (${entry.player.label})`}
+ </DataTableCell>
+ <DataTableCell>{entry.score}</DataTableCell>
+ <DataTableCell>
+ {formatUnixTimestamp(entry.submitted_at)}
+ </DataTableCell>
+ {showCode && (
+ <DataTableCell>
+ {entry.code && (
+ <CodePopover code={entry.code} language={problemLanguage} />
+ )}
+ </DataTableCell>
+ )}
+ </tr>
+ ))}
+ </DataTable>
+ );
}
diff --git a/frontend/app/components/Gaming/Score.tsx b/frontend/app/components/Gaming/Score.tsx
index ee23a6c..8e1e61d 100644
--- a/frontend/app/components/Gaming/Score.tsx
+++ b/frontend/app/components/Gaming/Score.tsx
@@ -1,36 +1,36 @@
import { useEffect, useState } from "react";
type Props = {
- status: string | null;
- score: number | null;
+ status: string | null;
+ score: number | null;
};
export default function Score({ status, score }: Props) {
- const [randomScore, setRandomScore] = useState<number | null>(null);
+ const [randomScore, setRandomScore] = useState<number | null>(null);
- useEffect(() => {
- if (status !== "running") {
- return;
- }
+ useEffect(() => {
+ if (status !== "running") {
+ return;
+ }
- const intervalId = setInterval(() => {
- const maxValue = Math.pow(10, String(score ?? 100).length) - 1;
- const minValue = Math.pow(10, String(score ?? 100).length - 1);
- const randomValue =
- Math.floor(Math.random() * (maxValue - minValue + 1)) + minValue;
- setRandomScore(randomValue);
- }, 50);
+ const intervalId = setInterval(() => {
+ const maxValue = Math.pow(10, String(score ?? 100).length) - 1;
+ const minValue = Math.pow(10, String(score ?? 100).length - 1);
+ const randomValue =
+ Math.floor(Math.random() * (maxValue - minValue + 1)) + minValue;
+ setRandomScore(randomValue);
+ }, 50);
- return () => {
- clearInterval(intervalId);
- };
- }, [status, score]);
+ return () => {
+ clearInterval(intervalId);
+ };
+ }, [status, score]);
- const displayScore = status === "running" ? randomScore : score;
+ const displayScore = status === "running" ? randomScore : score;
- return (
- <span className={status === "running" ? "animate-pulse" : ""}>
- {displayScore}
- </span>
- );
+ return (
+ <span className={status === "running" ? "animate-pulse" : ""}>
+ {displayScore}
+ </span>
+ );
}
diff --git a/frontend/app/components/Gaming/ScoreBar.tsx b/frontend/app/components/Gaming/ScoreBar.tsx
index 6a291cd..50a2402 100644
--- a/frontend/app/components/Gaming/ScoreBar.tsx
+++ b/frontend/app/components/Gaming/ScoreBar.tsx
@@ -1,30 +1,30 @@
type Props = {
- scoreA: number | null;
- scoreB: number | null;
- bgA: string;
- bgB: string;
+ scoreA: number | null;
+ scoreB: number | null;
+ bgA: string;
+ bgB: string;
};
export default function ScoreBar({ scoreA, scoreB, bgA, bgB }: Props) {
- let scoreRatio;
- if (scoreA === null && scoreB === null) {
- scoreRatio = 50;
- } else if (scoreA === null) {
- scoreRatio = 0;
- } else if (scoreB === null) {
- scoreRatio = 100;
- } else {
- const rawRatio = scoreB / (scoreA + scoreB);
- const k = 3.0;
- const emphasizedRatio =
- Math.pow(rawRatio, k) /
- (Math.pow(rawRatio, k) + Math.pow(1 - rawRatio, k));
- scoreRatio = emphasizedRatio * 100;
- }
+ let scoreRatio;
+ if (scoreA === null && scoreB === null) {
+ scoreRatio = 50;
+ } else if (scoreA === null) {
+ scoreRatio = 0;
+ } else if (scoreB === null) {
+ scoreRatio = 100;
+ } else {
+ const rawRatio = scoreB / (scoreA + scoreB);
+ const k = 3.0;
+ const emphasizedRatio =
+ Math.pow(rawRatio, k) /
+ (Math.pow(rawRatio, k) + Math.pow(1 - rawRatio, k));
+ scoreRatio = emphasizedRatio * 100;
+ }
- return (
- <div className={`w-full ${bgB}`}>
- <div className={`h-10 ${bgA}`} style={{ width: `${scoreRatio}%` }}></div>
- </div>
- );
+ return (
+ <div className={`w-full ${bgB}`}>
+ <div className={`h-10 ${bgA}`} style={{ width: `${scoreRatio}%` }}></div>
+ </div>
+ );
}