diff --git a/src/app/(withAuth)/courses/[courseId]/programs/[programId]/CheckpointProblem.tsx b/src/app/(withAuth)/courses/[courseId]/programs/[programId]/CheckpointProblem.tsx index 467a81ea..3e948672 100644 --- a/src/app/(withAuth)/courses/[courseId]/programs/[programId]/CheckpointProblem.tsx +++ b/src/app/(withAuth)/courses/[courseId]/programs/[programId]/CheckpointProblem.tsx @@ -7,6 +7,9 @@ import { SyntaxHighlighter } from '../../../../../../components/organisms/Syntax import type { TurtleGraphicsHandle } from '../../../../../../components/organisms/TurtleGraphics'; import { TurtleGraphics } from '../../../../../../components/organisms/TurtleGraphics'; import type { GeneratedProgram, ProblemType } from '../../../../../../types'; +import { solveProblem } from '../../../../../lib/solveProblem'; + +import { Variables } from './Variables'; interface CheckpointProblemProps { problemProgram: GeneratedProgram; @@ -31,6 +34,8 @@ export const CheckpointProblem: React.FC = ({ }) => { const turtleGraphicsRef = useRef(null); + const beforeCheckpointResult = solveProblem(problemProgram.excuteProgram).histories?.at(beforeCheckPointLine); + const handleClickResetButton = (): void => { turtleGraphicsRef.current?.init(); }; @@ -86,7 +91,7 @@ export const CheckpointProblem: React.FC = ({ - + = ({ programmingLanguageId={selectedLanguageId} /> + diff --git a/src/app/(withAuth)/courses/[courseId]/programs/[programId]/StepProblem.tsx b/src/app/(withAuth)/courses/[courseId]/programs/[programId]/StepProblem.tsx index bb5c7ffd..9e76ee6f 100644 --- a/src/app/(withAuth)/courses/[courseId]/programs/[programId]/StepProblem.tsx +++ b/src/app/(withAuth)/courses/[courseId]/programs/[programId]/StepProblem.tsx @@ -7,6 +7,9 @@ import { SyntaxHighlighter } from '../../../../../../components/organisms/Syntax import type { TurtleGraphicsHandle } from '../../../../../../components/organisms/TurtleGraphics'; import { TurtleGraphics } from '../../../../../../components/organisms/TurtleGraphics'; import type { GeneratedProgram } from '../../../../../../types'; +import { solveProblem } from '../../../../../lib/solveProblem'; + +import { Variables } from './Variables'; interface StepProblemProps { beforeCheckPointLine: number; @@ -29,6 +32,8 @@ export const StepProblem: React.FC = ({ }) => { const turtleGraphicsRef = useRef(null); + const beforeCheckpointResult = solveProblem(problemProgram.excuteProgram).histories?.at(beforeCheckPointLine); + const handleClickResetButton = (): void => { turtleGraphicsRef.current?.init(); }; @@ -80,7 +85,7 @@ export const StepProblem: React.FC = ({ - + = ({ programmingLanguageId={selectedLanguageId} /> + diff --git a/src/app/(withAuth)/courses/[courseId]/programs/[programId]/Variables.tsx b/src/app/(withAuth)/courses/[courseId]/programs/[programId]/Variables.tsx new file mode 100644 index 00000000..0c1a90ae --- /dev/null +++ b/src/app/(withAuth)/courses/[courseId]/programs/[programId]/Variables.tsx @@ -0,0 +1,73 @@ +import { Box, Table, TableCaption, TableContainer, Tbody, Td, Th, Thead, Tr, VStack } from '@chakra-ui/react'; +import React from 'react'; + +import type { CharacterDirection, CharacterVariable, Variable } from '../../../../../../types'; + +interface VariablesProps { + characterVariables?: CharacterVariable[]; + variables?: Variable[]; +} + +const directionJapanese: { [key in CharacterDirection]: string } = { + up: '上', + down: '下', + left: '左', + right: '右', +}; + +const penStateJapanese = (penDown: boolean): string => { + return penDown ? 'おいている' : 'あげている'; +}; + +export const Variables: React.FC = ({ characterVariables, variables }) => { + return ( + + + + キャラクター + + + + + + + + + + + {characterVariables?.map((variable) => ( + + + + + + + + ))} + +
変数名名前線の色向きペンの状態
{variable.name}{variable.value.name} + + {directionJapanese[variable.value.direction]}{penStateJapanese(variable.value.penDown)}
+
+ + + 変数 + + + + + + + + {variables?.map((variable) => ( + + + + + ))} + +
変数名
{variable.name}{variable.value}
+
+
+ ); +}; diff --git a/src/app/(withAuth)/courses/[courseId]/programs/[programId]/page.tsx b/src/app/(withAuth)/courses/[courseId]/programs/[programId]/page.tsx index 3861f05f..38e4c347 100644 --- a/src/app/(withAuth)/courses/[courseId]/programs/[programId]/page.tsx +++ b/src/app/(withAuth)/courses/[courseId]/programs/[programId]/page.tsx @@ -20,7 +20,7 @@ const ProblemPage: NextPage<{ params: { courseId: string; programId: string } }> const courseId = params.courseId; const programId = params.programId; // TODO: チェックポイントを取得する処理が実装できたら置き換える - const checkPointLines = [1, 4]; + const checkPointLines = [2, 6, 8, 12]; const [selectedLanguageId, setSelectedLanguageId] = useState(''); const [problemProgram, setProblemProgram] = useState(); diff --git a/src/app/lib/solveProblem.ts b/src/app/lib/solveProblem.ts index d0076a93..8715fe97 100644 --- a/src/app/lib/solveProblem.ts +++ b/src/app/lib/solveProblem.ts @@ -1,4 +1,4 @@ -import type { History, SolveProblemResult } from '../../types'; +import type { CharacterVariable, History, SolveProblemResult, Variable } from '../../types'; import { Board as BoardClass } from './Board'; import { Character as CharacterClass } from './Character'; @@ -10,30 +10,43 @@ export function parseProgram(program: string): string[] { .filter((line) => line !== ''); } -export function executeEval(command: string): CharacterClass[] { +export function executeEval(command: string): (CharacterVariable | Variable)[] { const Character = CharacterClass; // eslint-disable-line const Board = BoardClass; // eslint-disable-line - const characterVariableName = 'character'; - const charactersVariables = extractVariables(characterVariableName, command); + const variableNames = extractVariableNames(command); const semicolonEndedCommand = (() => { if (command.endsWith(';')) return command; return (command += ';'); })(); - const returnValueCommand = ` - [${charactersVariables}]; - `; + const result = variableNames.map((variableName) => { + const returnValueCommand = ` + ${variableName}; + `; + const mergedCommand = semicolonEndedCommand + '\n' + returnValueCommand; + return { name: variableName, value: eval(mergedCommand) }; + }); - const mergedCommand = semicolonEndedCommand + '\n' + returnValueCommand; + return result; +} - return eval(mergedCommand); +export function selectCharacterVariables(variables: (CharacterVariable | Variable)[]): CharacterVariable[] { + return variables.filter((variable) => variable.value instanceof CharacterClass) as CharacterVariable[]; } -export function extractVariables(variableName: string, command: string): string[] { - const regex = new RegExp(`${variableName}\\d+`, 'g'); - const matches = command.match(regex); +export function selectOtherVariables(variables: (CharacterVariable | Variable)[]): Variable[] { + return variables.filter((variable) => !(variable.value instanceof CharacterClass)) as Variable[]; +} + +export function extractVariableNames(command: string): string[] { + // 'const' 'let' 'var' で始まる変数名を comand から抽出する + const regex = /(?:const|let|var)\s+(\w+)\s*=\s*(.*?);/g; + const matches = [...command.matchAll(regex)]; + if (matches) { - return [...new Set(matches)]; + return matches.map((match) => { + return match[1]; + }); } return []; } @@ -41,7 +54,7 @@ export function extractVariables(variableName: string, command: string): string[ export function solveProblem(program: string): SolveProblemResult { const commands = parseProgram(program); const board = new BoardClass(); - const histories: History[] = [{ step: 0, characters: [], board }]; + const histories: History[] = [{ step: 0, characterVariables: [], board, otherVariables: [] }]; for (let i = 0; i < commands.length; i++) { if (i < commands.length) { @@ -51,26 +64,29 @@ export function solveProblem(program: string): SolveProblemResult { mergedCommand += commands[j]; } - const characters = executeEval(mergedCommand); + const variables = executeEval(mergedCommand); + const characterVariables = selectCharacterVariables(variables); + const otherVariables = selectOtherVariables(variables); const board = new BoardClass(); for (const history of histories) { - if (!history.characters) continue; + if (!history.characterVariables) continue; - for (const character of history.characters) { - board.updateGrid(character); + for (const character of history.characterVariables) { + board.updateGrid(character.value); } } - for (const character of characters) { - board.updateGrid(character); + for (const character of characterVariables) { + board.updateGrid(character.value as CharacterClass); } - histories.push({ step: histories.length + 1, characters, board }); + histories.push({ step: histories.length + 1, characterVariables, board, otherVariables }); } } const result: SolveProblemResult = { - characters: histories?.at(-1)?.characters, + characterVariables: histories?.at(-1)?.characterVariables, + otherVariables: histories?.at(-1)?.otherVariables, board: histories?.at(-1)?.board || board, histories, }; @@ -85,10 +101,12 @@ export function isAnswerCorrect( ): boolean { const correctAnswer = solveProblem(problemProgram).histories?.at(step || -1); - if (!correctAnswer || !correctAnswer.characters) return false; + if (!correctAnswer || !correctAnswer.characterVariables) return false; // 順番は関係なく、id以外のキャラクターの状態が一致しているかチェック - const isCorrectCharacters: boolean = correctAnswer.characters.every((correctCharacter) => { + const isCorrectCharacters: boolean = correctAnswer.characterVariables.every((characterVariable) => { + const correctCharacter = characterVariable.value; + const character = answerCharacters.find( (answerCharacter) => answerCharacter.name === correctCharacter.name && diff --git a/src/components/organisms/TurtleGraphics.tsx b/src/components/organisms/TurtleGraphics.tsx index ecb979e7..7fc9fdc1 100644 --- a/src/components/organisms/TurtleGraphics.tsx +++ b/src/components/organisms/TurtleGraphics.tsx @@ -43,7 +43,7 @@ export const TurtleGraphics = forwardRef character.value); setBoard(initBoard || new Board()); setCharacters(initCharacters || []); diff --git a/src/types.ts b/src/types.ts index f02db672..c258fc7a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -18,14 +18,20 @@ export type SelectedCell = { y: number; }; +export type CharacterVariable = { name: string; value: Character }; + +export type Variable = { name: string; value: string }; + export type History = { step: number; - characters: Character[] | undefined; + characterVariables: CharacterVariable[] | undefined; board: Board; + otherVariables: Variable[]; }; export type SolveProblemResult = { - characters: Character[] | undefined; + characterVariables: CharacterVariable[] | undefined; + otherVariables: Variable[] | undefined; board: Board; histories: History[] | undefined; }; diff --git a/tests/solveProblem.test.ts b/tests/solveProblem.test.ts index d703ce95..f39abe75 100644 --- a/tests/solveProblem.test.ts +++ b/tests/solveProblem.test.ts @@ -1,6 +1,12 @@ import { expect, test } from 'vitest'; -import { parseProgram, extractVariables, executeEval, solveProblem } from '../src/app/lib/solveProblem'; +import { + parseProgram, + extractVariableNames, + executeEval, + solveProblem, + selectCharacterVariables, +} from '../src/app/lib/solveProblem'; test('Parse a program', () => { const program = ` @@ -25,7 +31,6 @@ test('Parse a program', () => { }); test('Extract variables', () => { - const variableName = 'character'; const command = ` const character1 = new Character(); const character2 = new Character(); @@ -38,7 +43,7 @@ test('Extract variables', () => { character4.moveForward(); character5.moveForward(); `; - const characterVariables = extractVariables(variableName, command); + const characterVariables = extractVariableNames(command); expect(characterVariables).not.toBeFalsy(); expect(characterVariables).toEqual(['character1', 'character2', 'character3', 'character4', 'character5']); @@ -53,11 +58,12 @@ test('Execute eval (1character)', () => { character1.moveForward(); character1.moveForward(); `; - const characters = executeEval(command); + const variables = executeEval(command); + const characterVariables = selectCharacterVariables(variables); - expect(characters).not.toBeFalsy(); - expect(characters[0].x).toEqual(1); - expect(characters[0].y).toEqual(6); + expect(characterVariables).not.toBeFalsy(); + expect(characterVariables[0].value.x).toEqual(1); + expect(characterVariables[0].value.y).toEqual(6); }); test('Execute eval (2characters)', () => { @@ -69,13 +75,14 @@ test('Execute eval (2characters)', () => { character2.moveForward(); character2.moveForward(); `; - const characters = executeEval(command); + const variables = executeEval(command); + const characterVariables = selectCharacterVariables(variables); - expect(characters).not.toBeFalsy(); - expect(characters[0].x).toEqual(1); - expect(characters[0].y).toEqual(3); - expect(characters[1].x).toEqual(2); - expect(characters[1].y).toEqual(3); + expect(characterVariables).not.toBeFalsy(); + expect(characterVariables[0].value.x).toEqual(1); + expect(characterVariables[0].value.y).toEqual(3); + expect(characterVariables[1].value.x).toEqual(2); + expect(characterVariables[1].value.y).toEqual(3); }); test('Solve a problem (1character)', () => { @@ -100,12 +107,12 @@ test('Solve a problem (1character)', () => { const answer = solveProblem(problemProgram); expect(answer).not.toBeFalsy(); - expect(answer.characters).not.toBeFalsy(); - expect(answer.characters?.length).toEqual(1); + expect(answer.characterVariables).not.toBeFalsy(); + expect(answer.characterVariables?.length).toEqual(1); expect(answer.board).not.toBeFalsy(); - expect(answer.characters?.[0].color).toEqual('red'); - expect(answer.characters?.[0].direction).toEqual('down'); - expect(answer.characters?.[0].penDown).toEqual(true); + expect(answer.characterVariables?.[0].value.color).toEqual('red'); + expect(answer.characterVariables?.[0].value.direction).toEqual('down'); + expect(answer.characterVariables?.[0].value.penDown).toEqual(true); // prettier-ignore expect(answer.board.grid).toEqual([ @@ -119,15 +126,15 @@ test('Solve a problem (1character)', () => { [{ color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }], ]); - expect(answer.histories?.at(0)?.characters?.length).toEqual(0); + expect(answer.histories?.at(0)?.characterVariables?.length).toEqual(0); - expect(answer.histories?.at(1)?.characters?.length).toEqual(1); - expect(answer.histories?.at(1)?.characters?.[0]?.x).toEqual(1); - expect(answer.histories?.at(1)?.characters?.[0]?.y).toEqual(1); + expect(answer.histories?.at(1)?.characterVariables?.length).toEqual(1); + expect(answer.histories?.at(1)?.characterVariables?.[0]?.value.x).toEqual(1); + expect(answer.histories?.at(1)?.characterVariables?.[0]?.value.y).toEqual(1); - expect(answer.histories?.at(-1)?.characters?.length).toEqual(1); - expect(answer.histories?.at(-1)?.characters?.[0]?.x).toEqual(2); - expect(answer.histories?.at(-1)?.characters?.[0]?.y).toEqual(3); + expect(answer.histories?.at(-1)?.characterVariables?.length).toEqual(1); + expect(answer.histories?.at(-1)?.characterVariables?.[0]?.value.x).toEqual(2); + expect(answer.histories?.at(-1)?.characterVariables?.[0]?.value.y).toEqual(3); // prettier-ignore expect(answer.histories?.at(0)?.board?.grid).toEqual([ @@ -167,15 +174,15 @@ test('Solve a problem (multiple characters)', () => { const answer = solveProblem(problemProgram); expect(answer).not.toBeFalsy(); - expect(answer.characters).not.toBeFalsy(); - expect(answer.characters?.length).toEqual(3); + expect(answer.characterVariables).not.toBeFalsy(); + expect(answer.characterVariables?.length).toEqual(3); expect(answer.board).not.toBeFalsy(); - expect(answer.characters?.[0].color).toEqual('red'); - expect(answer.characters?.[0].direction).toEqual('down'); - expect(answer.characters?.[0].penDown).toEqual(true); - expect(answer.characters?.[1].color).toEqual('green'); - expect(answer.characters?.[1].direction).toEqual('down'); - expect(answer.characters?.[1].penDown).toEqual(true); + expect(answer.characterVariables?.[0].value.color).toEqual('red'); + expect(answer.characterVariables?.[0].value.direction).toEqual('down'); + expect(answer.characterVariables?.[0].value.penDown).toEqual(true); + expect(answer.characterVariables?.[1].value.color).toEqual('green'); + expect(answer.characterVariables?.[1].value.direction).toEqual('down'); + expect(answer.characterVariables?.[1].value.penDown).toEqual(true); // prettier-ignore expect(answer.board.grid).toEqual([ @@ -188,9 +195,9 @@ test('Solve a problem (multiple characters)', () => { [{ color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }], [{ color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }, { color: undefined }], ]); - expect(answer.histories?.at(-1)?.characters?.length).toEqual(3); - expect(answer.histories?.at(-1)?.characters?.[0]?.x).toEqual(1); - expect(answer.histories?.at(-1)?.characters?.[0]?.y).toEqual(3); - expect(answer.histories?.at(-1)?.characters?.[1]?.x).toEqual(2); - expect(answer.histories?.at(-1)?.characters?.[1]?.y).toEqual(3); + expect(answer.histories?.at(-1)?.characterVariables?.length).toEqual(3); + expect(answer.histories?.at(-1)?.characterVariables?.[0]?.value.x).toEqual(1); + expect(answer.histories?.at(-1)?.characterVariables?.[0]?.value.y).toEqual(3); + expect(answer.histories?.at(-1)?.characterVariables?.[1]?.value.x).toEqual(2); + expect(answer.histories?.at(-1)?.characterVariables?.[1]?.value.y).toEqual(3); });