Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions client/.prettierignore

This file was deleted.

4 changes: 0 additions & 4 deletions client/.prettierrc

This file was deleted.

4 changes: 2 additions & 2 deletions client/app/_components/HomeClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ export const HomeClient = () => {
return (
<>
<div className="mb-2 flex items-center">
<span className="mr-2 text-green-400" aria-label="出力">
<span className="mr-2 text-green-400" aria-hidden="true">
&gt;
</span>
<span className="text-white">早押しコードリーディング対決</span>
</div>

<nav className="mt-8 flex flex-col gap-4" role="menu">
<nav className="mt-8 flex flex-col gap-4">
<Button
context="home"
onClick={handleDisplayMakeModal}
Expand Down
2 changes: 1 addition & 1 deletion client/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default function Home() {
return (
<TerminalLayout cli="--init">
<div className="glitch my-4">
<h1 className="font-sixtyfour typing-text text-4xl font-bold text-green-400 md:text-5xl">
<h1 className="typing-text font-bold font-sixtyfour text-4xl text-green-400 md:text-5xl">
CODE RUSH
</h1>
</div>
Expand Down
12 changes: 6 additions & 6 deletions client/app/room/[roomId]/_components/QuestionLog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism";
export const QuestionLog = () => {
return (
<div className="text-center">
<h1 className="font-sixtyfour my-5 text-4xl font-bold text-green-400 md:text-5xl">
<h1 className="my-5 font-bold font-sixtyfour text-4xl text-green-400 md:text-5xl">
QUESTION LOG
</h1>

<div className="my-4 border-t border-green-700" />
<div className="my-4 border-green-700 border-t" />

<div className="max-h-96 space-y-4 overflow-y-auto">
{[
Expand Down Expand Up @@ -90,7 +90,7 @@ export const QuestionLog = () => {
].map((q, index) => (
<div key={q.id} className="rounded bg-black/60 p-5 text-left">
<div className="mb-2 font-bold text-green-400">Question {q.id}</div>
<pre className="mb-3 overflow-x-auto text-sm text-gray-300">
<pre className="mb-3 overflow-x-auto text-gray-300 text-sm">
<SyntaxHighlighter
language="c"
style={vscDarkPlus}
Expand All @@ -102,16 +102,16 @@ export const QuestionLog = () => {
<div className="mb-2 grid grid-cols-2 gap-1">
{q.choices.map((choice, idx) => (
<div
key={idx}
className="flex items-center gap-2 text-sm text-gray-400"
key={`choice-${idx}-${choice.slice(0, 10)}`}
className="flex items-center gap-2 text-gray-400 text-sm"
>
<span>{String.fromCharCode(65 + idx)}.</span>
<span>{choice}</span>
</div>
))}
</div>
<div className="font-bold text-yellow-400">Answer: {q.answer}</div>
{index < 9 && <div className="border-t border-green-700" />}
{index < 9 && <div className="border-green-700 border-t" />}
</div>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { memo } from "react";
import { memo } from "react";
import { Button } from "@/components/Button";
import { STYLE_CLASSES } from "../../_constants/quiz";

Expand Down
8 changes: 4 additions & 4 deletions client/app/room/[roomId]/_components/ingame/CodeDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { memo, useRef, useState, useEffect } from "react";
import { memo, useCallback, useEffect, useRef, useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism";

Expand All @@ -17,14 +17,14 @@ export const CodeDisplay = memo((props: CodeDisplayProps) => {
const displayContent = question || code || "";

// スクロール可能かどうかをチェックする関数
const checkScrollable = () => {
const checkScrollable = useCallback(() => {
if (scrollRef.current) {
const { scrollHeight, clientHeight, scrollTop } = scrollRef.current;
const isScrollable = scrollHeight > clientHeight;
const isNotAtBottom = scrollTop < scrollHeight - clientHeight - 5; // 5pxの余裕
setShowBottomShadow(isScrollable && isNotAtBottom);
}
};
}, []);

useEffect(() => {
checkScrollable();
Expand All @@ -40,7 +40,7 @@ export const CodeDisplay = memo((props: CodeDisplayProps) => {
window.removeEventListener("resize", checkScrollable);
};
}
}, [displayContent]);
}, [checkScrollable]);

return (
<div className="relative mb-8">
Expand Down
41 changes: 20 additions & 21 deletions client/app/room/[roomId]/_components/ingame/QuizGameClient.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
"use client";

import { useCallback, useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { TerminalLayout } from "@/components/TerminalLayout";
import { QuizTimerSection } from "./QuizTimerSection";
import { CodeDisplay } from "./CodeDisplay";
import { AnswerChoice } from "./AnswerChoice";
import { QuizResultScreen } from "./QuizResult";
import { QuestionLog } from "../QuestionLog";
import { useCallback, useEffect, useState } from "react";
import type { ClientMessage, ServerMessage } from "@/app/_types/api";
import { Button } from "@/components/Button";
import { TerminalLayout } from "@/components/TerminalLayout";
import { useWebSocket } from "@/hooks/useWebSocket";
import { ANIMATION } from "../../_constants/quiz";
import type {
QuizResult,
AnswerState,
QuizResult,
WebSocketQuestion,
} from "../../_types/quiz";
import type { ClientMessage, ServerMessage } from "@/app/_types/api";
import { ANIMATION } from "../../_constants/quiz";
import { QuestionLog } from "../QuestionLog";
import { AnswerChoice } from "./AnswerChoice";
import { CodeDisplay } from "./CodeDisplay";
import { QuizResultScreen } from "./QuizResult";
import { QuizTimerSection } from "./QuizTimerSection";

type QuizGameClientProps = {
onGameEnd?: () => void;
Expand Down Expand Up @@ -114,7 +114,7 @@ export const QuizGameClient = (props: QuizGameClientProps) => {
setCorrectAnswer(""); // 正解をリセット
}
break;
case "answer_result":
case "answer_result": {
const {
userId: answeredUserId,
correctAnswer,
Expand All @@ -126,9 +126,7 @@ export const QuizGameClient = (props: QuizGameClientProps) => {
// gamePhaseの代わりにquizResultで判定
// 正解の選択肢インデックスを見つける
const correctIndex =
currentQuestion?.choices.findIndex(
(choice) => choice === correctAnswer,
) ?? -1;
currentQuestion?.choices.indexOf(correctAnswer) ?? -1;

if (correctIndex !== -1) {
setAnswerState({
Expand All @@ -147,7 +145,8 @@ export const QuizGameClient = (props: QuizGameClientProps) => {
setScore(scores[userId]);
}
break;
case "game_over":
}
case "game_over": {
// 実際の問題数を設定(最後の問題番号から推定)
const actualTotalQuestions = currentQuestionIndex + 1;
setTotalQuestions(actualTotalQuestions);
Expand Down Expand Up @@ -198,6 +197,7 @@ export const QuizGameClient = (props: QuizGameClientProps) => {
handleQuizComplete(result);
}
break;
}
}
}, [
lastMessage,
Expand Down Expand Up @@ -294,9 +294,7 @@ export const QuizGameClient = (props: QuizGameClientProps) => {
if (!hasAnswered || !currentQuestion) return "default";

// 正解の選択肢インデックスを見つける
const correctIndex = currentQuestion.choices.findIndex(
(choice) => choice === correctAnswer,
);
const correctIndex = currentQuestion.choices.indexOf(correctAnswer);
const isCorrect = choiceIndex === correctIndex;

if (answerState.type === "time_expired") {
Expand All @@ -320,6 +318,7 @@ export const QuizGameClient = (props: QuizGameClientProps) => {
);

// 問題が変わったらタイピング状態をリセット
// biome-ignore lint/correctness/useExhaustiveDependencies: currentQuestionIndex triggers typing state reset on question change
useEffect(() => {
setIsTypingComplete(false);
}, [currentQuestionIndex]);
Expand All @@ -331,7 +330,7 @@ export const QuizGameClient = (props: QuizGameClientProps) => {
const handleKeyPress = (event: KeyboardEvent) => {
if (hasAnswered || isComplete) return;

const keyNumber = parseInt(event.key);
const keyNumber = parseInt(event.key, 10);
if (keyNumber >= 1 && keyNumber <= 4) {
submitAnswer(keyNumber - 1);
}
Expand Down Expand Up @@ -410,7 +409,7 @@ export const QuizGameClient = (props: QuizGameClientProps) => {
/>

<div className="mb-4 flex items-center">
<span className="mr-2 text-green-400" aria-label="出力">
<span className="mr-2 text-green-400" aria-hidden="true">
&gt;
</span>
<span className="text-white">
Expand All @@ -427,7 +426,7 @@ export const QuizGameClient = (props: QuizGameClientProps) => {
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
{currentQuestion.choices.map((choice, index) => (
<AnswerChoice
key={index}
key={`${index}-${choice.slice(0, 15)}`}
choice={choice}
index={index}
status={getAnswerChoiceStatus(index)}
Expand Down
5 changes: 2 additions & 3 deletions client/app/room/[roomId]/_components/ingame/QuizResult.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from "react";
import { useRouter } from "next/navigation";
import { Button } from "@/components/Button";
import type { QuizResult } from "../../_types/quiz";
Expand Down Expand Up @@ -28,11 +27,11 @@ export const QuizResultScreen = (props: QuizResultScreenProps) => {

return (
<div className="text-center">
<h1 className="font-sixtyfour typing-text my-5 text-4xl font-bold text-green-400 md:text-5xl">
<h1 className="typing-text my-5 font-bold font-sixtyfour text-4xl text-green-400 md:text-5xl">
{isWinner ? "WIN" : "GAME OVER"}
</h1>

<div className="my-4 border-t border-green-700" />
<div className="my-4 border-green-700 border-t" />
<div className="font-bold text-green-400">FINAL RESULT</div>
<pre className="mb-3 rounded bg-black/60 p-4">
<div className="flex items-center gap-2 py-1 text-green-400">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import React, { memo, useEffect } from "react";
import { memo, useEffect } from "react";
import { useQuizTimer } from "@/hooks/useQuizTimer";
import { TimerProgressBar } from "./TimerProgressBar";

Expand All @@ -20,14 +20,15 @@ export const QuizTimerSection = memo((props: QuizTimerSectionProps) => {
});

// 問題が変わったらタイマーリセット
// biome-ignore lint/correctness/useExhaustiveDependencies: currentQuestionIndex triggers timer reset on question change
useEffect(() => {
resetTimer();
}, [currentQuestionIndex, resetTimer]);

return (
<div className="my-4 flex items-center justify-between gap-2">
<TimerProgressBar progress={progress} />
<span className="font-mono text-xs text-white">Score: {score}</span>
<span className="font-mono text-white text-xs">Score: {score}</span>
</div>
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const TimerProgressBar = memo((props: TimerProgressBarProps) => {
const isFilled = index < filledBars;
return (
<span
key={index}
key={`bar-${index}`}
className={`flex-1 ${colorClass} text-xs`}
aria-hidden="true"
>
Expand Down
20 changes: 10 additions & 10 deletions client/app/room/_components/RoomClient.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"use client";

import { TerminalLayout } from "@/components/TerminalLayout";
import { memo, useCallback, useEffect, useState } from "react";
import type { ServerMessage } from "@/app/_types/api";
import { Button } from "@/components/Button";
import { memo, useState, useCallback, useEffect } from "react";
import { QuizGameClient } from "../[roomId]/_components/ingame/QuizGameClient";
import { TerminalLayout } from "@/components/TerminalLayout";
import { useWebSocket } from "@/hooks/useWebSocket";
import type { ServerMessage } from "@/app/_types/api";
import { QuizGameClient } from "../[roomId]/_components/ingame/QuizGameClient";

type Player = {
id: string;
Expand Down Expand Up @@ -39,9 +39,9 @@ const PlayerCard = memo((props: { player: Player }) => {
{/* プレイヤー情報 */}
<div className="flex-1">
<div className="flex items-center justify-between">
<div className="text-sm font-medium text-white">{player.name}</div>
<div className="font-medium text-sm text-white">{player.name}</div>
{player.isHost && (
<span className="rounded-sm border border-orange-500/30 bg-orange-500/20 px-2 py-0.5 text-xs font-medium text-orange-400">
<span className="rounded-sm border border-orange-500/30 bg-orange-500/20 px-2 py-0.5 font-medium text-orange-400 text-xs">
HOST
</span>
)}
Expand All @@ -57,8 +57,8 @@ const PlayerCard = memo((props: { player: Player }) => {
*/
const EmptySlot = memo(() => {
return (
<div className="flex min-h-[60px] items-center justify-center rounded-md border border-dashed border-gray-600/30 bg-gray-800/20 p-3">
<span className="text-xs text-gray-500" />
<div className="flex min-h-[60px] items-center justify-center rounded-md border border-gray-600/30 border-dashed bg-gray-800/20 p-3">
<span className="text-gray-500 text-xs" />
</div>
);
});
Expand Down Expand Up @@ -325,7 +325,7 @@ export const RoomClient = memo((props: RoomClientProps) => {
</span>
</div>
{connectionStatus === "error" && (
<div className="mt-2 text-xs text-red-400">
<div className="mt-2 text-red-400 text-xs">
サーバーに接続できません。サーバーが起動しているか確認してください。
</div>
)}
Expand Down Expand Up @@ -380,7 +380,7 @@ export const RoomClient = memo((props: RoomClientProps) => {

{/* 空きスロットを表示 */}
{Array.from({ length: emptySlots }, (_, index) => (
<EmptySlot key={`empty-${index}`} />
<EmptySlot key={`empty-slot-${emptySlots}-${index}`} />
))}
</div>
</div>
Expand Down
41 changes: 41 additions & 0 deletions client/biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"$schema": "https://biomejs.dev/schemas/2.2.0/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": true,
"includes": ["**", "!node_modules", "!.next", "!dist", "!build"]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"nursery": {
"useSortedClasses": "error"
},
"suspicious": {
"noArrayIndexKey": "off",
"noUnknownAtRules": "off"
}
},
"domains": {
"next": "recommended",
"react": "recommended"
}
},
"assist": {
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}
Loading