diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-03-06 02:18:40 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-03-06 02:18:40 +0900 |
| commit | 46f9ba5d8c295454381655e6ec02ad3cf8bd79db (patch) | |
| tree | c54719cb129ee05f96c4898219588062f71daa36 /frontend/app/components/Gaming | |
| parent | 27f509ccf4fbfeaa1bc2580ae2251461dc44ebfa (diff) | |
| download | phperkaigi-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.tsx | 94 | ||||
| -rw-r--r-- | frontend/app/components/Gaming/CodePopover.tsx | 62 | ||||
| -rw-r--r-- | frontend/app/components/Gaming/DataTable.test.tsx | 104 | ||||
| -rw-r--r-- | frontend/app/components/Gaming/DataTable.tsx | 64 | ||||
| -rw-r--r-- | frontend/app/components/Gaming/ExecStatusIndicatorIcon.tsx | 72 | ||||
| -rw-r--r-- | frontend/app/components/Gaming/InlineCode.tsx | 12 | ||||
| -rw-r--r-- | frontend/app/components/Gaming/LeftTime.test.tsx | 58 | ||||
| -rw-r--r-- | frontend/app/components/Gaming/LeftTime.tsx | 38 | ||||
| -rw-r--r-- | frontend/app/components/Gaming/ProblemColumn.tsx | 34 | ||||
| -rw-r--r-- | frontend/app/components/Gaming/ProblemColumnContent.tsx | 146 | ||||
| -rw-r--r-- | frontend/app/components/Gaming/RankingTable.tsx | 70 | ||||
| -rw-r--r-- | frontend/app/components/Gaming/Score.tsx | 48 | ||||
| -rw-r--r-- | frontend/app/components/Gaming/ScoreBar.tsx | 48 |
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 & ~E_WARNING & ~E_NOTICE & ~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 & ~E_WARNING & ~E_NOTICE & ~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> + ); } |
