Skip to content

Commit

Permalink
feat: 一発解答モードに3回挑戦できるようにする (#156)
Browse files Browse the repository at this point in the history
  • Loading branch information
exKAZUu authored Sep 26, 2024
1 parent efb4818 commit ef857c2
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ const LecturePage: NextPage<Props> = async (props) => {
select: {
problemId: true,
completedAt: true,
elapsedMilliseconds: true,
submissions: { select: { elapsedMilliseconds: true, isCorrect: true } },
submissions: { select: { isCorrect: true } },
},
where: { userId: session.superTokensUserId, courseId: props.params.courseId, lectureId: props.params.lectureId },
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ import {
type Props = {
params: { courseId: CourseId; lectureId: string };
lectureIndex: number;
problemSessions: (Pick<ProblemSession, 'problemId' | 'completedAt' | 'elapsedMilliseconds'> & {
submissions: Pick<ProblemSubmission, 'elapsedMilliseconds' | 'isCorrect'>[];
problemSessions: (Pick<ProblemSession, 'problemId' | 'completedAt'> & {
submissions: Pick<ProblemSubmission, 'isCorrect'>[];
})[];
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,12 +236,13 @@ export const BoardEditor = forwardRef<TurtleGraphicsHandle, TurtleGraphicsProps>
onClick={() => handleTurnTurtle(false)}
/>
</Grid>
<HStack justify="flex-end">
<HStack justify="space-between" width="100%">
<Button
colorScheme="brand"
leftIcon={<Icon as={MdOutlineDelete} />}
size="sm"
variant="outline"
width="100%"
onClick={() => handleRemoveCharacterButton()}
>
タートルを削除
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { ProblemSession } from '@prisma/client';
import { useRouter } from 'next/navigation';
import { useEffect, useRef, useState } from 'react';

import { MAX_CHALLENGE_COUNT } from '../../../../../../../../constants';
import { backendTrpcReact } from '../../../../../../../../infrastructures/trpcBackend/client';
import {
AlertDialog,
AlertDialogBody,
Expand All @@ -22,7 +24,6 @@ import {
import type { Problem } from '../../../../../../../../problems/generateProblem';
import type { CourseId, ProblemId } from '../../../../../../../../problems/problemData';
import { isTurtleTrace } from '../../../../../../../../problems/traceProgram';
import { useIsMacOS } from '../../../../../../../../utils/platform';

import type { TurtleGraphicsHandle } from './BoardEditor';
import { BoardEditor } from './BoardEditor';
Expand Down Expand Up @@ -62,23 +63,41 @@ export const ProblemBody: React.FC<Props> = (props) => {
onAlertOpen();
};

const { refetch: fetchIncorrectSubmissionsCount } = backendTrpcReact.countIncorrectSubmissions.useQuery(
{ sessionId: props.problemSession.id },
{ enabled: false }
);

const handleClickSubmitButton = async (): Promise<void> => {
const isCorrect = turtleGraphicsRef.current?.isCorrect() || false;

switch (problemType) {
case 'executionResult': {
void props.createSubmissionUpdatingProblemSession(isCorrect, isCorrect);
if (isCorrect) {
void props.createSubmissionUpdatingProblemSession(true, true);
openAlertDialog(
'正解',
'一発正解です!この問題は完了です。問題一覧ページに戻って、次の問題に挑戦してください。',
'一発正解です!この問題は完了です。問題一覧ページに戻りますので、次の問題に挑戦してください。',
() => {
router.push(`/courses/${props.params.courseId}/lectures/${props.params.lectureId}`);
}
);
} else {
void props.updateProblemSession('step', 1);
openAlertDialog('不正解', '不正解です。ステップごとに問題を解いてみましょう。');
const response = await fetchIncorrectSubmissionsCount();
const incorrectCount = (response.data ?? 0) + 1;
void props.createSubmissionUpdatingProblemSession(false, false);
if (incorrectCount === MAX_CHALLENGE_COUNT) {
void props.updateProblemSession('step', 1);
openAlertDialog(
'不正解',
`不正解です。${MAX_CHALLENGE_COUNT}回間違えたので、ステップ実行モードに移ります。ステップごとに問題を解いてください。`
);
} else {
openAlertDialog(
'不正解',
`不正解です。あと${MAX_CHALLENGE_COUNT - incorrectCount}回間違えたら、ステップ実行モードに移ります。一発正解を目指しましょう!`
);
}
}
break;
}
Expand All @@ -91,7 +110,7 @@ export const ProblemBody: React.FC<Props> = (props) => {
if (currentTraceItemIndex === props.problem.traceItems.length - 1) {
openAlertDialog(
'正解',
'正解です。この問題は完了です。問題一覧ページに戻って、次の問題に挑戦してください。',
'正解です。この問題は完了です。問題一覧ページに戻りますので、次の問題に挑戦してください。',
() => {
router.push(`/courses/${props.params.courseId}/lectures/${props.params.lectureId}`);
}
Expand All @@ -108,24 +127,36 @@ export const ProblemBody: React.FC<Props> = (props) => {
}
};

const isMacOS = useIsMacOS();
useShortcutKeys(handleClickSubmitButton);

return (
<>
<VStack align="stretch" flexBasis={0} flexGrow={1} minW={0} spacing={4}>
<VStack align="stretch" as={Card} overflow="hidden" spacing={0}>
<VStack align="stretch" borderBottomWidth="1px" p={5}>
<HStack>
<HStack justifyContent="space-between">
<Heading size="md">問題</Heading>
{problemType === 'step' && (
<Tag colorScheme="brand" fontWeight="bold" size="sm" variant="solid">
ステップ実行モード
</Tag>
)}
{problemType === 'executionResult' && (
<Tooltip label="減点になりますが、確実に問題を解けます。">
<Button
colorScheme="brand"
variant="outline"
onClick={() => {
void props.updateProblemSession('step', 1);
}}
>
諦めてステップ実行モードに移る
</Button>
</Tooltip>
)}
</HStack>

<div>
<Box>
<Box as="span" fontWeight="bold">
{problemType === 'executionResult' ? (
'プログラムを実行した後'
Expand All @@ -139,7 +170,7 @@ export const ProblemBody: React.FC<Props> = (props) => {
)}
</Box>
の盤面を作成し、提出ボタンを押してください。
</div>
</Box>
</VStack>

<SyntaxHighlighter
Expand Down Expand Up @@ -205,7 +236,7 @@ export const ProblemBody: React.FC<Props> = (props) => {
colorScheme="brand"
rightIcon={
<Box as="span" color="whiteAlpha.800" fontSize="sm" fontWeight="bold">
{isMacOS ? 'Cmd + Enter' : 'Ctrl + Enter'}
(Enter)
</Box>
}
onClick={() => handleClickSubmitButton()}
Expand Down Expand Up @@ -254,7 +285,7 @@ export const ProblemBody: React.FC<Props> = (props) => {
function useShortcutKeys(handleClickAnswerButton: () => Promise<void>): void {
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent): void => {
if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
if (event.key === 'Enter') {
event.preventDefault();
void handleClickAnswerButton();
}
Expand Down
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const SUPERTOKENS_ACCESS_TOKEN_COOKIE_NAME = 'sAccessToken';
export const SUPERTOKENS_REFRESH_TOKEN_COOKIE_NAME = 'sRefreshToken';
export const SERVICE_ADMIN_ROLE = 'admin';

export const MAX_CHALLENGE_COUNT = 3;

/**
* 最後のイベントから今回のイベント発生時の期間において、アクティブと解釈される最大時間。
*/
Expand Down
16 changes: 16 additions & 0 deletions src/infrastructures/trpcBackend/routers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@ export const backendRouter = router({

await prisma.problemSubmission.create({ data: input });
}),

countIncorrectSubmissions: procedure
.use(authorize)
.input(
z.object({
sessionId: z.number().int().positive(),
})
)
.query(async ({ input }) => {
return await prisma.problemSubmission.count({
where: {
sessionId: input.sessionId,
isCorrect: false,
},
});
}),
});

// export type definition of API
Expand Down

0 comments on commit ef857c2

Please sign in to comment.