diff --git a/demo/test.js b/demo/test.js index 62c757a13..7416245a9 100644 --- a/demo/test.js +++ b/demo/test.js @@ -17,7 +17,6 @@ function updateCharacter() { height: 400, renderer: 'svg', radicalColor: '#166E16', - highlightCompleteColor: '#FF0000', onCorrectStroke: printStrokePoints, onMistake: printStrokePoints, showCharacter: false, diff --git a/src/Quiz.ts b/src/Quiz.ts index cf7de4ff6..e6a1616ad 100644 --- a/src/Quiz.ts +++ b/src/Quiz.ts @@ -100,7 +100,7 @@ export default class Quiz { return; } - const { acceptBackwardsStrokes } = this._options!; + const { acceptBackwardsStrokes, markStrokeCorrectAfterMisses } = this._options!; const currentStroke = this._getCurrentStroke(); const { isMatch, meta } = strokeMatches( @@ -113,7 +113,13 @@ export default class Quiz { }, ); - const isAccepted = isMatch || (meta.isStrokeBackwards && acceptBackwardsStrokes); + // if markStrokeCorrectAfterMisses is passed, just force the stroke to count as correct after n tries + const isForceAccepted = + markStrokeCorrectAfterMisses && + this._mistakesOnStroke + 1 >= markStrokeCorrectAfterMisses; + + const isAccepted = + isMatch || isForceAccepted || (meta.isStrokeBackwards && acceptBackwardsStrokes); if (isAccepted) { this._handleSuccess(meta); diff --git a/src/__tests__/Quiz-test.ts b/src/__tests__/Quiz-test.ts index 6565b69f8..5b662e755 100644 --- a/src/__tests__/Quiz-test.ts +++ b/src/__tests__/Quiz-test.ts @@ -699,6 +699,93 @@ describe('Quiz', () => { expect(renderState.state.character.highlight.strokes[0].opacity).toBe(0); }); + it('marks the stroke as correct if the number of mistakes exceeds markStrokeCorrectAfterMisses', async () => { + (strokeMatches as any).mockImplementation(() => ({ + isMatch: false, + meta: { isStrokeBackwards: false }, + })); + + const renderState = createRenderState(); + const quiz = new Quiz( + char, + renderState, + new Positioner({ padding: 20, width: 200, height: 200 }), + ); + const onCorrectStroke = jest.fn(); + const onMistake = jest.fn(); + const onComplete = jest.fn(); + quiz.startQuiz( + Object.assign({}, opts, { + onCorrectStroke, + onComplete, + onMistake, + markStrokeCorrectAfterMisses: 2, + }), + ); + clock.tick(1000); + await resolvePromises(); + + quiz.startUserStroke({ x: 10, y: 20 }); + quiz.continueUserStroke({ x: 11, y: 21 }); + quiz.endUserStroke(); + await resolvePromises(); + clock.tick(1000); + await resolvePromises(); + + // should not draw the stroke yet + expect(quiz._currentStrokeIndex).toBe(0); + expect(renderState.state.character.main.strokes[0].opacity).toBe(0); + + quiz.startUserStroke({ x: 10, y: 20 }); + quiz.continueUserStroke({ x: 11, y: 21 }); + quiz.endUserStroke(); + await resolvePromises(); + + expect(quiz._userStroke).toBeUndefined(); + expect(quiz._isActive).toBe(true); + expect(quiz._currentStrokeIndex).toBe(1); + expect(onMistake).toHaveBeenCalledTimes(1); + expect(onMistake).toHaveBeenLastCalledWith({ + character: '人', + mistakesOnStroke: 1, + strokeNum: 0, + strokesRemaining: 2, + totalMistakes: 1, + drawnPath: { + pathString: 'M 10 20 L 11 21', + points: [ + { x: 15, y: 25 }, + { x: 16, y: 26 }, + ], + }, + isBackwards: false, + }); + expect(onCorrectStroke).toHaveBeenCalledTimes(1); + expect(onCorrectStroke).toHaveBeenLastCalledWith({ + character: '人', + mistakesOnStroke: 1, + strokeNum: 0, + strokesRemaining: 1, + totalMistakes: 1, + drawnPath: { + pathString: 'M 10 20 L 11 21', + points: [ + { x: 15, y: 25 }, + { x: 16, y: 26 }, + ], + }, + isBackwards: false, + }); + + clock.tick(1000); + await resolvePromises(); + + // should draw the stroke now + clock.tick(1000); + await resolvePromises(); + expect(renderState.state.character.main.strokes[0].opacity).toBe(1); + }); + it('does not highlight strokes if showHintAfterMisses is set to false', async () => { (strokeMatches as any).mockImplementation(() => ({ isMatch: false, diff --git a/src/defaultOptions.ts b/src/defaultOptions.ts index b7682ff72..9994b5c63 100644 --- a/src/defaultOptions.ts +++ b/src/defaultOptions.ts @@ -38,6 +38,7 @@ const defaultOptions: HanziWriterOptions = { showHintAfterMisses: 3, highlightOnComplete: true, highlightCompleteColor: null, + markStrokeCorrectAfterMisses: false, acceptBackwardsStrokes: false, quizStartStrokeNum: 0, diff --git a/src/typings/types.ts b/src/typings/types.ts index e6f4e8b77..f3ac29677 100644 --- a/src/typings/types.ts +++ b/src/typings/types.ts @@ -70,6 +70,8 @@ export type QuizOptions = { acceptBackwardsStrokes: boolean; /** Begin quiz on this stroke number rather than stroke 0 */ quizStartStrokeNum: number; + /** After a user makes this many mistakes, just mark the stroke correct and move on. Default: false */ + markStrokeCorrectAfterMisses: number | false; onMistake?: (strokeData: StrokeData) => void; onCorrectStroke?: (strokeData: StrokeData) => void; /** Callback when the quiz completes */