Skip to content

Commit

Permalink
feat: Add checkpoint problem (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tatehito authored Feb 8, 2024
1 parent edb1120 commit 9781b56
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 84 deletions.
99 changes: 99 additions & 0 deletions src/app/(withAuth)/problems/[problemId]/CheckpointProblem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
'use client';

import { Box, Button, Flex, HStack, VStack } from '@chakra-ui/react';
import { useEffect, useRef, useState } from 'react';

import { SyntaxHighlighter } from '../../../../components/organisms/SyntaxHighlighter';
import type { TurtleGraphicsHandle } from '../../../../components/organisms/TurtleGraphics';
import { TurtleGraphics } from '../../../../components/organisms/TurtleGraphics';
import { generateProgram } from '../../../../problems/problemData';
import type { ProblemType } from '../../../../types';
import { getLanguageIdFromSessionStorage } from '../../../lib/SessionStorage';

interface CheckpointProblemProps {
problemId: string;
setStep: (step: ProblemType) => void;
}

export const CheckpointProblem: React.FC<CheckpointProblemProps> = ({ problemId }) => {
const turtleGraphicsRef = useRef<TurtleGraphicsHandle>(null);
const [selectedLanguageId, setSelectedLanguageId] = useState('');

// TODO: チェックポイントを取得する処理が実装できたら置き換える
const getCheckPointLines = [2, 4];
const [problemProgram, setProblemProgram] = useState<string>('');
const [beforeCheckPointLine, setBeforeCheckPointLine] = useState(1);
const [currentCheckPointLine, setCurrentCheckPointLine] = useState(getCheckPointLines[0]);

useEffect(() => {
setSelectedLanguageId(getLanguageIdFromSessionStorage());
}, []);

useEffect(() => {
setProblemProgram(generateProgram(problemId, selectedLanguageId));
}, [problemId, selectedLanguageId]);

const handleClickResetButton = (): void => {
turtleGraphicsRef.current?.init();
};

const handleClickAnswerButton = (): void => {
const isCorrect = turtleGraphicsRef.current?.isCorrect();

// TODO: 一旦アラートで表示
if (isCorrect) {
alert('正解です');

if (currentCheckPointLine === getCheckPointLines.at(-1)) return;

setBeforeCheckPointLine(currentCheckPointLine);
setCurrentCheckPointLine(getCheckPointLines[getCheckPointLines.indexOf(currentCheckPointLine) + 1]);
} else {
alert('不正解です');
// setStep('step');
}
};

return (
<Flex gap="6" w="100%">
<VStack spacing="10">
<Box>茶色にハイライトされている行における盤面を作成してください。</Box>
<Box>青色のハイライト時点の実行結果</Box>
<Box>
<TurtleGraphics
ref={turtleGraphicsRef}
beforeCheckPointLine={beforeCheckPointLine}
currentCheckPointLine={currentCheckPointLine}
isEnableOperation={false}
problemProgram={problemProgram}
/>
</Box>
<Box>茶色のハイライト時点の実行結果</Box>
<Box>
<TurtleGraphics
ref={turtleGraphicsRef}
beforeCheckPointLine={beforeCheckPointLine}
currentCheckPointLine={currentCheckPointLine}
isEnableOperation={true}
problemProgram={problemProgram}
/>
</Box>
</VStack>
<VStack align="end" minW="50%" overflow="hidden">
<Button colorScheme="gray">解説</Button>
<Box h="840px" w="100%">
<SyntaxHighlighter
beforeCheckPointLine={beforeCheckPointLine}
code={problemProgram}
currentCheckPointLine={currentCheckPointLine}
programmingLanguageId={selectedLanguageId}
/>
</Box>
<HStack>
<Button onClick={() => handleClickResetButton()}>リセット</Button>
<Button onClick={() => handleClickAnswerButton()}>解答</Button>
</HStack>
</VStack>
</Flex>
);
};
65 changes: 65 additions & 0 deletions src/app/(withAuth)/problems/[problemId]/ExecutionResultProblem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
'use client';

import { Box, Button, Flex, HStack, VStack } from '@chakra-ui/react';
import { useEffect, useState, useRef } from 'react';

import { SyntaxHighlighter } from '../../../../components/organisms/SyntaxHighlighter';
import type { TurtleGraphicsHandle } from '../../../../components/organisms/TurtleGraphics';
import { TurtleGraphics } from '../../../../components/organisms/TurtleGraphics';
import { generateProgram } from '../../../../problems/problemData';
import type { ProblemType } from '../../../../types';
import { getLanguageIdFromSessionStorage } from '../../../lib/SessionStorage';

interface ExecutionResultProblemProps {
problemId: string;
setStep: (step: ProblemType) => void;
}

export const ExecutionResultProblem: React.FC<ExecutionResultProblemProps> = ({ problemId, setStep }) => {
const turtleGraphicsRef = useRef<TurtleGraphicsHandle>(null);
const [selectedLanguageId, setSelectedLanguageId] = useState('');

useEffect(() => {
setSelectedLanguageId(getLanguageIdFromSessionStorage());
}, []);

const problemProgram = generateProgram(problemId, selectedLanguageId);

const handleClickResetButton = (): void => {
turtleGraphicsRef.current?.init();
};

const handleClickAnswerButton = (): void => {
const isCorrect = turtleGraphicsRef.current?.isCorrect();

// TODO: 一旦アラートで表示
if (isCorrect) {
alert('正解です');
} else {
alert('不正解です');
setStep('checkpoint');
}
};

return (
<Flex gap="6" w="100%">
<VStack spacing="10">
<Box>プログラムの実行後の結果を解答してください。</Box>
<Box>
<TurtleGraphics ref={turtleGraphicsRef} isEnableOperation={true} problemProgram={problemProgram} />
</Box>
</VStack>
<VStack align="end" minW="50%" overflow="hidden">
<Button colorScheme="gray">解説</Button>
{/* 画面に収まる高さに設定 */}
<Box h="calc(100vh - 370px)" w="100%">
<SyntaxHighlighter code={problemProgram} programmingLanguageId={selectedLanguageId} />
</Box>
<HStack>
<Button onClick={() => handleClickResetButton()}>リセット</Button>
<Button onClick={() => handleClickAnswerButton()}>解答</Button>
</HStack>
</VStack>
</Flex>
);
};
65 changes: 18 additions & 47 deletions src/app/(withAuth)/problems/[problemId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,34 @@
'use client';

import { Box, Button, Flex, HStack, Heading, VStack } from '@chakra-ui/react';
import { Heading, VStack } from '@chakra-ui/react';
import type { NextPage } from 'next';
import { useEffect, useState, useRef } from 'react';
import { useState } from 'react';

import { SyntaxHighlighter } from '../../../../components/organisms/SyntaxHighlighter';
import type { TurtleGraphicsHandle } from '../../../../components/organisms/TurtleGraphics';
import { TurtleGraphics } from '../../../../components/organisms/TurtleGraphics';
import { programIdToName, generateProgram } from '../../../../problems/problemData';
import { getLanguageIdFromSessionStorage } from '../../../lib/SessionStorage';
import { programIdToName } from '../../../../problems/problemData';
import type { ProblemType } from '../../../../types';

const ProblemPage: NextPage<{ params: { problemId: string } }> = ({ params }) => {
const turtleGraphicsRef = useRef<TurtleGraphicsHandle>(null);
const [selectedLanguageId, setSelectedLanguageId] = useState('');

useEffect(() => {
setSelectedLanguageId(getLanguageIdFromSessionStorage());
}, []);
import { CheckpointProblem } from './CheckpointProblem';
import { ExecutionResultProblem } from './ExecutionResultProblem';

const problemProgram = generateProgram(params.problemId, selectedLanguageId);

const handleClickResetButton = (): void => {
turtleGraphicsRef.current?.reset();
};

const handleClickAnswerButton = (): void => {
const isCorrect = turtleGraphicsRef.current?.isCorrect();

// TODO: 一旦アラートで表示
if (isCorrect) {
alert('正解です');
} else {
alert('不正解です');
const ProblemPage: NextPage<{ params: { problemId: string } }> = ({ params }) => {
const [step, setStep] = useState<ProblemType>('normal');

const ProblemComponent: React.FC = () => {
switch (step) {
case 'normal': {
return <ExecutionResultProblem problemId={params.problemId} setStep={setStep} />;
}
case 'checkpoint': {
return <CheckpointProblem problemId={params.problemId} setStep={setStep} />;
}
}
};

return (
<main>
<VStack spacing="4">
<Heading as="h1">{programIdToName[params.problemId]}</Heading>
<Flex gap="6" w="100%">
<VStack spacing="10">
<Box>プログラムの実行後の結果を解答してください。</Box>
<Box>
<TurtleGraphics ref={turtleGraphicsRef} isEnableOperation={true} problemProgram={problemProgram} />
</Box>
</VStack>
<VStack align="end" minW="50%" overflow="hidden">
<Button colorScheme="gray">解説</Button>
{/* 画面に収まる高さに設定 */}
<Box h="calc(100vh - 370px)" w="100%">
<SyntaxHighlighter code={problemProgram} programmingLanguageId={selectedLanguageId} />
</Box>
<HStack>
<Button onClick={() => handleClickResetButton()}>リセット</Button>
<Button onClick={() => handleClickAnswerButton()}>解答</Button>
</HStack>
</VStack>
</Flex>
<ProblemComponent />
</VStack>
</main>
);
Expand Down
19 changes: 14 additions & 5 deletions src/app/lib/solveProblem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ export function solveProblem(program: string): SolveProblemResult {

const characters = executeEval(mergedCommand);

const board = new BoardClass();
for (const history of histories) {
if (!history.characters) continue;

for (const character of history.characters) {
board.updateGrid(character);
}
}
for (const character of characters) {
board.updateGrid(character);
}
Expand All @@ -72,14 +80,15 @@ export function solveProblem(program: string): SolveProblemResult {
export function isAnswerCorrect(
problemProgram: string,
answerCharacters: CharacterClass[],
answerBoard: BoardClass
answerBoard: BoardClass,
step?: number
): boolean {
const answer = solveProblem(problemProgram);
const correctAnswer = solveProblem(problemProgram).histories?.at(step || -1);

if (!answer.characters || !answer.board) return false;
if (!correctAnswer || !correctAnswer.characters) return false;

// 順番は関係なく、id以外のキャラクターの状態が一致しているかチェック
const isCorrectCharacters: boolean = answer.characters.every((correctCharacter) => {
const isCorrectCharacters: boolean = correctAnswer.characters.every((correctCharacter) => {
const character = answerCharacters.find(
(answerCharacter) =>
answerCharacter.name === correctCharacter.name &&
Expand All @@ -93,7 +102,7 @@ export function isAnswerCorrect(
});

// すべてのセルの色が一致しているかチェック
const isCorrectBoard: boolean = answer.board.grid.every((rows, rowIndex) =>
const isCorrectBoard: boolean = correctAnswer.board.grid.every((rows, rowIndex) =>
rows.every((column, columnIndex) => {
const cell = answerBoard.grid[rowIndex][columnIndex];
return cell.color === column.color;
Expand Down
20 changes: 13 additions & 7 deletions src/components/organisms/SyntaxHighlighter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import { Prism } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism';

interface SyntaxHighlighterProps {
beforeCheckPointLine?: number;
code: string;
programmingLanguageId: string;
currentCheckPointLine?: number;
}

export const SyntaxHighlighter: React.FC<SyntaxHighlighterProps> = ({
beforeCheckPointLine: beforeCheckPointLine,
code: code,
currentCheckPointLine: currentCheckPointLine,
programmingLanguageId: programmingLanguageId,
}) => {
return (
Expand All @@ -25,15 +29,17 @@ export const SyntaxHighlighter: React.FC<SyntaxHighlighterProps> = ({
}}
language={programmingLanguageId === 'c' ? 'cpp' : programmingLanguageId}
lineNumberStyle={{ paddingRight: 0, marginRight: 16, minWidth: '1rem' }}
// lineProps={(lineNumber) => {
lineProps={() => {
const style = {
lineProps={(lineNumber) => {
const style: React.CSSProperties = {
padding: 0,
backgroundColor: '',
};
// TODO: チェックポイント問題・ステップ問題のハイライト
// if (isStepPage && problem && lineNumber === problem.traceList[highlightedLineCount].row + 1) {
// style.backgroundColor = '#744210';
// }
// チェックポイント問題・ステップ問題のハイライト
if (lineNumber === beforeCheckPointLine) {
style.backgroundColor = '#2E3D9F';
} else if (lineNumber === currentCheckPointLine) {
style.backgroundColor = '#744210';
}
return { style };
}}
showLineNumbers={true}
Expand Down
Loading

0 comments on commit 9781b56

Please sign in to comment.