From 90694b360d5604c847c7ae7c7f0b23a83424b51f Mon Sep 17 00:00:00 2001 From: reginateh Date: Wed, 20 Mar 2024 21:18:18 +0800 Subject: [PATCH 01/61] add new action show_quiz --- src/features/game/action/GameActionExecuter.ts | 4 ++++ src/features/game/action/GameActionTypes.ts | 3 ++- src/features/game/parser/ActionParser.ts | 5 +++++ src/features/game/parser/ParserConverter.ts | 3 ++- .../game/scenes/gameManager/GameGlobalAPI.ts | 13 +++++++++++++ 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/features/game/action/GameActionExecuter.ts b/src/features/game/action/GameActionExecuter.ts index 717b5645ad..9194b6f7bb 100644 --- a/src/features/game/action/GameActionExecuter.ts +++ b/src/features/game/action/GameActionExecuter.ts @@ -102,6 +102,9 @@ export default class GameActionExecuter { case GameActionType.Delay: await sleep(actionParams.duration); return; + case GameActionType.ShowQuiz: + globalAPI.showQuiz(actionParams.id); + return; default: return; } @@ -141,6 +144,7 @@ export default class GameActionExecuter { case GameActionType.PlaySFX: case GameActionType.ShowObjectLayer: case GameActionType.Delay: + case GameActionType.ShowQuiz: return false; } } diff --git a/src/features/game/action/GameActionTypes.ts b/src/features/game/action/GameActionTypes.ts index fcc2d9d0f3..7b282c3559 100644 --- a/src/features/game/action/GameActionTypes.ts +++ b/src/features/game/action/GameActionTypes.ts @@ -25,7 +25,8 @@ export enum GameActionType { ShowObjectLayer = 'ShowObjectLayer', NavigateToAssessment = 'NavigateToAssessment', UpdateAssessmentStatus = 'UpdateAssessmentStatus', - Delay = 'Delay' + Delay = 'Delay', + ShowQuiz = 'ShowQuiz' } /** diff --git a/src/features/game/parser/ActionParser.ts b/src/features/game/parser/ActionParser.ts index 9cc216e21b..e233ba02f3 100644 --- a/src/features/game/parser/ActionParser.ts +++ b/src/features/game/parser/ActionParser.ts @@ -185,6 +185,11 @@ export default class ActionParser { case GameActionType.Delay: actionParamObj.duration = parseInt(actionParams[0]) * 1000; break; + + case GameActionType.ShowQuiz: + actionParamObj.id = actionParams[0]; + Parser.validator.assertItemType(GameItemType.quizzes, actionParams[0], actionType); + break; } const actionId = Parser.generateActionId(); diff --git a/src/features/game/parser/ParserConverter.ts b/src/features/game/parser/ParserConverter.ts index 56b7dcb3bb..fbb491d9ec 100644 --- a/src/features/game/parser/ParserConverter.ts +++ b/src/features/game/parser/ParserConverter.ts @@ -59,7 +59,8 @@ const stringToActionTypeMap = { show_object_layer: GameActionType.ShowObjectLayer, navigate_to_assessment: GameActionType.NavigateToAssessment, update_assessment_status: GameActionType.UpdateAssessmentStatus, - delay: GameActionType.Delay + delay: GameActionType.Delay, + show_quiz: GameActionType.ShowQuiz }; const stringToGameStateStorageMap = { diff --git a/src/features/game/scenes/gameManager/GameGlobalAPI.ts b/src/features/game/scenes/gameManager/GameGlobalAPI.ts index 0be52dd764..cfbb712107 100644 --- a/src/features/game/scenes/gameManager/GameGlobalAPI.ts +++ b/src/features/game/scenes/gameManager/GameGlobalAPI.ts @@ -20,6 +20,7 @@ import { StateObserver, UserStateType } from '../../state/GameStateTypes'; import { TaskDetail } from '../../task/GameTaskTypes'; import { courseId, mandatory } from '../../utils/GameUtils'; import GameManager from './GameManager'; +import { Quiz } from '../../quiz/GameQuizType'; /** * This class exposes all the public API's of managers @@ -501,9 +502,21 @@ class GameGlobalAPI { public getBBoxById(bboxId: ItemId): BBoxProperty { return mandatory(this.getGameMap().getBBoxPropMap().get(bboxId)); } + + public getQuizById(quizId: ItemId): Quiz { + return mandatory(this.getGameMap().getQuizMap().get(quizId)); + } + public getAssetByKey(assetKey: AssetKey) { return this.getGameMap().getAssetByKey(assetKey); } + + ///////////////////// + // Game Quiz // + ///////////////////// + public showQuiz(quizId: ItemId) { + this.getGameManager().getQuizManager().showQuiz(quizId); + } } export default GameGlobalAPI; From 2ecf4396590f071d7e5785686e556d26abcd3326 Mon Sep 17 00:00:00 2001 From: reginateh Date: Wed, 20 Mar 2024 21:20:23 +0800 Subject: [PATCH 02/61] add quiz parser & quiz manager --- src/features/game/location/GameMap.ts | 7 + src/features/game/location/GameMapTypes.ts | 3 +- src/features/game/parser/DialogueParser.ts | 12 +- src/features/game/parser/Parser.ts | 4 + src/features/game/parser/QuizParser.ts | 140 ++++++++++++++++++ src/features/game/quiz/GameQuizConstants.ts | 4 + src/features/game/quiz/GameQuizManager.ts | 30 ++++ src/features/game/quiz/GameQuizType.ts | 16 ++ .../game/scenes/gameManager/GameManager.ts | 4 + 9 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 src/features/game/parser/QuizParser.ts create mode 100644 src/features/game/quiz/GameQuizConstants.ts create mode 100644 src/features/game/quiz/GameQuizManager.ts create mode 100644 src/features/game/quiz/GameQuizType.ts diff --git a/src/features/game/location/GameMap.ts b/src/features/game/location/GameMap.ts index d009abce86..e58ffcc91c 100644 --- a/src/features/game/location/GameMap.ts +++ b/src/features/game/location/GameMap.ts @@ -8,6 +8,7 @@ import { GameMode } from '../mode/GameModeTypes'; import { ObjectProperty } from '../objects/GameObjectTypes'; import { mandatory } from '../utils/GameUtils'; import { AnyId, GameItemType, GameLocation, LocationId } from './GameMapTypes'; +import { Quiz } from '../quiz/GameQuizType'; /** * Game map is the class that encapsulates data about @@ -36,6 +37,7 @@ class GameMap { private actions: Map; private gameStartActions: ItemId[]; private checkpointCompleteActions: ItemId[]; + private quizzes: Map; constructor() { this.soundAssets = []; @@ -47,6 +49,7 @@ class GameMap { this.boundingBoxes = new Map(); this.characters = new Map(); this.actions = new Map(); + this.quizzes = new Map(); this.gameStartActions = []; this.checkpointCompleteActions = []; @@ -120,6 +123,10 @@ class GameMap { return this.actions; } + public getQuizMap(): Map { + return this.quizzes; + } + public getSoundAssets(): SoundAsset[] { return this.soundAssets; } diff --git a/src/features/game/location/GameMapTypes.ts b/src/features/game/location/GameMapTypes.ts index 21a36fd1b6..70be24f38d 100644 --- a/src/features/game/location/GameMapTypes.ts +++ b/src/features/game/location/GameMapTypes.ts @@ -48,5 +48,6 @@ export enum GameItemType { characters = 'characters', actions = 'actions', bgmKey = 'bgmKey', - collectibles = 'collectibles' + collectibles = 'collectibles', + quizzes = 'quizzes' } diff --git a/src/features/game/parser/DialogueParser.ts b/src/features/game/parser/DialogueParser.ts index 14d79ac4d4..e0436aec0a 100644 --- a/src/features/game/parser/DialogueParser.ts +++ b/src/features/game/parser/DialogueParser.ts @@ -1,4 +1,4 @@ -import { Dialogue, DialogueLine, PartName } from '../dialogue/GameDialogueTypes'; +import { Dialogue, DialogueLine, DialogueObject, PartName } from '../dialogue/GameDialogueTypes'; import { GameItemType } from '../location/GameMapTypes'; import { mapValues } from '../utils/GameUtils'; import StringUtils from '../utils/StringUtils'; @@ -143,6 +143,16 @@ export default class DialogueParser { } return dialogueLines; } + /** + * This function parses a diaglogue written in a quiz as reaction + * and returns a DialogueObject. + * Itis only called by the QuizParser. + * + * @param {Array} dialogueBody the lines inside a dialogue + */ + public static parseQuizReaction(dialogueBody: string[]): DialogueObject { + return this.parseDialogueContent(dialogueBody); + } } const isInteger = (line: string) => new RegExp(/^[0-9]+$/).test(line); diff --git a/src/features/game/parser/Parser.ts b/src/features/game/parser/Parser.ts index 0ee1843a4b..14bf39f53a 100644 --- a/src/features/game/parser/Parser.ts +++ b/src/features/game/parser/Parser.ts @@ -9,6 +9,7 @@ import LocationsParser from './LocationDetailsParser'; import LocationParser from './LocationParser'; import ParserValidator, { GameEntityType } from './ParserValidator'; import TasksParser from './TasksParser'; +import QuizParser from './QuizParser'; /** * This class converts a checkpoint txt file into a Checkpoint @@ -94,6 +95,9 @@ class Parser { case 'dialogues': DialoguesParser.parse(body); break; + case 'quizzes': + QuizParser.parse(body); + break; default: return false; } diff --git a/src/features/game/parser/QuizParser.ts b/src/features/game/parser/QuizParser.ts new file mode 100644 index 0000000000..cedd8017b0 --- /dev/null +++ b/src/features/game/parser/QuizParser.ts @@ -0,0 +1,140 @@ +import Parser from "./Parser"; +import StringUtils from "../utils/StringUtils"; +import { Quiz, Question, Option } from "../quiz/GameQuizType"; +import DialogueParser from "./DialogueParser"; +import { DialogueObject } from "../dialogue/GameDialogueTypes"; +import { GameItemType } from '../location/GameMapTypes'; +import { defaultReaction } from "../quiz/GameQuizConstants"; + +/** + * This class parses quizzes and creates Quiz Objects + * which can be read by the Quiz Manager. + */ +export default class QuizParser { + /** + * This function reads the entire text under the "quizzes" heading, + * converts quizzes listed underneath into Quiz entities, + * and stores these quizzes in the game map. + * + * @param quizText the entire quiz text beneath quizzes heading + */ + public static parse(quizText: string[]) { + const quizParagraphs = StringUtils.splitToParagraph(quizText); + quizParagraphs.forEach(([quizId, quizBody]: [string, string[]]) => { + if (quizBody.length === 0) { + console.error('No quiz content found for quizId'); + return; + } + this.parseQuiz(quizId, quizBody); + }) + } + + /** + * This function parses one quiz and stores it into the game map. + * + * @param quizId the string containing quiz Id + * @param quizBody the body of the dialogue containing its questions and options + */ + private static parseQuiz(quizId: string, quizBody: string[]) { + Parser.validator.registerId(quizId); + const rawQuestions: Map = StringUtils.mapByHeader(quizBody, isInteger); + const questions: Question[] = this.parseQuizQuestions(rawQuestions); + const quiz: Quiz = {questions: questions}; + Parser.checkpoint.map.setItemInMap(GameItemType.quizzes, quizId, quiz); + } + + /** + * This function parses the quiz's questions, + * converts each into a Question object, + * and store in an array. + * + * @param map The map of raw questions that map from index to question body + */ + private static parseQuizQuestions(map: Map): Question[] { + const questions: Question[] = new Array(map.size); + map.forEach((value: string[], key: string) => { + const question: Question = this.createQuestion(value); + questions[parseInt(key)] = question; + }); + return questions; + } + + /** + * This function parses a question and + * create a Question object. + * + * @param questionText The text of a question body + * containing question text, correct answer, and options + */ + private static createQuestion(questionText: string[]): Question { + const ans = this.getQuizAnswer(questionText[1]); + const question: Question = { + question: questionText[0], + answer: ans, + options: this.parseOptions(questionText.slice(2), ans) + }; + return question; + } + + /** + * This function parses a answer string and + * converts it into Number. + * + * @param answer The string containing the correct answer of a question + */ + private static getQuizAnswer(answer: string): Number { + return parseInt(answer.split(':')[1]); + } + + /** + * This function parses options of a question and + * store them in an array. + * + * @param optionsText An Array of string containing all options' content, + * including option text and reactions + * @param answer The correct answer of the corresponding question + */ + private static parseOptions(optionsText: string[], answer: Number): Option[] { + const optionsParagraph = StringUtils.splitToParagraph(optionsText); + const options: Option[] = Array(optionsParagraph.length); + optionsParagraph.forEach(([header, content]: [string, string[]], index) => { + options[index] = this.createOption(content, (answer === index)); + }); + return options; + } + + /** + * This function creates an Option object. + * + * @param content An Array of string containing an option's content, + * including option text and reaction + * @param isCorrect Indicates whether this option is the correct answer + * @param [defaultReaction=false] Indicates whether this option uses the default reaction + */ + private static createOption(content: string[], isCorrect: boolean, defaultReaction: boolean = false): Option { + if (content.length <= 1) { + defaultReaction = true; + } + const option: Option = { + text: content[0], + reaction: defaultReaction ? this.createDefaultReaction(isCorrect) : DialogueParser.parseQuizReaction(content.slice(1)) + } + return option; + } + + /** + * This function creates a Dialogue object with + * the default reactions defined in GamaQuizConstants + * + * @param isCorrect Indicates whether the correct or wrong reaction should be used + */ + private static createDefaultReaction(isCorrect: boolean): DialogueObject { + if (isCorrect) { + return DialogueParser.parseQuizReaction(defaultReaction.correct); + } else { + return DialogueParser.parseQuizReaction(defaultReaction.wrong); + } + } +} + +const isInteger = (line: string) => new RegExp(/^[0-9]+$/).test(line); \ No newline at end of file diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts new file mode 100644 index 0000000000..453110dd1f --- /dev/null +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -0,0 +1,4 @@ +export const defaultReaction = { + correct: ["You are right."], + wrong: ["Wrong answer..."] +} \ No newline at end of file diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts new file mode 100644 index 0000000000..9316360d45 --- /dev/null +++ b/src/features/game/quiz/GameQuizManager.ts @@ -0,0 +1,30 @@ +import GameGlobalAPI from "../scenes/gameManager/GameGlobalAPI"; +import { ItemId } from "../commons/CommonTypes"; +import { Question, Option } from "./GameQuizType"; +import { DialogueLine } from "../dialogue/GameDialogueTypes"; + +export default class QuizManager { + + // Print everything. To test if the quiz parser parses correctly. + public showQuiz(quizId: ItemId) { + console.log("A quiz is shown"); + const quiz = GameGlobalAPI.getInstance().getQuizById(quizId); // get a quiz + const questions = quiz.questions; + questions.forEach((value: Question) => {this.showQuestion(value)}); + } + + private showQuestion(question: Question) { + console.log(question.question); + console.log(question.answer); + question.options.forEach((option) => {this.showOption(option)}); + } + + private showOption(option: Option) { + console.log(option.text); + option.reaction.forEach((value: DialogueLine[]) => this.showReaction(value)); + } + + private showReaction(reaction: DialogueLine[]) { + reaction.forEach((line: DialogueLine) => console.log(line.line)); + } +} \ No newline at end of file diff --git a/src/features/game/quiz/GameQuizType.ts b/src/features/game/quiz/GameQuizType.ts new file mode 100644 index 0000000000..eab11fc34b --- /dev/null +++ b/src/features/game/quiz/GameQuizType.ts @@ -0,0 +1,16 @@ +import { DialogueObject } from "../dialogue/GameDialogueTypes"; + +export type Quiz = { + questions: Question[] +} + +export type Question = { + question: string, + answer: Number, + options: Option[] +} + +export type Option = { + text: string, + reaction: DialogueObject +} \ No newline at end of file diff --git a/src/features/game/scenes/gameManager/GameManager.ts b/src/features/game/scenes/gameManager/GameManager.ts index 125e077fd1..b88fffdf38 100644 --- a/src/features/game/scenes/gameManager/GameManager.ts +++ b/src/features/game/scenes/gameManager/GameManager.ts @@ -34,6 +34,7 @@ import GameToolbarManager from '../../toolbar/GameToolbarManager'; import { mandatory, sleep, toS3Path } from '../../utils/GameUtils'; import GameGlobalAPI from './GameGlobalAPI'; import { createGamePhases } from './GameManagerHelper'; +import GameQuizManager from '../../quiz/GameQuizManager'; type GameManagerProps = { continueGame: boolean; @@ -75,6 +76,7 @@ class GameManager extends Phaser.Scene { private toolbarManager?: GameToolbarManager; private taskLogManager?: GameTaskLogManager; private dashboardManager?: GameDashboardManager; + private quizManager?: GameQuizManager; constructor() { super('GameManager'); @@ -125,6 +127,7 @@ class GameManager extends Phaser.Scene { ], [this.logManager, this.taskLogManager, this.collectibleManager, this.achievementManager] ); + this.quizManager = new GameQuizManager(); } ////////////////////// @@ -432,6 +435,7 @@ class GameManager extends Phaser.Scene { public getToolbarManager = () => mandatory(this.toolbarManager); public getTaskLogManager = () => mandatory(this.taskLogManager); public getDashboardManager = () => mandatory(this.dashboardManager); + public getQuizManager = () => mandatory(this.quizManager); } export default GameManager; From 9758022c8399cda3c3552118c72eb21006541d62 Mon Sep 17 00:00:00 2001 From: reginateh Date: Wed, 20 Mar 2024 21:30:35 +0800 Subject: [PATCH 03/61] reformat --- src/features/game/location/GameMap.ts | 2 +- src/features/game/parser/Parser.ts | 2 +- src/features/game/parser/QuizParser.ts | 40 +++++++++++-------- src/features/game/quiz/GameQuizConstants.ts | 6 +-- src/features/game/quiz/GameQuizManager.ts | 21 +++++----- src/features/game/quiz/GameQuizType.ts | 20 +++++----- .../game/scenes/gameManager/GameGlobalAPI.ts | 2 +- .../game/scenes/gameManager/GameManager.ts | 2 +- 8 files changed, 52 insertions(+), 43 deletions(-) diff --git a/src/features/game/location/GameMap.ts b/src/features/game/location/GameMap.ts index e58ffcc91c..f7a02ff807 100644 --- a/src/features/game/location/GameMap.ts +++ b/src/features/game/location/GameMap.ts @@ -6,9 +6,9 @@ import { AssetKey, ItemId } from '../commons/CommonTypes'; import { Dialogue } from '../dialogue/GameDialogueTypes'; import { GameMode } from '../mode/GameModeTypes'; import { ObjectProperty } from '../objects/GameObjectTypes'; +import { Quiz } from '../quiz/GameQuizType'; import { mandatory } from '../utils/GameUtils'; import { AnyId, GameItemType, GameLocation, LocationId } from './GameMapTypes'; -import { Quiz } from '../quiz/GameQuizType'; /** * Game map is the class that encapsulates data about diff --git a/src/features/game/parser/Parser.ts b/src/features/game/parser/Parser.ts index 14bf39f53a..4def0edccd 100644 --- a/src/features/game/parser/Parser.ts +++ b/src/features/game/parser/Parser.ts @@ -8,8 +8,8 @@ import DialoguesParser from './DialogueParser'; import LocationsParser from './LocationDetailsParser'; import LocationParser from './LocationParser'; import ParserValidator, { GameEntityType } from './ParserValidator'; -import TasksParser from './TasksParser'; import QuizParser from './QuizParser'; +import TasksParser from './TasksParser'; /** * This class converts a checkpoint txt file into a Checkpoint diff --git a/src/features/game/parser/QuizParser.ts b/src/features/game/parser/QuizParser.ts index cedd8017b0..0a0f90ae8f 100644 --- a/src/features/game/parser/QuizParser.ts +++ b/src/features/game/parser/QuizParser.ts @@ -1,10 +1,10 @@ -import Parser from "./Parser"; -import StringUtils from "../utils/StringUtils"; -import { Quiz, Question, Option } from "../quiz/GameQuizType"; -import DialogueParser from "./DialogueParser"; -import { DialogueObject } from "../dialogue/GameDialogueTypes"; +import { DialogueObject } from '../dialogue/GameDialogueTypes'; import { GameItemType } from '../location/GameMapTypes'; -import { defaultReaction } from "../quiz/GameQuizConstants"; +import { defaultReaction } from '../quiz/GameQuizConstants'; +import { Option, Question, Quiz } from '../quiz/GameQuizType'; +import StringUtils from '../utils/StringUtils'; +import DialogueParser from './DialogueParser'; +import Parser from './Parser'; /** * This class parses quizzes and creates Quiz Objects @@ -15,7 +15,7 @@ export default class QuizParser { * This function reads the entire text under the "quizzes" heading, * converts quizzes listed underneath into Quiz entities, * and stores these quizzes in the game map. - * + * * @param quizText the entire quiz text beneath quizzes heading */ public static parse(quizText: string[]) { @@ -26,12 +26,12 @@ export default class QuizParser { return; } this.parseQuiz(quizId, quizBody); - }) + }); } /** * This function parses one quiz and stores it into the game map. - * + * * @param quizId the string containing quiz Id * @param quizBody the body of the dialogue containing its questions and options */ @@ -39,7 +39,7 @@ export default class QuizParser { Parser.validator.registerId(quizId); const rawQuestions: Map = StringUtils.mapByHeader(quizBody, isInteger); const questions: Question[] = this.parseQuizQuestions(rawQuestions); - const quiz: Quiz = {questions: questions}; + const quiz: Quiz = { questions: questions }; Parser.checkpoint.map.setItemInMap(GameItemType.quizzes, quizId, quiz); } @@ -90,7 +90,7 @@ export default class QuizParser { * This function parses options of a question and * store them in an array. * - * @param optionsText An Array of string containing all options' content, + * @param optionsText An Array of string containing all options' content, * including option text and reactions * @param answer The correct answer of the corresponding question */ @@ -98,7 +98,7 @@ export default class QuizParser { const optionsParagraph = StringUtils.splitToParagraph(optionsText); const options: Option[] = Array(optionsParagraph.length); optionsParagraph.forEach(([header, content]: [string, string[]], index) => { - options[index] = this.createOption(content, (answer === index)); + options[index] = this.createOption(content, answer === index); }); return options; } @@ -106,19 +106,25 @@ export default class QuizParser { /** * This function creates an Option object. * - * @param content An Array of string containing an option's content, + * @param content An Array of string containing an option's content, * including option text and reaction * @param isCorrect Indicates whether this option is the correct answer * @param [defaultReaction=false] Indicates whether this option uses the default reaction */ - private static createOption(content: string[], isCorrect: boolean, defaultReaction: boolean = false): Option { + private static createOption( + content: string[], + isCorrect: boolean, + defaultReaction: boolean = false + ): Option { if (content.length <= 1) { defaultReaction = true; } const option: Option = { text: content[0], - reaction: defaultReaction ? this.createDefaultReaction(isCorrect) : DialogueParser.parseQuizReaction(content.slice(1)) - } + reaction: defaultReaction + ? this.createDefaultReaction(isCorrect) + : DialogueParser.parseQuizReaction(content.slice(1)) + }; return option; } @@ -137,4 +143,4 @@ export default class QuizParser { } } -const isInteger = (line: string) => new RegExp(/^[0-9]+$/).test(line); \ No newline at end of file +const isInteger = (line: string) => new RegExp(/^[0-9]+$/).test(line); diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index 453110dd1f..79ba47a577 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -1,4 +1,4 @@ export const defaultReaction = { - correct: ["You are right."], - wrong: ["Wrong answer..."] -} \ No newline at end of file + correct: ['You are right.'], + wrong: ['Wrong answer...'] +}; diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 9316360d45..4c58544e57 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -1,22 +1,25 @@ -import GameGlobalAPI from "../scenes/gameManager/GameGlobalAPI"; -import { ItemId } from "../commons/CommonTypes"; -import { Question, Option } from "./GameQuizType"; -import { DialogueLine } from "../dialogue/GameDialogueTypes"; +import { ItemId } from '../commons/CommonTypes'; +import { DialogueLine } from '../dialogue/GameDialogueTypes'; +import GameGlobalAPI from '../scenes/gameManager/GameGlobalAPI'; +import { Option, Question } from './GameQuizType'; export default class QuizManager { - // Print everything. To test if the quiz parser parses correctly. public showQuiz(quizId: ItemId) { - console.log("A quiz is shown"); + console.log('A quiz is shown'); const quiz = GameGlobalAPI.getInstance().getQuizById(quizId); // get a quiz const questions = quiz.questions; - questions.forEach((value: Question) => {this.showQuestion(value)}); + questions.forEach((value: Question) => { + this.showQuestion(value); + }); } private showQuestion(question: Question) { console.log(question.question); console.log(question.answer); - question.options.forEach((option) => {this.showOption(option)}); + question.options.forEach(option => { + this.showOption(option); + }); } private showOption(option: Option) { @@ -27,4 +30,4 @@ export default class QuizManager { private showReaction(reaction: DialogueLine[]) { reaction.forEach((line: DialogueLine) => console.log(line.line)); } -} \ No newline at end of file +} diff --git a/src/features/game/quiz/GameQuizType.ts b/src/features/game/quiz/GameQuizType.ts index eab11fc34b..01f1594041 100644 --- a/src/features/game/quiz/GameQuizType.ts +++ b/src/features/game/quiz/GameQuizType.ts @@ -1,16 +1,16 @@ -import { DialogueObject } from "../dialogue/GameDialogueTypes"; +import { DialogueObject } from '../dialogue/GameDialogueTypes'; export type Quiz = { - questions: Question[] -} + questions: Question[]; +}; export type Question = { - question: string, - answer: Number, - options: Option[] -} + question: string; + answer: Number; + options: Option[]; +}; export type Option = { - text: string, - reaction: DialogueObject -} \ No newline at end of file + text: string; + reaction: DialogueObject; +}; diff --git a/src/features/game/scenes/gameManager/GameGlobalAPI.ts b/src/features/game/scenes/gameManager/GameGlobalAPI.ts index cfbb712107..ffd8fd1e4e 100644 --- a/src/features/game/scenes/gameManager/GameGlobalAPI.ts +++ b/src/features/game/scenes/gameManager/GameGlobalAPI.ts @@ -14,13 +14,13 @@ import { AnyId, GameItemType, GameLocation, LocationId } from '../../location/Ga import { GameMode } from '../../mode/GameModeTypes'; import { ObjectProperty } from '../../objects/GameObjectTypes'; import { GamePhaseType } from '../../phase/GamePhaseTypes'; +import { Quiz } from '../../quiz/GameQuizType'; import { SettingsJson } from '../../save/GameSaveTypes'; import SourceAcademyGame from '../../SourceAcademyGame'; import { StateObserver, UserStateType } from '../../state/GameStateTypes'; import { TaskDetail } from '../../task/GameTaskTypes'; import { courseId, mandatory } from '../../utils/GameUtils'; import GameManager from './GameManager'; -import { Quiz } from '../../quiz/GameQuizType'; /** * This class exposes all the public API's of managers diff --git a/src/features/game/scenes/gameManager/GameManager.ts b/src/features/game/scenes/gameManager/GameManager.ts index b88fffdf38..d06f7151db 100644 --- a/src/features/game/scenes/gameManager/GameManager.ts +++ b/src/features/game/scenes/gameManager/GameManager.ts @@ -27,6 +27,7 @@ import GameObjectManager from '../../objects/GameObjectManager'; import GamePhaseManager from '../../phase/GamePhaseManager'; import { GamePhaseType } from '../../phase/GamePhaseTypes'; import GamePopUpManager from '../../popUp/GamePopUpManager'; +import GameQuizManager from '../../quiz/GameQuizManager'; import SourceAcademyGame from '../../SourceAcademyGame'; import GameStateManager from '../../state/GameStateManager'; import GameTaskLogManager from '../../task/GameTaskLogManager'; @@ -34,7 +35,6 @@ import GameToolbarManager from '../../toolbar/GameToolbarManager'; import { mandatory, sleep, toS3Path } from '../../utils/GameUtils'; import GameGlobalAPI from './GameGlobalAPI'; import { createGamePhases } from './GameManagerHelper'; -import GameQuizManager from '../../quiz/GameQuizManager'; type GameManagerProps = { continueGame: boolean; From 8a0546a8e40952038c042bc756be03db1455984f Mon Sep 17 00:00:00 2001 From: CZX Date: Sun, 17 Mar 2024 15:10:21 +0800 Subject: [PATCH 04/61] CSE Machine: Remove old components and make compact components the default (#2848) * Make compact components the new default and remove any mentions to the old components. Also removes the experimental button toggle. * Update test snapshots * Clean up testing code a little * Formatting changes * Fix some minor issues --- src/features/cseMachine/components/values/GlobalFnValue.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/cseMachine/components/values/GlobalFnValue.tsx b/src/features/cseMachine/components/values/GlobalFnValue.tsx index 5bd18587b0..6a3c235378 100644 --- a/src/features/cseMachine/components/values/GlobalFnValue.tsx +++ b/src/features/cseMachine/components/values/GlobalFnValue.tsx @@ -190,7 +190,7 @@ export class GlobalFnValue extends Value implements IHoverable { /> )} - {this._arrow?.draw()} + {Layout.globalEnvNode.frame && new ArrowFromFn(this).to(Layout.globalEnvNode.frame).draw()} ); } From 491a4f39dd484eb10b4fb50d57485627255975bc Mon Sep 17 00:00:00 2001 From: Martin Henz Date: Sun, 17 Mar 2024 18:25:55 +0800 Subject: [PATCH 05/61] bumping js-slang (#2852) * bumping js-slang * Update tests * remove obsolete snapshots --------- Co-authored-by: NhatMinh0208 --- src/features/cseMachine/__tests__/CseMachine.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/cseMachine/__tests__/CseMachine.tsx b/src/features/cseMachine/__tests__/CseMachine.tsx index ac2827a432..904c000938 100644 --- a/src/features/cseMachine/__tests__/CseMachine.tsx +++ b/src/features/cseMachine/__tests__/CseMachine.tsx @@ -184,7 +184,7 @@ const codeSamplesControlStash = [ } create(3)[1](); `, - 33 + 32 ], [ 'global environments are treated correctly', From 59143df1d34b3842d887c8000e6cdd25f187e5b1 Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Fri, 22 Mar 2024 21:36:13 +0800 Subject: [PATCH 06/61] add quiz manager --- .../game/action/GameActionExecuter.ts | 2 +- .../game/dialogue/GameDialogueManager.ts | 6 +- src/features/game/quiz/GameQuizManager.ts | 184 ++++++++++++++++-- .../game/quiz/GameQuizReactionManager.ts | 87 +++++++++ .../game/scenes/gameManager/GameGlobalAPI.ts | 4 +- 5 files changed, 258 insertions(+), 25 deletions(-) create mode 100644 src/features/game/quiz/GameQuizReactionManager.ts diff --git a/src/features/game/action/GameActionExecuter.ts b/src/features/game/action/GameActionExecuter.ts index 9194b6f7bb..39cc0f7fe2 100644 --- a/src/features/game/action/GameActionExecuter.ts +++ b/src/features/game/action/GameActionExecuter.ts @@ -103,7 +103,7 @@ export default class GameActionExecuter { await sleep(actionParams.duration); return; case GameActionType.ShowQuiz: - globalAPI.showQuiz(actionParams.id); + await globalAPI.showQuiz(actionParams.id); return; default: return; diff --git a/src/features/game/dialogue/GameDialogueManager.ts b/src/features/game/dialogue/GameDialogueManager.ts index e6283e4bc6..82555e27c4 100644 --- a/src/features/game/dialogue/GameDialogueManager.ts +++ b/src/features/game/dialogue/GameDialogueManager.ts @@ -53,8 +53,7 @@ export default class DialogueManager { // add keyboard listener for dialogue box this.getInputManager().registerKeyboardListener(keyboardShortcuts.Next, 'up', async () => { // show the next line if dashboard or escape menu are not displayed - if ( - !GameGlobalAPI.getInstance().getGameManager().getPhaseManager().isCurrentPhaseTerminal() + if ( !GameGlobalAPI.getInstance().getGameManager().getPhaseManager().isCurrentPhaseTerminal() ) { await this.showNextLine(resolve); } @@ -73,7 +72,8 @@ export default class DialogueManager { const lineWithName = line.replace('{name}', this.getUsername()); this.getDialogueRenderer().changeText(lineWithName); this.getSpeakerRenderer().changeSpeakerTo(speakerDetail); - + + console.log(GameGlobalAPI.getInstance().getGameManager().getPhaseManager().isCurrentPhaseTerminal()); // Store the current line into the storage GameGlobalAPI.getInstance().storeDialogueLine(lineWithName, speakerDetail); diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 4c58544e57..5a100096c0 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -1,33 +1,179 @@ import { ItemId } from '../commons/CommonTypes'; -import { DialogueLine } from '../dialogue/GameDialogueTypes'; import GameGlobalAPI from '../scenes/gameManager/GameGlobalAPI'; -import { Option, Question } from './GameQuizType'; +import { Question } from './GameQuizType'; +import FontAssets from '../assets/FontAssets'; +import ImageAssets from '../assets/ImageAssets'; +import SoundAssets from '../assets/SoundAssets'; +import { Constants, screenSize } from '../commons/CommonConstants'; +import { BitmapFontStyle } from '../commons/CommonTypes'; +import { Layer } from '../layer/GameLayerTypes'; +import SourceAcademyGame from '../SourceAcademyGame'; +import { createButton } from '../utils/ButtonUtils'; +import { sleep } from '../utils/GameUtils'; +import { calcListFormatPos, Color, HexColor } from '../utils/StyleUtils'; +import { fadeAndDestroy } from '../effects/FadeEffect'; +import { rightSideEntryTweenProps, rightSideExitTweenProps } from '../effects/FlyEffect'; +import { DialogueObject } from '../dialogue/GameDialogueTypes'; +import GameQuizReactionManager from './GameQuizReactionManager'; export default class QuizManager { + private reactionManager? : GameQuizReactionManager; + QuizConstants = { + textPad: 20, + textConfig: { x: 15, y: -15, oriX: 0.5, oriY: 0.5 }, + y: 100, + width: 450, + yInterval: 100 + }; + + textStyle = { + fontFamily: 'Verdana', + fontSize: '20px', + fill: Color.offWhite, + align: 'right', + lineSpacing: 10, + wordWrap: { width: this.QuizConstants.width - this.QuizConstants.textPad * 2 } + }; + + quizOptStyle: BitmapFontStyle = { + key: FontAssets.zektonFont.key, + size: 25, + align: Phaser.GameObjects.BitmapText.ALIGN_CENTER + }; // Print everything. To test if the quiz parser parses correctly. - public showQuiz(quizId: ItemId) { - console.log('A quiz is shown'); + public async showQuiz(quizId:ItemId) { const quiz = GameGlobalAPI.getInstance().getQuizById(quizId); // get a quiz - const questions = quiz.questions; - questions.forEach((value: Question) => { - this.showQuestion(value); - }); + + + for (var i = 0; i < quiz.questions.length; i++ ) { + const res = await this.showQuizQuestion(GameGlobalAPI.getInstance().getGameManager(), quiz.questions[i]); + console.log("check the question displayed: " + res); + } } - private showQuestion(question: Question) { - console.log(question.question); - console.log(question.answer); - question.options.forEach(option => { - this.showOption(option); - }); + //Display the specific quiz question + public async showQuizQuestion(scene: Phaser.Scene, question: Question){ + + console.log(GameGlobalAPI.getInstance().getGameManager().getPhaseManager().isCurrentPhaseTerminal()); + const choices = question.options; + const quizContainer = new Phaser.GameObjects.Container(scene, 0, 0); + + const quizPartitions = Math.ceil(choices.length / 5); + const quizHeight = choices.length > 5 ? 5 : choices.length; + + const header = new Phaser.GameObjects.Text( + scene, + screenSize.x - this.QuizConstants.textPad, + this.QuizConstants.y, + question.question, + this.textStyle + ).setOrigin(1.0, 0.0); + const quizHeaderBg = new Phaser.GameObjects.Rectangle( + scene, + screenSize.x, + this.QuizConstants.y - this.QuizConstants.textPad, + this.QuizConstants.width * quizPartitions, + header.getBounds().bottom * 0.5 + this.QuizConstants.textPad, + HexColor.darkBlue, + 0.8 + ).setOrigin(1.0, 0.0); + const quizBg = new Phaser.GameObjects.Rectangle( + scene, + screenSize.x, + this.QuizConstants.y - this.QuizConstants.textPad, + this.QuizConstants.width * quizPartitions, + quizHeaderBg.getBounds().bottom * 0.5 + (quizHeight + 0.5) * this.QuizConstants.yInterval, + HexColor.lightBlue, + 0.2 + ).setOrigin(1.0, 0.0); + + quizContainer.add([quizBg, quizHeaderBg, header]); + + const buttonPositions = calcListFormatPos({ + numOfItems: choices.length, + xSpacing: 0, + ySpacing: this.QuizConstants.yInterval + }); + + GameGlobalAPI.getInstance().addToLayer(Layer.UI, quizContainer); + + const activateQuizContainer: Promise = new Promise(resolve => { + quizContainer.add( + choices.map((response, index) => + createButton(scene, { + assetKey: ImageAssets.mediumButton.key, + message: response.text, + textConfig: this.QuizConstants.textConfig, + bitMapTextStyle: this.quizOptStyle, + onUp: () => { + quizContainer.destroy(); + if (index == question.answer) { + resolve(this.showReaction(scene, question, choices[index].reaction, true)); + } else { + resolve(this.showReaction(scene, question, choices[index].reaction, false)); + } + } + }).setPosition( + screenSize.x - + this.QuizConstants.width / 2 - + this.QuizConstants.width * (quizPartitions - Math.floor(index / 5) - 1), + (buttonPositions[index][1] % (5 * this.QuizConstants.yInterval)) + + quizHeaderBg.getBounds().bottom + + 75 + ) + ) + );}); + + const response = await activateQuizContainer; + + // Animate in + quizContainer.setPosition(screenSize.x, 0); + SourceAcademyGame.getInstance().getSoundManager().playSound(SoundAssets.notifEnter.key); + scene.add.tween({ + targets: quizContainer, + alpha: 1, + ...rightSideEntryTweenProps + }); + await sleep(rightSideEntryTweenProps.duration); + + // Animate out + SourceAcademyGame.getInstance().getSoundManager().playSound(SoundAssets.notifExit.key); + scene.add.tween({ + targets: quizContainer, + alpha: 1, + ...rightSideExitTweenProps + }); + + await sleep(rightSideExitTweenProps.duration); + fadeAndDestroy(scene, quizContainer, { fadeDuration: Constants.fadeDuration }); + return response; } - private showOption(option: Option) { - console.log(option.text); - option.reaction.forEach((value: DialogueLine[]) => this.showReaction(value)); + private async showReaction(scene: Phaser.Scene, question: Question, reaction: DialogueObject, status: boolean) { + console.log("correct answer: " + status); + await this.showResult(scene, reaction); } - private showReaction(reaction: DialogueLine[]) { - reaction.forEach((line: DialogueLine) => console.log(line.line)); + + private async showResult(scene: Phaser.Scene, reaction: DialogueObject) { + this.reactionManager = new GameQuizReactionManager(reaction); + await this.reactionManager.showReaction(); } + + // private showQuestion(question: Question) { + // console.log(question.question); + // console.log(question.answer); + // question.options.forEach(option => { + // this.showOption(option); + // }); + // } + + // private showOption(option: Option) { + // console.log(option.text); + // option.reaction.forEach((value: DialogueLine[]) => this.showReaction(value)); + // } + + // private showReaction(reaction: DialogueLine[]) { + // reaction.forEach((line: DialogueLine) => console.log(line.line)); + // } } diff --git a/src/features/game/quiz/GameQuizReactionManager.ts b/src/features/game/quiz/GameQuizReactionManager.ts new file mode 100644 index 0000000000..2eb9a98025 --- /dev/null +++ b/src/features/game/quiz/GameQuizReactionManager.ts @@ -0,0 +1,87 @@ +import SoundAssets from '../assets/SoundAssets'; +import { promptWithChoices } from '../effects/Prompt'; +import { Layer } from '../layer/GameLayerTypes'; +import GameGlobalAPI from '../scenes/gameManager/GameGlobalAPI'; +import SourceAcademyGame from '../SourceAcademyGame'; +import { textTypeWriterStyle } from '../dialogue/GameDialogueConstants'; +import DialogueGenerator from '../dialogue/GameDialogueGenerator'; +import DialogueRenderer from '../dialogue/GameDialogueRenderer'; +import DialogueSpeakerRenderer from '../dialogue/GameDialogueSpeakerRenderer'; +import { DialogueObject } from "../dialogue/GameDialogueTypes"; + + +export default class GameQuizReactionManager { + private dialogue: DialogueObject; + private dialogueRenderer? : DialogueRenderer; + private dialogueGenerator? : DialogueGenerator; + private speakerRenderer? : DialogueSpeakerRenderer; + + + constructor(dialogue: DialogueObject) { + this.dialogue = dialogue; + } + + public async showReaction() : Promise { + this.dialogueRenderer = new DialogueRenderer(textTypeWriterStyle); + this.dialogueGenerator = new DialogueGenerator(this.dialogue); + this.speakerRenderer = new DialogueSpeakerRenderer(); + + GameGlobalAPI.getInstance().addToLayer( + Layer.Dialogue, + this.dialogueRenderer.getDialogueContainer() + ); + + GameGlobalAPI.getInstance().fadeInLayer(Layer.Dialogue); + await new Promise(resolve => this.playWholeDialogue(resolve as () => void)); + this.getDialogueRenderer().destroy(); + this.getSpeakerRenderer().changeSpeakerTo(null); + } + + private async playWholeDialogue(resolve: () => void) { + await this.showNextLine(resolve); + this.getDialogueRenderer() + .getDialogueBox() + .on(Phaser.Input.Events.GAMEOBJECT_POINTER_UP, async () => { + await this.showNextLine(resolve); + }); + } + + private async showNextLine(resolve: () => void) { + GameGlobalAPI.getInstance().playSound(SoundAssets.dialogueAdvance.key); + const { line, speakerDetail, actionIds, prompt } = + await this.getDialogueGenerator().generateNextLine(); + const lineWithName = line.replace('{name}', this.getUsername()); + this.getDialogueRenderer().changeText(lineWithName); + this.getSpeakerRenderer().changeSpeakerTo(speakerDetail); + + // Store the current line into the storage + GameGlobalAPI.getInstance().storeDialogueLine(lineWithName, speakerDetail); + + // Disable interactions while processing actions + GameGlobalAPI.getInstance().enableSprite(this.getDialogueRenderer().getDialogueBox(), false); + + if (prompt) { + // disable keyboard input to prevent continue dialogue + const response = await promptWithChoices( + GameGlobalAPI.getInstance().getGameManager(), + prompt.promptTitle, + prompt.choices.map(choice => choice[0]) + ); + + this.getDialogueGenerator().updateCurrPart(prompt.choices[response][1]); + } + await GameGlobalAPI.getInstance().processGameActionsInSamePhase(actionIds); + GameGlobalAPI.getInstance().enableSprite(this.getDialogueRenderer().getDialogueBox(), true); + + if (!line) { + resolve(); + } + } + + + + private getDialogueGenerator = () => this.dialogueGenerator as DialogueGenerator; + private getDialogueRenderer = () => this.dialogueRenderer as DialogueRenderer; + private getSpeakerRenderer = () => this.speakerRenderer as DialogueSpeakerRenderer; + public getUsername = () => SourceAcademyGame.getInstance().getAccountInfo().name; +} \ No newline at end of file diff --git a/src/features/game/scenes/gameManager/GameGlobalAPI.ts b/src/features/game/scenes/gameManager/GameGlobalAPI.ts index ffd8fd1e4e..3f114a7f24 100644 --- a/src/features/game/scenes/gameManager/GameGlobalAPI.ts +++ b/src/features/game/scenes/gameManager/GameGlobalAPI.ts @@ -514,8 +514,8 @@ class GameGlobalAPI { ///////////////////// // Game Quiz // ///////////////////// - public showQuiz(quizId: ItemId) { - this.getGameManager().getQuizManager().showQuiz(quizId); + public async showQuiz(quizId: ItemId) { + await this.getGameManager().getQuizManager().showQuiz(quizId); } } From 583f14e5d7c916a4732de0cc556309001e3422a3 Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Sat, 23 Mar 2024 07:48:05 +0800 Subject: [PATCH 07/61] create quiz result type --- src/features/game/quiz/GameQuizManager.ts | 10 ++++++---- src/features/game/quiz/GameQuizType.ts | 4 ++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 5a100096c0..842ef64f5e 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -1,6 +1,6 @@ import { ItemId } from '../commons/CommonTypes'; import GameGlobalAPI from '../scenes/gameManager/GameGlobalAPI'; -import { Question } from './GameQuizType'; +import { Question, QuizResult } from './GameQuizType'; import FontAssets from '../assets/FontAssets'; import ImageAssets from '../assets/ImageAssets'; import SoundAssets from '../assets/SoundAssets'; @@ -97,6 +97,7 @@ export default class QuizManager { GameGlobalAPI.getInstance().addToLayer(Layer.UI, quizContainer); + const quizResult: QuizResult = {numberOfQuestions : 0}; const activateQuizContainer: Promise = new Promise(resolve => { quizContainer.add( choices.map((response, index) => @@ -108,9 +109,10 @@ export default class QuizManager { onUp: () => { quizContainer.destroy(); if (index == question.answer) { - resolve(this.showReaction(scene, question, choices[index].reaction, true)); + quizResult.numberOfQuestions += 1; + resolve(this.showReaction(scene, question, choices[index].reaction, quizResult)); } else { - resolve(this.showReaction(scene, question, choices[index].reaction, false)); + resolve(this.showReaction(scene, question, choices[index].reaction, quizResult)); } } }).setPosition( @@ -149,7 +151,7 @@ export default class QuizManager { return response; } - private async showReaction(scene: Phaser.Scene, question: Question, reaction: DialogueObject, status: boolean) { + private async showReaction(scene: Phaser.Scene, question: Question, reaction: DialogueObject, status: QuizResult) { console.log("correct answer: " + status); await this.showResult(scene, reaction); } diff --git a/src/features/game/quiz/GameQuizType.ts b/src/features/game/quiz/GameQuizType.ts index 01f1594041..e8b8cb2875 100644 --- a/src/features/game/quiz/GameQuizType.ts +++ b/src/features/game/quiz/GameQuizType.ts @@ -14,3 +14,7 @@ export type Option = { text: string; reaction: DialogueObject; }; + +export type QuizResult = { + numberOfQuestions: number; +} \ No newline at end of file From a8d37b934dc28c7ed4c7f48f5fcd72805926750c Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Sat, 23 Mar 2024 10:50:39 +0800 Subject: [PATCH 08/61] hide dialogue box while showing quiz --- .../game/action/GameActionExecuter.ts | 5 ++++ .../game/dialogue/GameDialogueManager.ts | 18 ++++++++++++-- .../game/dialogue/GameDialogueRenderer.ts | 20 ++++++++++++++++ .../dialogue/GameDialogueSpeakerRenderer.ts | 24 +++++++++++++++++++ src/features/game/quiz/GameQuizManager.ts | 18 +++++++------- 5 files changed, 74 insertions(+), 11 deletions(-) diff --git a/src/features/game/action/GameActionExecuter.ts b/src/features/game/action/GameActionExecuter.ts index 39cc0f7fe2..7cffab691c 100644 --- a/src/features/game/action/GameActionExecuter.ts +++ b/src/features/game/action/GameActionExecuter.ts @@ -103,7 +103,12 @@ export default class GameActionExecuter { await sleep(actionParams.duration); return; case GameActionType.ShowQuiz: + //alert("action type showQuiz is caught"); + await GameGlobalAPI.getInstance().getGameManager() + .getDialogueManager().hideAll(); await globalAPI.showQuiz(actionParams.id); + await GameGlobalAPI.getInstance().getGameManager() + .getDialogueManager().showAll(); return; default: return; diff --git a/src/features/game/dialogue/GameDialogueManager.ts b/src/features/game/dialogue/GameDialogueManager.ts index 82555e27c4..3cf6b9659b 100644 --- a/src/features/game/dialogue/GameDialogueManager.ts +++ b/src/features/game/dialogue/GameDialogueManager.ts @@ -33,6 +33,8 @@ export default class DialogueManager { public async showDialogue(dialogueId: ItemId): Promise { const dialogue = GameGlobalAPI.getInstance().getDialogueById(dialogueId); + + this.dialogueRenderer = new DialogueRenderer(textTypeWriterStyle); this.dialogueGenerator = new DialogueGenerator(dialogue.content); this.speakerRenderer = new DialogueSpeakerRenderer(); @@ -79,7 +81,8 @@ export default class DialogueManager { // Disable interactions while processing actions GameGlobalAPI.getInstance().enableSprite(this.getDialogueRenderer().getDialogueBox(), false); - + this.getInputManager().enableKeyboardInput(false); + if (prompt) { // disable keyboard input to prevent continue dialogue this.getInputManager().enableKeyboardInput(false); @@ -94,6 +97,7 @@ export default class DialogueManager { } await GameGlobalAPI.getInstance().processGameActionsInSamePhase(actionIds); GameGlobalAPI.getInstance().enableSprite(this.getDialogueRenderer().getDialogueBox(), true); + this.getInputManager().enableKeyboardInput(true); if (!line) { // clear keyboard listeners when dialogue ends @@ -102,8 +106,18 @@ export default class DialogueManager { } } + public async hideAll() { + await this.getDialogueRenderer().hide(); + //await this.getSpeakerRenderer().hide(); + } + + public async showAll() { + await this.getDialogueRenderer().show(); + //await this.getSpeakerRenderer().show(); + } + private getDialogueGenerator = () => this.dialogueGenerator as DialogueGenerator; - private getDialogueRenderer = () => this.dialogueRenderer as DialogueRenderer; + public getDialogueRenderer = () => this.dialogueRenderer as DialogueRenderer; private getSpeakerRenderer = () => this.speakerRenderer as DialogueSpeakerRenderer; private getInputManager = () => this.gameInputManager as GameInputManager; diff --git a/src/features/game/dialogue/GameDialogueRenderer.ts b/src/features/game/dialogue/GameDialogueRenderer.ts index 092296c5cf..d67841fa37 100644 --- a/src/features/game/dialogue/GameDialogueRenderer.ts +++ b/src/features/game/dialogue/GameDialogueRenderer.ts @@ -19,6 +19,7 @@ class DialogueRenderer { */ constructor(typewriterStyle: Phaser.Types.GameObjects.Text.TextStyle) { const gameManager = GameGlobalAPI.getInstance().getGameManager(); + this.dialogueBox = createDialogueBox(gameManager).setInteractive({ useHandCursor: true, pixelPerfect: true @@ -67,6 +68,25 @@ class DialogueRenderer { fadeAndDestroy(gameManager, this.getDialogueContainer()); } + /** + * Hide the dialoguebox + */ + public async hide() { + this.typewriter.container.setVisible(false); + this.dialogueBox.setVisible(false); + this.blinkingDiamond.container.setVisible(false); + } + + /** + * Show the hidden dialoguebox + */ + public async show() { + this.typewriter.container.setVisible(true); + this.dialogueBox.setVisible(true); + this.blinkingDiamond.container.setVisible(true); + } + + /** * Change the text written in the box */ diff --git a/src/features/game/dialogue/GameDialogueSpeakerRenderer.ts b/src/features/game/dialogue/GameDialogueSpeakerRenderer.ts index 3a58adbb05..f409c37b51 100644 --- a/src/features/game/dialogue/GameDialogueSpeakerRenderer.ts +++ b/src/features/game/dialogue/GameDialogueSpeakerRenderer.ts @@ -16,6 +16,8 @@ import DialogueConstants, { speakerTextStyle } from './GameDialogueConstants'; */ export default class DialogueSpeakerRenderer { private currentSpeakerId?: string; + private speakerSprite?:Phaser.GameObjects.Image; + private speakerSpriteBox?:Phaser.GameObjects.Container; /** * Changes the speaker shown in the speaker box and the speaker rendered on screen @@ -63,6 +65,7 @@ export default class DialogueSpeakerRenderer { expression, speakerPosition ); + this.speakerSprite = speakerSprite; GameGlobalAPI.getInstance().addToLayer(Layer.Speaker, speakerSprite); } @@ -90,8 +93,29 @@ export default class DialogueSpeakerRenderer { container.add([rectangle, speakerText]); speakerText.text = StringUtils.capitalize(text); + this.speakerSpriteBox = container; return container; } + /** + * Hide the speaker box and sprite + */ + public async hide() { + this.getSpeakerSprite().setVisible(false); + this.getSpeakerSpriteBox().setVisible(false); + } + + /** + * Show the hidden speaker box and sprite + */ + public async show() { + console.log(this.getSpeakerSprite().visible); + this.getSpeakerSprite().setVisible(true); + console.log(this.getSpeakerSprite().visible); + this.getSpeakerSpriteBox().setVisible(true); + } + public getUsername = () => SourceAcademyGame.getInstance().getAccountInfo().name; + public getSpeakerSprite = () => this.speakerSprite as Phaser.GameObjects.Image; + public getSpeakerSpriteBox = () => this.speakerSpriteBox as Phaser.GameObjects.Container; } diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 842ef64f5e..f7bf76106e 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -15,6 +15,7 @@ import { fadeAndDestroy } from '../effects/FadeEffect'; import { rightSideEntryTweenProps, rightSideExitTweenProps } from '../effects/FlyEffect'; import { DialogueObject } from '../dialogue/GameDialogueTypes'; import GameQuizReactionManager from './GameQuizReactionManager'; +//import { displayNotification } from '../effects/Notification'; export default class QuizManager { private reactionManager? : GameQuizReactionManager; @@ -43,16 +44,16 @@ export default class QuizManager { // Print everything. To test if the quiz parser parses correctly. public async showQuiz(quizId:ItemId) { const quiz = GameGlobalAPI.getInstance().getQuizById(quizId); // get a quiz - + const quizResult : QuizResult = {numberOfQuestions : 0}; for (var i = 0; i < quiz.questions.length; i++ ) { - const res = await this.showQuizQuestion(GameGlobalAPI.getInstance().getGameManager(), quiz.questions[i]); + const res = await this.showQuizQuestion(GameGlobalAPI.getInstance().getGameManager(), quiz.questions[i], quizResult); console.log("check the question displayed: " + res); } } //Display the specific quiz question - public async showQuizQuestion(scene: Phaser.Scene, question: Question){ + public async showQuizQuestion(scene: Phaser.Scene, question: Question, quizResult : QuizResult){ console.log(GameGlobalAPI.getInstance().getGameManager().getPhaseManager().isCurrentPhaseTerminal()); const choices = question.options; @@ -96,8 +97,6 @@ export default class QuizManager { }); GameGlobalAPI.getInstance().addToLayer(Layer.UI, quizContainer); - - const quizResult: QuizResult = {numberOfQuestions : 0}; const activateQuizContainer: Promise = new Promise(resolve => { quizContainer.add( choices.map((response, index) => @@ -108,11 +107,11 @@ export default class QuizManager { bitMapTextStyle: this.quizOptStyle, onUp: () => { quizContainer.destroy(); - if (index == question.answer) { + if (index === question.answer) { quizResult.numberOfQuestions += 1; - resolve(this.showReaction(scene, question, choices[index].reaction, quizResult)); + resolve(this.showReaction(scene, question, choices[index].reaction, quizResult)); } else { - resolve(this.showReaction(scene, question, choices[index].reaction, quizResult)); + resolve(this.showReaction(scene, question, choices[index].reaction, quizResult)); } } }).setPosition( @@ -152,8 +151,9 @@ export default class QuizManager { } private async showReaction(scene: Phaser.Scene, question: Question, reaction: DialogueObject, status: QuizResult) { - console.log("correct answer: " + status); + console.log("the number of correct answer: " + status.numberOfQuestions); await this.showResult(scene, reaction); + //await displayNotification(GameGlobalAPI.getInstance().getGameManager(), "number of correct questions: " + status.numberOfQuestions); } From eab42e59e9632ec9a04f71f6813d35b0b1b54f7b Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Sat, 23 Mar 2024 11:04:54 +0800 Subject: [PATCH 09/61] modify character hidding feature --- src/features/game/dialogue/GameDialogueManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/game/dialogue/GameDialogueManager.ts b/src/features/game/dialogue/GameDialogueManager.ts index 3cf6b9659b..e2d8065e8f 100644 --- a/src/features/game/dialogue/GameDialogueManager.ts +++ b/src/features/game/dialogue/GameDialogueManager.ts @@ -108,12 +108,12 @@ export default class DialogueManager { public async hideAll() { await this.getDialogueRenderer().hide(); - //await this.getSpeakerRenderer().hide(); + await this.getSpeakerRenderer().hide(); } public async showAll() { await this.getDialogueRenderer().show(); - //await this.getSpeakerRenderer().show(); + await this.getSpeakerRenderer().show(); } private getDialogueGenerator = () => this.dialogueGenerator as DialogueGenerator; From 91f79765b6f6ca23f728071dcf72b759fd25ddd6 Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Sat, 23 Mar 2024 11:56:31 +0800 Subject: [PATCH 10/61] track and display quiz results --- src/features/game/quiz/GameQuizConstants.ts | 18 ++++++++++++++++ src/features/game/quiz/GameQuizManager.ts | 24 +++++++++++++++++---- src/features/game/quiz/GameQuizOutcome.ts | 7 ++++++ src/features/game/quiz/GameQuizType.ts | 1 + 4 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 src/features/game/quiz/GameQuizOutcome.ts diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index 79ba47a577..12baea1c94 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -1,4 +1,22 @@ +import { DialogueLine, DialogueObject } from "../dialogue/GameDialogueTypes"; + export const defaultReaction = { correct: ['You are right.'], wrong: ['Wrong answer...'] }; + +const resultLine : DialogueLine = { + line : "good job", +} + +const resultLine2 : DialogueLine = { + line : "Need improvement. Some questions are wrong. ", +} + +export const allCorrect : DialogueObject = new Map([ + ["all correct", [resultLine]] +]); + +export const ImproveMent : DialogueObject = new Map([ + ["improvement", [resultLine2]] +]); \ No newline at end of file diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index f7bf76106e..a1cd79c35c 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -15,7 +15,9 @@ import { fadeAndDestroy } from '../effects/FadeEffect'; import { rightSideEntryTweenProps, rightSideExitTweenProps } from '../effects/FlyEffect'; import { DialogueObject } from '../dialogue/GameDialogueTypes'; import GameQuizReactionManager from './GameQuizReactionManager'; -//import { displayNotification } from '../effects/Notification'; +import { displayNotification } from '../effects/Notification'; +import GameQuizOutcomeManager from './GameQuizOutcome'; +import { ImproveMent, allCorrect } from './GameQuizConstants'; export default class QuizManager { private reactionManager? : GameQuizReactionManager; @@ -44,12 +46,17 @@ export default class QuizManager { // Print everything. To test if the quiz parser parses correctly. public async showQuiz(quizId:ItemId) { const quiz = GameGlobalAPI.getInstance().getQuizById(quizId); // get a quiz - const quizResult : QuizResult = {numberOfQuestions : 0}; + const quizResult : QuizResult = { + numberOfQuestions : 0, + allCorrect : true, + }; for (var i = 0; i < quiz.questions.length; i++ ) { - const res = await this.showQuizQuestion(GameGlobalAPI.getInstance().getGameManager(), quiz.questions[i], quizResult); - console.log("check the question displayed: " + res); + await this.showQuizQuestion(GameGlobalAPI.getInstance().getGameManager(), quiz.questions[i], quizResult); + //console.log("check the question displayed: " + res); } + + await this.displayFinalResult(quizResult); } //Display the specific quiz question @@ -111,6 +118,7 @@ export default class QuizManager { quizResult.numberOfQuestions += 1; resolve(this.showReaction(scene, question, choices[index].reaction, quizResult)); } else { + quizResult.allCorrect = false; resolve(this.showReaction(scene, question, choices[index].reaction, quizResult)); } } @@ -162,6 +170,14 @@ export default class QuizManager { await this.reactionManager.showReaction(); } + private async displayFinalResult(quizResult : QuizResult) { + await displayNotification(GameGlobalAPI.getInstance().getGameManager(), "scores: " + quizResult.numberOfQuestions); + const outComeManager : GameQuizOutcomeManager = + quizResult.allCorrect ? new GameQuizOutcomeManager(allCorrect) + : new GameQuizOutcomeManager(ImproveMent); + await outComeManager.showReaction(); + } + // private showQuestion(question: Question) { // console.log(question.question); // console.log(question.answer); diff --git a/src/features/game/quiz/GameQuizOutcome.ts b/src/features/game/quiz/GameQuizOutcome.ts new file mode 100644 index 0000000000..4d15a31f9c --- /dev/null +++ b/src/features/game/quiz/GameQuizOutcome.ts @@ -0,0 +1,7 @@ +import GameQuizReactionManager from './GameQuizReactionManager'; +import { DialogueObject } from '../dialogue/GameDialogueTypes'; +export default class GameQuizOutcomeManager extends GameQuizReactionManager { + constructor(dialogueObj : DialogueObject) { + super(dialogueObj); + } +} \ No newline at end of file diff --git a/src/features/game/quiz/GameQuizType.ts b/src/features/game/quiz/GameQuizType.ts index e8b8cb2875..f5970a2251 100644 --- a/src/features/game/quiz/GameQuizType.ts +++ b/src/features/game/quiz/GameQuizType.ts @@ -17,4 +17,5 @@ export type Option = { export type QuizResult = { numberOfQuestions: number; + allCorrect : boolean; } \ No newline at end of file From 023c9cb8734871ef4c57fa6bf53c2d6c1b03a803 Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Mon, 25 Mar 2024 09:58:07 +0800 Subject: [PATCH 11/61] debugging for speaker displaying --- .../dialogue/GameDialogueSpeakerRenderer.ts | 2 -- src/features/game/quiz/GameQuizConstants.ts | 28 +++++++++++++++++- src/features/game/quiz/GameQuizManager.ts | 29 +++++-------------- .../game/quiz/GameQuizReactionManager.ts | 12 ++++---- 4 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/features/game/dialogue/GameDialogueSpeakerRenderer.ts b/src/features/game/dialogue/GameDialogueSpeakerRenderer.ts index f409c37b51..970df687dd 100644 --- a/src/features/game/dialogue/GameDialogueSpeakerRenderer.ts +++ b/src/features/game/dialogue/GameDialogueSpeakerRenderer.ts @@ -109,9 +109,7 @@ export default class DialogueSpeakerRenderer { * Show the hidden speaker box and sprite */ public async show() { - console.log(this.getSpeakerSprite().visible); this.getSpeakerSprite().setVisible(true); - console.log(this.getSpeakerSprite().visible); this.getSpeakerSpriteBox().setVisible(true); } diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index 12baea1c94..fb96df00b6 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -1,4 +1,7 @@ import { DialogueLine, DialogueObject } from "../dialogue/GameDialogueTypes"; +import FontAssets from '../assets/FontAssets'; +import { BitmapFontStyle } from '../commons/CommonTypes'; +import { Color } from '../utils/StyleUtils'; export const defaultReaction = { correct: ['You are right.'], @@ -19,4 +22,27 @@ export const allCorrect : DialogueObject = new Map([ export const ImproveMent : DialogueObject = new Map([ ["improvement", [resultLine2]] -]); \ No newline at end of file +]); + +export const QuizConstants = { + textPad: 20, + textConfig: { x: 15, y: -15, oriX: 0.5, oriY: 0.5 }, + y: 100, + width: 450, + yInterval: 100 +}; + +export const textStyle = { + fontFamily: 'Verdana', + fontSize: '20px', + fill: Color.offWhite, + align: 'right', + lineSpacing: 10, + wordWrap: { width: QuizConstants.width - QuizConstants.textPad * 2 } + }; + +export const quizOptStyle: BitmapFontStyle = { + key: FontAssets.zektonFont.key, + size: 25, + align: Phaser.GameObjects.BitmapText.ALIGN_CENTER + }; \ No newline at end of file diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index a1cd79c35c..dff590f687 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -43,6 +43,7 @@ export default class QuizManager { size: 25, align: Phaser.GameObjects.BitmapText.ALIGN_CENTER }; + // Print everything. To test if the quiz parser parses correctly. public async showQuiz(quizId:ItemId) { const quiz = GameGlobalAPI.getInstance().getQuizById(quizId); // get a quiz @@ -61,21 +62,20 @@ export default class QuizManager { //Display the specific quiz question public async showQuizQuestion(scene: Phaser.Scene, question: Question, quizResult : QuizResult){ - - console.log(GameGlobalAPI.getInstance().getGameManager().getPhaseManager().isCurrentPhaseTerminal()); const choices = question.options; const quizContainer = new Phaser.GameObjects.Container(scene, 0, 0); const quizPartitions = Math.ceil(choices.length / 5); - const quizHeight = choices.length > 5 ? 5 : choices.length; + const quizHeight = choices.length; const header = new Phaser.GameObjects.Text( scene, screenSize.x - this.QuizConstants.textPad, this.QuizConstants.y, - question.question, + "quiz: " + question.question, this.textStyle ).setOrigin(1.0, 0.0); + const quizHeaderBg = new Phaser.GameObjects.Rectangle( scene, screenSize.x, @@ -85,6 +85,7 @@ export default class QuizManager { HexColor.darkBlue, 0.8 ).setOrigin(1.0, 0.0); + const quizBg = new Phaser.GameObjects.Rectangle( scene, screenSize.x, @@ -104,6 +105,7 @@ export default class QuizManager { }); GameGlobalAPI.getInstance().addToLayer(Layer.UI, quizContainer); + const activateQuizContainer: Promise = new Promise(resolve => { quizContainer.add( choices.map((response, index) => @@ -153,7 +155,7 @@ export default class QuizManager { ...rightSideExitTweenProps }); - await sleep(rightSideExitTweenProps.duration); + //await sleep(rightSideExitTweenProps.duration); fadeAndDestroy(scene, quizContainer, { fadeDuration: Constants.fadeDuration }); return response; } @@ -178,20 +180,5 @@ export default class QuizManager { await outComeManager.showReaction(); } - // private showQuestion(question: Question) { - // console.log(question.question); - // console.log(question.answer); - // question.options.forEach(option => { - // this.showOption(option); - // }); - // } - - // private showOption(option: Option) { - // console.log(option.text); - // option.reaction.forEach((value: DialogueLine[]) => this.showReaction(value)); - // } - - // private showReaction(reaction: DialogueLine[]) { - // reaction.forEach((line: DialogueLine) => console.log(line.line)); - // } + } diff --git a/src/features/game/quiz/GameQuizReactionManager.ts b/src/features/game/quiz/GameQuizReactionManager.ts index 2eb9a98025..ee70651826 100644 --- a/src/features/game/quiz/GameQuizReactionManager.ts +++ b/src/features/game/quiz/GameQuizReactionManager.ts @@ -6,7 +6,7 @@ import SourceAcademyGame from '../SourceAcademyGame'; import { textTypeWriterStyle } from '../dialogue/GameDialogueConstants'; import DialogueGenerator from '../dialogue/GameDialogueGenerator'; import DialogueRenderer from '../dialogue/GameDialogueRenderer'; -import DialogueSpeakerRenderer from '../dialogue/GameDialogueSpeakerRenderer'; +//import DialogueSpeakerRenderer from '../dialogue/GameDialogueSpeakerRenderer'; import { DialogueObject } from "../dialogue/GameDialogueTypes"; @@ -14,7 +14,7 @@ export default class GameQuizReactionManager { private dialogue: DialogueObject; private dialogueRenderer? : DialogueRenderer; private dialogueGenerator? : DialogueGenerator; - private speakerRenderer? : DialogueSpeakerRenderer; + //private speakerRenderer? : DialogueSpeakerRenderer; constructor(dialogue: DialogueObject) { @@ -24,7 +24,7 @@ export default class GameQuizReactionManager { public async showReaction() : Promise { this.dialogueRenderer = new DialogueRenderer(textTypeWriterStyle); this.dialogueGenerator = new DialogueGenerator(this.dialogue); - this.speakerRenderer = new DialogueSpeakerRenderer(); + //this.speakerRenderer = new DialogueSpeakerRenderer(); GameGlobalAPI.getInstance().addToLayer( Layer.Dialogue, @@ -34,7 +34,7 @@ export default class GameQuizReactionManager { GameGlobalAPI.getInstance().fadeInLayer(Layer.Dialogue); await new Promise(resolve => this.playWholeDialogue(resolve as () => void)); this.getDialogueRenderer().destroy(); - this.getSpeakerRenderer().changeSpeakerTo(null); + //this.getSpeakerRenderer().changeSpeakerTo(null); } private async playWholeDialogue(resolve: () => void) { @@ -52,7 +52,7 @@ export default class GameQuizReactionManager { await this.getDialogueGenerator().generateNextLine(); const lineWithName = line.replace('{name}', this.getUsername()); this.getDialogueRenderer().changeText(lineWithName); - this.getSpeakerRenderer().changeSpeakerTo(speakerDetail); + //this.getSpeakerRenderer().changeSpeakerTo(speakerDetail); // Store the current line into the storage GameGlobalAPI.getInstance().storeDialogueLine(lineWithName, speakerDetail); @@ -82,6 +82,6 @@ export default class GameQuizReactionManager { private getDialogueGenerator = () => this.dialogueGenerator as DialogueGenerator; private getDialogueRenderer = () => this.dialogueRenderer as DialogueRenderer; - private getSpeakerRenderer = () => this.speakerRenderer as DialogueSpeakerRenderer; + //private getSpeakerRenderer = () => this.speakerRenderer as DialogueSpeakerRenderer; public getUsername = () => SourceAcademyGame.getInstance().getAccountInfo().name; } \ No newline at end of file From 6d34b75031ab2accf6871f74835a0813ef1c57b7 Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Mon, 25 Mar 2024 10:55:47 +0800 Subject: [PATCH 12/61] midify UI for quiz displaying --- src/features/game/quiz/GameQuizConstants.ts | 15 +++- src/features/game/quiz/GameQuizManager.ts | 76 ++++++++----------- .../game/quiz/GameQuizReactionManager.ts | 5 +- 3 files changed, 46 insertions(+), 50 deletions(-) diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index fb96df00b6..ad71a7a016 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -25,14 +25,14 @@ export const ImproveMent : DialogueObject = new Map([ ]); export const QuizConstants = { - textPad: 20, + textPad: 10, textConfig: { x: 15, y: -15, oriX: 0.5, oriY: 0.5 }, - y: 100, + y: 50, width: 450, yInterval: 100 }; -export const textStyle = { +export const textStyle = { fontFamily: 'Verdana', fontSize: '20px', fill: Color.offWhite, @@ -40,6 +40,15 @@ export const textStyle = { lineSpacing: 10, wordWrap: { width: QuizConstants.width - QuizConstants.textPad * 2 } }; + +export const questionTextStyle = { + fontFamily: 'Verdana', + fontSize: '30px', + fill: Color.lightBlue, + align: 'left', + lineSpacing: 10, + wordWrap: { width: QuizConstants.width - QuizConstants.textPad * 2 } + }; export const quizOptStyle: BitmapFontStyle = { key: FontAssets.zektonFont.key, diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index dff590f687..93e326a2df 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -1,16 +1,15 @@ import { ItemId } from '../commons/CommonTypes'; import GameGlobalAPI from '../scenes/gameManager/GameGlobalAPI'; import { Question, QuizResult } from './GameQuizType'; -import FontAssets from '../assets/FontAssets'; +import { QuizConstants, textStyle, quizOptStyle, questionTextStyle } from './GameQuizConstants'; import ImageAssets from '../assets/ImageAssets'; import SoundAssets from '../assets/SoundAssets'; import { Constants, screenSize } from '../commons/CommonConstants'; -import { BitmapFontStyle } from '../commons/CommonTypes'; import { Layer } from '../layer/GameLayerTypes'; import SourceAcademyGame from '../SourceAcademyGame'; import { createButton } from '../utils/ButtonUtils'; import { sleep } from '../utils/GameUtils'; -import { calcListFormatPos, Color, HexColor } from '../utils/StyleUtils'; +import { calcListFormatPos, HexColor } from '../utils/StyleUtils'; import { fadeAndDestroy } from '../effects/FadeEffect'; import { rightSideEntryTweenProps, rightSideExitTweenProps } from '../effects/FlyEffect'; import { DialogueObject } from '../dialogue/GameDialogueTypes'; @@ -18,31 +17,10 @@ import GameQuizReactionManager from './GameQuizReactionManager'; import { displayNotification } from '../effects/Notification'; import GameQuizOutcomeManager from './GameQuizOutcome'; import { ImproveMent, allCorrect } from './GameQuizConstants'; +import { createDialogueBox, createTypewriter } from '../dialogue/GameDialogueHelper'; export default class QuizManager { private reactionManager? : GameQuizReactionManager; - QuizConstants = { - textPad: 20, - textConfig: { x: 15, y: -15, oriX: 0.5, oriY: 0.5 }, - y: 100, - width: 450, - yInterval: 100 - }; - - textStyle = { - fontFamily: 'Verdana', - fontSize: '20px', - fill: Color.offWhite, - align: 'right', - lineSpacing: 10, - wordWrap: { width: this.QuizConstants.width - this.QuizConstants.textPad * 2 } - }; - - quizOptStyle: BitmapFontStyle = { - key: FontAssets.zektonFont.key, - size: 25, - align: Phaser.GameObjects.BitmapText.ALIGN_CENTER - }; // Print everything. To test if the quiz parser parses correctly. public async showQuiz(quizId:ItemId) { @@ -68,40 +46,48 @@ export default class QuizManager { const quizPartitions = Math.ceil(choices.length / 5); const quizHeight = choices.length; + //create quiz box contains quiz questions + const quizQuestionBox = createDialogueBox(scene); + + const quizQuestionWriter = createTypewriter(scene, questionTextStyle); + + quizQuestionWriter.changeLine(question.question); + const header = new Phaser.GameObjects.Text( scene, - screenSize.x - this.QuizConstants.textPad, - this.QuizConstants.y, - "quiz: " + question.question, - this.textStyle + screenSize.x / 2 - QuizConstants.textPad, + QuizConstants.y, + "options" , + textStyle ).setOrigin(1.0, 0.0); const quizHeaderBg = new Phaser.GameObjects.Rectangle( scene, - screenSize.x, - this.QuizConstants.y - this.QuizConstants.textPad, - this.QuizConstants.width * quizPartitions, - header.getBounds().bottom * 0.5 + this.QuizConstants.textPad, + screenSize.x / 2, + QuizConstants.y - QuizConstants.textPad, + QuizConstants.width * quizPartitions, + header.getBounds().bottom * 0.5 + QuizConstants.textPad, HexColor.darkBlue, 0.8 ).setOrigin(1.0, 0.0); const quizBg = new Phaser.GameObjects.Rectangle( scene, - screenSize.x, - this.QuizConstants.y - this.QuizConstants.textPad, - this.QuizConstants.width * quizPartitions, - quizHeaderBg.getBounds().bottom * 0.5 + (quizHeight + 0.5) * this.QuizConstants.yInterval, + screenSize.x / 2, + QuizConstants.y - QuizConstants.textPad, + QuizConstants.width * quizPartitions, + quizHeaderBg.getBounds().bottom * 0.5 + (quizHeight + 0.5) * QuizConstants.yInterval, HexColor.lightBlue, 0.2 ).setOrigin(1.0, 0.0); - quizContainer.add([quizBg, quizHeaderBg, header]); + quizContainer.add([quizBg, quizHeaderBg, header, + quizQuestionBox, quizQuestionWriter.container]); const buttonPositions = calcListFormatPos({ numOfItems: choices.length, xSpacing: 0, - ySpacing: this.QuizConstants.yInterval + ySpacing: QuizConstants.yInterval }); GameGlobalAPI.getInstance().addToLayer(Layer.UI, quizContainer); @@ -112,8 +98,8 @@ export default class QuizManager { createButton(scene, { assetKey: ImageAssets.mediumButton.key, message: response.text, - textConfig: this.QuizConstants.textConfig, - bitMapTextStyle: this.quizOptStyle, + textConfig: QuizConstants.textConfig, + bitMapTextStyle: quizOptStyle, onUp: () => { quizContainer.destroy(); if (index === question.answer) { @@ -125,10 +111,10 @@ export default class QuizManager { } } }).setPosition( - screenSize.x - - this.QuizConstants.width / 2 - - this.QuizConstants.width * (quizPartitions - Math.floor(index / 5) - 1), - (buttonPositions[index][1] % (5 * this.QuizConstants.yInterval)) + + screenSize.x / 2 - + QuizConstants.width / 2 - + QuizConstants.width * (quizPartitions - Math.floor(index / 5) - 1), + (buttonPositions[index][1] % (5 * QuizConstants.yInterval)) + quizHeaderBg.getBounds().bottom + 75 ) diff --git a/src/features/game/quiz/GameQuizReactionManager.ts b/src/features/game/quiz/GameQuizReactionManager.ts index ee70651826..39cd5332ee 100644 --- a/src/features/game/quiz/GameQuizReactionManager.ts +++ b/src/features/game/quiz/GameQuizReactionManager.ts @@ -3,11 +3,12 @@ import { promptWithChoices } from '../effects/Prompt'; import { Layer } from '../layer/GameLayerTypes'; import GameGlobalAPI from '../scenes/gameManager/GameGlobalAPI'; import SourceAcademyGame from '../SourceAcademyGame'; -import { textTypeWriterStyle } from '../dialogue/GameDialogueConstants'; +//import { textTypeWriterStyle } from '../dialogue/GameDialogueConstants'; import DialogueGenerator from '../dialogue/GameDialogueGenerator'; import DialogueRenderer from '../dialogue/GameDialogueRenderer'; //import DialogueSpeakerRenderer from '../dialogue/GameDialogueSpeakerRenderer'; import { DialogueObject } from "../dialogue/GameDialogueTypes"; +import { questionTextStyle } from './GameQuizConstants'; export default class GameQuizReactionManager { @@ -22,7 +23,7 @@ export default class GameQuizReactionManager { } public async showReaction() : Promise { - this.dialogueRenderer = new DialogueRenderer(textTypeWriterStyle); + this.dialogueRenderer = new DialogueRenderer(questionTextStyle); this.dialogueGenerator = new DialogueGenerator(this.dialogue); //this.speakerRenderer = new DialogueSpeakerRenderer(); From fd701c97950b4391ed39918320b4f715727cbd84 Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Tue, 26 Mar 2024 13:59:09 +0800 Subject: [PATCH 13/61] update UI for quiz display --- src/features/game/quiz/GameQuizConstants.ts | 5 +++-- src/features/game/quiz/GameQuizManager.ts | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index ad71a7a016..701adf5372 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -2,6 +2,7 @@ import { DialogueLine, DialogueObject } from "../dialogue/GameDialogueTypes"; import FontAssets from '../assets/FontAssets'; import { BitmapFontStyle } from '../commons/CommonTypes'; import { Color } from '../utils/StyleUtils'; +import { screenSize } from "../commons/CommonConstants"; export const defaultReaction = { correct: ['You are right.'], @@ -25,7 +26,7 @@ export const ImproveMent : DialogueObject = new Map([ ]); export const QuizConstants = { - textPad: 10, + textPad: 250, textConfig: { x: 15, y: -15, oriX: 0.5, oriY: 0.5 }, y: 50, width: 450, @@ -47,7 +48,7 @@ export const questionTextStyle = { fill: Color.lightBlue, align: 'left', lineSpacing: 10, - wordWrap: { width: QuizConstants.width - QuizConstants.textPad * 2 } + wordWrap: { width: screenSize.x - 240 } }; export const quizOptStyle: BitmapFontStyle = { diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 93e326a2df..3781373e7f 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -55,7 +55,7 @@ export default class QuizManager { const header = new Phaser.GameObjects.Text( scene, - screenSize.x / 2 - QuizConstants.textPad, + screenSize.x / 2 + QuizConstants.textPad, QuizConstants.y, "options" , textStyle @@ -63,7 +63,7 @@ export default class QuizManager { const quizHeaderBg = new Phaser.GameObjects.Rectangle( scene, - screenSize.x / 2, + screenSize.x / 2 + QuizConstants.textPad, QuizConstants.y - QuizConstants.textPad, QuizConstants.width * quizPartitions, header.getBounds().bottom * 0.5 + QuizConstants.textPad, @@ -73,8 +73,8 @@ export default class QuizManager { const quizBg = new Phaser.GameObjects.Rectangle( scene, - screenSize.x / 2, - QuizConstants.y - QuizConstants.textPad, + screenSize.x / 2 + QuizConstants.textPad, + QuizConstants.y, QuizConstants.width * quizPartitions, quizHeaderBg.getBounds().bottom * 0.5 + (quizHeight + 0.5) * QuizConstants.yInterval, HexColor.lightBlue, @@ -113,7 +113,7 @@ export default class QuizManager { }).setPosition( screenSize.x / 2 - QuizConstants.width / 2 - - QuizConstants.width * (quizPartitions - Math.floor(index / 5) - 1), + QuizConstants.width * (quizPartitions - Math.floor(index / 5) - 1) + QuizConstants.textPad, (buttonPositions[index][1] % (5 * QuizConstants.yInterval)) + quizHeaderBg.getBounds().bottom + 75 From 85ef1fbff6a7e5e5ae6af1223ae8a0e36bdfa075 Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Tue, 26 Mar 2024 14:05:27 +0800 Subject: [PATCH 14/61] attempt to rebase --- src/features/game/quiz/GameQuizConstants.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index 701adf5372..5903b5ee81 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -22,7 +22,6 @@ export const allCorrect : DialogueObject = new Map([ ]); export const ImproveMent : DialogueObject = new Map([ - ["improvement", [resultLine2]] ]); export const QuizConstants = { From f7eaed73223f2f24b390d57fe7ffc97b88c9cd29 Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Tue, 26 Mar 2024 14:07:41 +0800 Subject: [PATCH 15/61] small bug fix --- src/features/game/quiz/GameQuizConstants.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index 5903b5ee81..9711d28d78 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -22,6 +22,7 @@ export const allCorrect : DialogueObject = new Map([ ]); export const ImproveMent : DialogueObject = new Map([ + ["incorrect", [resultLine2]] ]); export const QuizConstants = { From c7044b5ff7e0a708ad9409255b375fe8c951b03d Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Fri, 29 Mar 2024 11:53:05 +0800 Subject: [PATCH 16/61] solve quiz speaker issue --- src/features/game/layer/GameLayerTypes.ts | 6 +- src/features/game/quiz/GameQuizConstants.ts | 20 ++- src/features/game/quiz/GameQuizManager.ts | 5 +- .../game/quiz/GameQuizReactionManager.ts | 12 +- .../game/quiz/GameQuizSpeakerRenderer.ts | 114 ++++++++++++++++++ 5 files changed, 143 insertions(+), 14 deletions(-) create mode 100644 src/features/game/quiz/GameQuizSpeakerRenderer.ts diff --git a/src/features/game/layer/GameLayerTypes.ts b/src/features/game/layer/GameLayerTypes.ts index 112c9ee0a5..55a7139cdc 100644 --- a/src/features/game/layer/GameLayerTypes.ts +++ b/src/features/game/layer/GameLayerTypes.ts @@ -12,7 +12,9 @@ export enum Layer { Escape, Selector, Dashboard, - WorkerMessage + WorkerMessage, + QuizSpeakerBox, + QuizSpeaker } // Back to Front @@ -23,9 +25,11 @@ export const defaultLayerSequence = [ Layer.BBox, Layer.Character, Layer.Speaker, + Layer.QuizSpeaker, Layer.PopUp, Layer.Dialogue, Layer.SpeakerBox, + Layer.QuizSpeakerBox, Layer.Effects, Layer.Dashboard, Layer.Escape, diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index 9711d28d78..906a49b6cd 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -30,16 +30,18 @@ export const QuizConstants = { textConfig: { x: 15, y: -15, oriX: 0.5, oriY: 0.5 }, y: 50, width: 450, - yInterval: 100 + yInterval: 100, + headerOff : 60, + speakerTextConfig: { x: 320, y: 745, oriX: 0.5, oriY: 0.5 } }; export const textStyle = { fontFamily: 'Verdana', - fontSize: '20px', + fontSize: '30px', fill: Color.offWhite, - align: 'right', + align: 'left', lineSpacing: 10, - wordWrap: { width: QuizConstants.width - QuizConstants.textPad * 2 } + wordWrap: { width: 60 } }; export const questionTextStyle = { @@ -55,4 +57,12 @@ export const quizOptStyle: BitmapFontStyle = { key: FontAssets.zektonFont.key, size: 25, align: Phaser.GameObjects.BitmapText.ALIGN_CENTER - }; \ No newline at end of file + }; + + + +export const speakerTextStyle: BitmapFontStyle = { + key: FontAssets.zektonFont.key, + size: 36, + align: Phaser.GameObjects.BitmapText.ALIGN_CENTER +}; \ No newline at end of file diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 3781373e7f..dbc1a63ff2 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -55,7 +55,8 @@ export default class QuizManager { const header = new Phaser.GameObjects.Text( scene, - screenSize.x / 2 + QuizConstants.textPad, + screenSize.x / 2 + QuizConstants.textPad - QuizConstants.width * quizPartitions / 2 + + QuizConstants.headerOff, QuizConstants.y, "options" , textStyle @@ -90,7 +91,7 @@ export default class QuizManager { ySpacing: QuizConstants.yInterval }); - GameGlobalAPI.getInstance().addToLayer(Layer.UI, quizContainer); + GameGlobalAPI.getInstance().addToLayer(Layer.Dialogue, quizContainer); const activateQuizContainer: Promise = new Promise(resolve => { quizContainer.add( diff --git a/src/features/game/quiz/GameQuizReactionManager.ts b/src/features/game/quiz/GameQuizReactionManager.ts index 39cd5332ee..2cd99c6478 100644 --- a/src/features/game/quiz/GameQuizReactionManager.ts +++ b/src/features/game/quiz/GameQuizReactionManager.ts @@ -9,13 +9,13 @@ import DialogueRenderer from '../dialogue/GameDialogueRenderer'; //import DialogueSpeakerRenderer from '../dialogue/GameDialogueSpeakerRenderer'; import { DialogueObject } from "../dialogue/GameDialogueTypes"; import { questionTextStyle } from './GameQuizConstants'; - +import { QuizSpeakerRenderer } from './GameQuizSpeakerRenderer'; export default class GameQuizReactionManager { private dialogue: DialogueObject; private dialogueRenderer? : DialogueRenderer; private dialogueGenerator? : DialogueGenerator; - //private speakerRenderer? : DialogueSpeakerRenderer; + private speakerRenderer? : QuizSpeakerRenderer; constructor(dialogue: DialogueObject) { @@ -25,7 +25,7 @@ export default class GameQuizReactionManager { public async showReaction() : Promise { this.dialogueRenderer = new DialogueRenderer(questionTextStyle); this.dialogueGenerator = new DialogueGenerator(this.dialogue); - //this.speakerRenderer = new DialogueSpeakerRenderer(); + this.speakerRenderer = new QuizSpeakerRenderer(); GameGlobalAPI.getInstance().addToLayer( Layer.Dialogue, @@ -35,7 +35,7 @@ export default class GameQuizReactionManager { GameGlobalAPI.getInstance().fadeInLayer(Layer.Dialogue); await new Promise(resolve => this.playWholeDialogue(resolve as () => void)); this.getDialogueRenderer().destroy(); - //this.getSpeakerRenderer().changeSpeakerTo(null); + this.getSpeakerRenderer().changeSpeakerTo(null); } private async playWholeDialogue(resolve: () => void) { @@ -53,7 +53,7 @@ export default class GameQuizReactionManager { await this.getDialogueGenerator().generateNextLine(); const lineWithName = line.replace('{name}', this.getUsername()); this.getDialogueRenderer().changeText(lineWithName); - //this.getSpeakerRenderer().changeSpeakerTo(speakerDetail); + this.getSpeakerRenderer().changeSpeakerTo(speakerDetail); // Store the current line into the storage GameGlobalAPI.getInstance().storeDialogueLine(lineWithName, speakerDetail); @@ -83,6 +83,6 @@ export default class GameQuizReactionManager { private getDialogueGenerator = () => this.dialogueGenerator as DialogueGenerator; private getDialogueRenderer = () => this.dialogueRenderer as DialogueRenderer; - //private getSpeakerRenderer = () => this.speakerRenderer as DialogueSpeakerRenderer; + private getSpeakerRenderer = () => this.speakerRenderer as QuizSpeakerRenderer; public getUsername = () => SourceAcademyGame.getInstance().getAccountInfo().name; } \ No newline at end of file diff --git a/src/features/game/quiz/GameQuizSpeakerRenderer.ts b/src/features/game/quiz/GameQuizSpeakerRenderer.ts new file mode 100644 index 0000000000..73d5467d14 --- /dev/null +++ b/src/features/game/quiz/GameQuizSpeakerRenderer.ts @@ -0,0 +1,114 @@ +import ImageAssets from '../assets/ImageAssets'; +import { SpeakerDetail } from '../character/GameCharacterTypes'; +import { screenCenter, screenSize } from '../commons/CommonConstants'; +import { GamePosition, ItemId } from '../commons/CommonTypes'; +import { Layer } from '../layer/GameLayerTypes'; +import GameGlobalAPI from '../scenes/gameManager/GameGlobalAPI'; +import SourceAcademyGame from '../SourceAcademyGame'; +import StringUtils from '../utils/StringUtils'; +import { createBitmapText } from '../utils/TextUtils'; +import {QuizConstants, speakerTextStyle } from './GameQuizConstants'; + +export class QuizSpeakerRenderer { + private currentSpeakerId?: string; + private speakerSprite?:Phaser.GameObjects.Image; + private speakerSpriteBox?:Phaser.GameObjects.Container; + + /** + * Changes the speaker shown in the speaker box and the speaker rendered on screen + * + * @param newSpeakerDetail the details about the new speaker, + * including his characaterId, expression and position. + * + * Undefined - if no speaker changes are involved in the dialogue line. + * Null - if there is no speaker for the line + */ + public changeSpeakerTo(newSpeakerDetail?: SpeakerDetail | null) { + if (newSpeakerDetail === undefined) return; + + this.currentSpeakerId && + GameGlobalAPI.getInstance().clearSeveralLayers([Layer.QuizSpeaker, Layer.QuizSpeakerBox]); + this.showNewSpeaker(newSpeakerDetail); + } + + private showNewSpeaker(newSpeakerDetail: SpeakerDetail | null) { + if (newSpeakerDetail) { + this.drawSpeakerSprite(newSpeakerDetail); + this.drawSpeakerBox(newSpeakerDetail.speakerId); + } + } + + private drawSpeakerBox(speakerId: ItemId) { + if (speakerId === 'narrator') return; + const speakerContainer = + speakerId === 'you' + ? this.createSpeakerBox(this.getUsername(), GamePosition.Right) + : this.createSpeakerBox( + GameGlobalAPI.getInstance().getCharacterById(speakerId).name, + GamePosition.Left + ); + GameGlobalAPI.getInstance().addToLayer(Layer.QuizSpeakerBox, speakerContainer); + } + + private drawSpeakerSprite({ speakerId, speakerPosition, expression }: SpeakerDetail) { + this.currentSpeakerId = speakerId; + if (speakerId === 'you' || speakerId === 'narrator') { + return; + } + const speakerSprite = GameGlobalAPI.getInstance().createCharacterSprite( + speakerId, + expression, + speakerPosition + ); + this.speakerSprite = speakerSprite; + GameGlobalAPI.getInstance().addToLayer(Layer.QuizSpeaker, speakerSprite); + } + + private createSpeakerBox(text: string, position: GamePosition) { + const gameManager = GameGlobalAPI.getInstance().getGameManager(); + const container = new Phaser.GameObjects.Container(gameManager, 0, 0); + const rectangle = new Phaser.GameObjects.Image( + gameManager, + screenCenter.x, + screenCenter.y, + ImageAssets.speakerBox.key + ).setAlpha(0.8); + + const speakerText = createBitmapText( + gameManager, + '', + QuizConstants.speakerTextConfig, + speakerTextStyle + ); + + if (position === GamePosition.Right) { + rectangle.displayWidth *= -1; + speakerText.x = screenSize.x - speakerText.x; + } + + container.add([rectangle, speakerText]); + speakerText.text = StringUtils.capitalize(text); + this.speakerSpriteBox = container; + return container; + } + + /** + * Hide the speaker box and sprite + */ + public async hide() { + this.getSpeakerSprite().setVisible(false); + this.getSpeakerSpriteBox().setVisible(false); + } + + /** + * Show the hidden speaker box and sprite + */ + public async show() { + this.getSpeakerSprite().setVisible(true); + this.getSpeakerSpriteBox().setVisible(true); + } + + public getUsername = () => SourceAcademyGame.getInstance().getAccountInfo().name; + public getSpeakerSprite = () => this.speakerSprite as Phaser.GameObjects.Image; + public getSpeakerSpriteBox = () => this.speakerSpriteBox as Phaser.GameObjects.Container; +} \ No newline at end of file From 35dd94ce61fa9479d9d4f6247548c52e14233e1c Mon Sep 17 00:00:00 2001 From: reginateh Date: Sat, 23 Mar 2024 09:49:54 +0800 Subject: [PATCH 17/61] remove default reaction in QuizParser and handle no reaction situation in QuizManager --- src/features/game/parser/QuizParser.ts | 24 +++------------ src/features/game/quiz/GameQuizManager.ts | 36 +++++++++++------------ src/features/game/quiz/GameQuizType.ts | 2 +- 3 files changed, 23 insertions(+), 39 deletions(-) diff --git a/src/features/game/parser/QuizParser.ts b/src/features/game/parser/QuizParser.ts index 0a0f90ae8f..8957255374 100644 --- a/src/features/game/parser/QuizParser.ts +++ b/src/features/game/parser/QuizParser.ts @@ -1,6 +1,4 @@ -import { DialogueObject } from '../dialogue/GameDialogueTypes'; import { GameItemType } from '../location/GameMapTypes'; -import { defaultReaction } from '../quiz/GameQuizConstants'; import { Option, Question, Quiz } from '../quiz/GameQuizType'; import StringUtils from '../utils/StringUtils'; import DialogueParser from './DialogueParser'; @@ -114,33 +112,19 @@ export default class QuizParser { private static createOption( content: string[], isCorrect: boolean, - defaultReaction: boolean = false + noReaction: boolean = false ): Option { if (content.length <= 1) { - defaultReaction = true; + noReaction = true; } const option: Option = { text: content[0], - reaction: defaultReaction - ? this.createDefaultReaction(isCorrect) + reaction: noReaction + ? undefined : DialogueParser.parseQuizReaction(content.slice(1)) }; return option; } - - /** - * This function creates a Dialogue object with - * the default reactions defined in GamaQuizConstants - * - * @param isCorrect Indicates whether the correct or wrong reaction should be used - */ - private static createDefaultReaction(isCorrect: boolean): DialogueObject { - if (isCorrect) { - return DialogueParser.parseQuizReaction(defaultReaction.correct); - } else { - return DialogueParser.parseQuizReaction(defaultReaction.wrong); - } - } } const isInteger = (line: string) => new RegExp(/^[0-9]+$/).test(line); diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index dbc1a63ff2..dc96546aca 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -122,25 +122,25 @@ export default class QuizManager { ) );}); - const response = await activateQuizContainer; + const response = await activateQuizContainer; - // Animate in - quizContainer.setPosition(screenSize.x, 0); - SourceAcademyGame.getInstance().getSoundManager().playSound(SoundAssets.notifEnter.key); - scene.add.tween({ - targets: quizContainer, - alpha: 1, - ...rightSideEntryTweenProps - }); - await sleep(rightSideEntryTweenProps.duration); - - // Animate out - SourceAcademyGame.getInstance().getSoundManager().playSound(SoundAssets.notifExit.key); - scene.add.tween({ - targets: quizContainer, - alpha: 1, - ...rightSideExitTweenProps - }); + // Animate in + quizContainer.setPosition(screenSize.x, 0); + SourceAcademyGame.getInstance().getSoundManager().playSound(SoundAssets.notifEnter.key); + scene.add.tween({ + targets: quizContainer, + alpha: 1, + ...rightSideEntryTweenProps + }); + await sleep(rightSideEntryTweenProps.duration); + + // Animate out + SourceAcademyGame.getInstance().getSoundManager().playSound(SoundAssets.notifExit.key); + scene.add.tween({ + targets: quizContainer, + alpha: 1, + ...rightSideExitTweenProps + }); //await sleep(rightSideExitTweenProps.duration); fadeAndDestroy(scene, quizContainer, { fadeDuration: Constants.fadeDuration }); diff --git a/src/features/game/quiz/GameQuizType.ts b/src/features/game/quiz/GameQuizType.ts index f5970a2251..3070eca119 100644 --- a/src/features/game/quiz/GameQuizType.ts +++ b/src/features/game/quiz/GameQuizType.ts @@ -12,7 +12,7 @@ export type Question = { export type Option = { text: string; - reaction: DialogueObject; + reaction?: DialogueObject; }; export type QuizResult = { From a740601ddc2c465014698a311cb4da001a981aa8 Mon Sep 17 00:00:00 2001 From: reginateh Date: Sat, 23 Mar 2024 11:37:48 +0800 Subject: [PATCH 18/61] add a new property (boolean array) in Quiz type to record quiz result --- src/features/game/parser/QuizParser.ts | 10 +++++----- src/features/game/quiz/GameQuizManager.ts | 2 +- src/features/game/quiz/GameQuizType.ts | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/features/game/parser/QuizParser.ts b/src/features/game/parser/QuizParser.ts index 8957255374..e7efd3672b 100644 --- a/src/features/game/parser/QuizParser.ts +++ b/src/features/game/parser/QuizParser.ts @@ -37,7 +37,9 @@ export default class QuizParser { Parser.validator.registerId(quizId); const rawQuestions: Map = StringUtils.mapByHeader(quizBody, isInteger); const questions: Question[] = this.parseQuizQuestions(rawQuestions); - const quiz: Quiz = { questions: questions }; + const result: boolean[] = new Array(questions.length); + console.log(quizId + ":" + result); + const quiz: Quiz = { questions: questions, result: result}; Parser.checkpoint.map.setItemInMap(GameItemType.quizzes, quizId, quiz); } @@ -96,7 +98,7 @@ export default class QuizParser { const optionsParagraph = StringUtils.splitToParagraph(optionsText); const options: Option[] = Array(optionsParagraph.length); optionsParagraph.forEach(([header, content]: [string, string[]], index) => { - options[index] = this.createOption(content, answer === index); + options[index] = this.createOption(content); }); return options; } @@ -106,12 +108,10 @@ export default class QuizParser { * * @param content An Array of string containing an option's content, * including option text and reaction - * @param isCorrect Indicates whether this option is the correct answer - * @param [defaultReaction=false] Indicates whether this option uses the default reaction + * @param [noReaction=false] Indicates whether this option provides a reaction */ private static createOption( content: string[], - isCorrect: boolean, noReaction: boolean = false ): Option { if (content.length <= 1) { diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index dc96546aca..af71046a4c 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -101,7 +101,7 @@ export default class QuizManager { message: response.text, textConfig: QuizConstants.textConfig, bitMapTextStyle: quizOptStyle, - onUp: () => { + onUp: async () => { quizContainer.destroy(); if (index === question.answer) { quizResult.numberOfQuestions += 1; diff --git a/src/features/game/quiz/GameQuizType.ts b/src/features/game/quiz/GameQuizType.ts index 3070eca119..c0f185c345 100644 --- a/src/features/game/quiz/GameQuizType.ts +++ b/src/features/game/quiz/GameQuizType.ts @@ -2,6 +2,7 @@ import { DialogueObject } from '../dialogue/GameDialogueTypes'; export type Quiz = { questions: Question[]; + result: boolean[]; }; export type Question = { From 08911601ba9d00e2eeb220e48234c6812e980f76 Mon Sep 17 00:00:00 2001 From: reginateh Date: Wed, 27 Mar 2024 21:51:03 +0800 Subject: [PATCH 19/61] rearrange quiz result --- src/features/game/parser/QuizParser.ts | 2 +- src/features/game/quiz/GameQuizConstants.ts | 25 ++------ src/features/game/quiz/GameQuizManager.ts | 65 +++++++++------------ src/features/game/quiz/GameQuizType.ts | 6 -- 4 files changed, 36 insertions(+), 62 deletions(-) diff --git a/src/features/game/parser/QuizParser.ts b/src/features/game/parser/QuizParser.ts index e7efd3672b..5ddcf1fd9c 100644 --- a/src/features/game/parser/QuizParser.ts +++ b/src/features/game/parser/QuizParser.ts @@ -39,7 +39,7 @@ export default class QuizParser { const questions: Question[] = this.parseQuizQuestions(rawQuestions); const result: boolean[] = new Array(questions.length); console.log(quizId + ":" + result); - const quiz: Quiz = { questions: questions, result: result}; + const quiz: Quiz = { questions: questions }; Parser.checkpoint.map.setItemInMap(GameItemType.quizzes, quizId, quiz); } diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index 906a49b6cd..f3d15565f6 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -1,34 +1,21 @@ -import { DialogueLine, DialogueObject } from "../dialogue/GameDialogueTypes"; import FontAssets from '../assets/FontAssets'; import { BitmapFontStyle } from '../commons/CommonTypes'; import { Color } from '../utils/StyleUtils'; import { screenSize } from "../commons/CommonConstants"; -export const defaultReaction = { - correct: ['You are right.'], - wrong: ['Wrong answer...'] -}; +const allCorrect : string = "Well done!"; -const resultLine : DialogueLine = { - line : "good job", -} +const notAllCorrect : string = "Let's keep going!" -const resultLine2 : DialogueLine = { - line : "Need improvement. Some questions are wrong. ", +export const resultMsg = { + allCorrect: allCorrect, + notAllCorrect: notAllCorrect } -export const allCorrect : DialogueObject = new Map([ - ["all correct", [resultLine]] -]); - -export const ImproveMent : DialogueObject = new Map([ - ["incorrect", [resultLine2]] -]); - export const QuizConstants = { textPad: 250, textConfig: { x: 15, y: -15, oriX: 0.5, oriY: 0.5 }, - y: 50, + y: 100, width: 450, yInterval: 100, headerOff : 60, diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index af71046a4c..7ab8cf7b4e 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -1,6 +1,6 @@ import { ItemId } from '../commons/CommonTypes'; import GameGlobalAPI from '../scenes/gameManager/GameGlobalAPI'; -import { Question, QuizResult } from './GameQuizType'; +import { Question } from './GameQuizType'; import { QuizConstants, textStyle, quizOptStyle, questionTextStyle } from './GameQuizConstants'; import ImageAssets from '../assets/ImageAssets'; import SoundAssets from '../assets/SoundAssets'; @@ -14,9 +14,7 @@ import { fadeAndDestroy } from '../effects/FadeEffect'; import { rightSideEntryTweenProps, rightSideExitTweenProps } from '../effects/FlyEffect'; import { DialogueObject } from '../dialogue/GameDialogueTypes'; import GameQuizReactionManager from './GameQuizReactionManager'; -import { displayNotification } from '../effects/Notification'; -import GameQuizOutcomeManager from './GameQuizOutcome'; -import { ImproveMent, allCorrect } from './GameQuizConstants'; +import { resultMsg } from './GameQuizConstants'; import { createDialogueBox, createTypewriter } from '../dialogue/GameDialogueHelper'; export default class QuizManager { @@ -24,18 +22,13 @@ export default class QuizManager { // Print everything. To test if the quiz parser parses correctly. public async showQuiz(quizId:ItemId) { - const quiz = GameGlobalAPI.getInstance().getQuizById(quizId); // get a quiz - const quizResult : QuizResult = { - numberOfQuestions : 0, - allCorrect : true, - }; - - for (var i = 0; i < quiz.questions.length; i++ ) { - await this.showQuizQuestion(GameGlobalAPI.getInstance().getGameManager(), quiz.questions[i], quizResult); - //console.log("check the question displayed: " + res); + const quiz = GameGlobalAPI.getInstance().getQuizById(quizId); + const numOfQns = quiz.questions.length; + let numOfCorrect = 0; + for (let i = 0; i < quiz.questions.length; i++ ) { + numOfCorrect += await this.showQuizQuestion(GameGlobalAPI.getInstance().getGameManager(), quiz.questions[i]); } - - await this.displayFinalResult(quizResult); + await this.showResult(numOfQns, numOfCorrect); } //Display the specific quiz question @@ -55,16 +48,15 @@ export default class QuizManager { const header = new Phaser.GameObjects.Text( scene, - screenSize.x / 2 + QuizConstants.textPad - QuizConstants.width * quizPartitions / 2 - + QuizConstants.headerOff, + screenSize.x - QuizConstants.textPad, QuizConstants.y, - "options" , + "" , textStyle ).setOrigin(1.0, 0.0); const quizHeaderBg = new Phaser.GameObjects.Rectangle( scene, - screenSize.x / 2 + QuizConstants.textPad, + screenSize.x, QuizConstants.y - QuizConstants.textPad, QuizConstants.width * quizPartitions, header.getBounds().bottom * 0.5 + QuizConstants.textPad, @@ -74,8 +66,8 @@ export default class QuizManager { const quizBg = new Phaser.GameObjects.Rectangle( scene, - screenSize.x / 2 + QuizConstants.textPad, - QuizConstants.y, + screenSize.x, + QuizConstants.y - QuizConstants.textPad, QuizConstants.width * quizPartitions, quizHeaderBg.getBounds().bottom * 0.5 + (quizHeight + 0.5) * QuizConstants.yInterval, HexColor.lightBlue, @@ -101,18 +93,17 @@ export default class QuizManager { message: response.text, textConfig: QuizConstants.textConfig, bitMapTextStyle: quizOptStyle, + onUp: async () => { onUp: async () => { quizContainer.destroy(); - if (index === question.answer) { - quizResult.numberOfQuestions += 1; - resolve(this.showReaction(scene, question, choices[index].reaction, quizResult)); - } else { - quizResult.allCorrect = false; - resolve(this.showReaction(scene, question, choices[index].reaction, quizResult)); - } + const isCorrect = (index === question.answer) ? 1 : 0; + if (response.reaction) { + await this.showReaction(response.reaction); + } + resolve(isCorrect); } }).setPosition( - screenSize.x / 2 - + screenSize.x - QuizConstants.width / 2 - QuizConstants.width * (quizPartitions - Math.floor(index / 5) - 1) + QuizConstants.textPad, (buttonPositions[index][1] % (5 * QuizConstants.yInterval)) + @@ -159,13 +150,15 @@ export default class QuizManager { await this.reactionManager.showReaction(); } - private async displayFinalResult(quizResult : QuizResult) { - await displayNotification(GameGlobalAPI.getInstance().getGameManager(), "scores: " + quizResult.numberOfQuestions); - const outComeManager : GameQuizOutcomeManager = - quizResult.allCorrect ? new GameQuizOutcomeManager(allCorrect) - : new GameQuizOutcomeManager(ImproveMent); - await outComeManager.showReaction(); + private async showResult(numOfQns: number, numOfCorrect: number) { + await this.showReaction(this.makeResultMsg(numOfQns, numOfCorrect)); } - + private makeResultMsg(numOfQns: number, numOfCorrect: number): DialogueObject { + let line = `You got ${ numOfCorrect } out of ${numOfQns} questions correct! `; + line += (numOfCorrect === numOfQns ? resultMsg.allCorrect : resultMsg.notAllCorrect); + return new Map([ + ["0", [{line: line}]] + ]); + } } diff --git a/src/features/game/quiz/GameQuizType.ts b/src/features/game/quiz/GameQuizType.ts index c0f185c345..3bc217f975 100644 --- a/src/features/game/quiz/GameQuizType.ts +++ b/src/features/game/quiz/GameQuizType.ts @@ -2,7 +2,6 @@ import { DialogueObject } from '../dialogue/GameDialogueTypes'; export type Quiz = { questions: Question[]; - result: boolean[]; }; export type Question = { @@ -15,8 +14,3 @@ export type Option = { text: string; reaction?: DialogueObject; }; - -export type QuizResult = { - numberOfQuestions: number; - allCorrect : boolean; -} \ No newline at end of file From fe704ae30be45edb232e716ccbbb43d8766e2ecd Mon Sep 17 00:00:00 2001 From: reginateh Date: Thu, 28 Mar 2024 01:47:09 +0800 Subject: [PATCH 20/61] minor changes & comment --- src/features/game/quiz/GameQuizManager.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 7ab8cf7b4e..c9fe887ab5 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -150,12 +150,23 @@ export default class QuizManager { await this.reactionManager.showReaction(); } + /** + * Show the final score of the quiz as a quiz reaction. + * @param numOfQns The number of questions of the quiz. + * @param numOfCorrect The number of correctly answered questions. + */ private async showResult(numOfQns: number, numOfCorrect: number) { await this.showReaction(this.makeResultMsg(numOfQns, numOfCorrect)); } + /** + * Create DialogueObject containing the message of quiz score. + * @param numOfQns The number of questions of the quiz. + * @param numOfCorrect The number of correctly answered questions. + * @returns A DialogueObject containing the message of quiz score. + */ private makeResultMsg(numOfQns: number, numOfCorrect: number): DialogueObject { - let line = `You got ${ numOfCorrect } out of ${numOfQns} questions correct! `; + let line = `You got ${ numOfCorrect } out of ${numOfQns} questions correct. `; line += (numOfCorrect === numOfQns ? resultMsg.allCorrect : resultMsg.notAllCorrect); return new Map([ ["0", [{line: line}]] From 112b8a372186c40746b4e4d8cab26d9631ec0c75 Mon Sep 17 00:00:00 2001 From: reginateh Date: Fri, 29 Mar 2024 11:24:00 +0800 Subject: [PATCH 21/61] remove redundant file and console logs --- src/features/game/action/GameActionExecuter.ts | 1 - src/features/game/parser/QuizParser.ts | 2 -- src/features/game/quiz/GameQuizOutcome.ts | 7 ------- 3 files changed, 10 deletions(-) delete mode 100644 src/features/game/quiz/GameQuizOutcome.ts diff --git a/src/features/game/action/GameActionExecuter.ts b/src/features/game/action/GameActionExecuter.ts index 7cffab691c..6bfa89915b 100644 --- a/src/features/game/action/GameActionExecuter.ts +++ b/src/features/game/action/GameActionExecuter.ts @@ -103,7 +103,6 @@ export default class GameActionExecuter { await sleep(actionParams.duration); return; case GameActionType.ShowQuiz: - //alert("action type showQuiz is caught"); await GameGlobalAPI.getInstance().getGameManager() .getDialogueManager().hideAll(); await globalAPI.showQuiz(actionParams.id); diff --git a/src/features/game/parser/QuizParser.ts b/src/features/game/parser/QuizParser.ts index 5ddcf1fd9c..79d5f7a51d 100644 --- a/src/features/game/parser/QuizParser.ts +++ b/src/features/game/parser/QuizParser.ts @@ -37,8 +37,6 @@ export default class QuizParser { Parser.validator.registerId(quizId); const rawQuestions: Map = StringUtils.mapByHeader(quizBody, isInteger); const questions: Question[] = this.parseQuizQuestions(rawQuestions); - const result: boolean[] = new Array(questions.length); - console.log(quizId + ":" + result); const quiz: Quiz = { questions: questions }; Parser.checkpoint.map.setItemInMap(GameItemType.quizzes, quizId, quiz); } diff --git a/src/features/game/quiz/GameQuizOutcome.ts b/src/features/game/quiz/GameQuizOutcome.ts deleted file mode 100644 index 4d15a31f9c..0000000000 --- a/src/features/game/quiz/GameQuizOutcome.ts +++ /dev/null @@ -1,7 +0,0 @@ -import GameQuizReactionManager from './GameQuizReactionManager'; -import { DialogueObject } from '../dialogue/GameDialogueTypes'; -export default class GameQuizOutcomeManager extends GameQuizReactionManager { - constructor(dialogueObj : DialogueObject) { - super(dialogueObj); - } -} \ No newline at end of file From 0515c9c43964466334b2c0f251c536a3600a0724 Mon Sep 17 00:00:00 2001 From: reginateh Date: Fri, 29 Mar 2024 11:25:46 +0800 Subject: [PATCH 22/61] save quiz status as attempted/completed arrays --- src/features/game/quiz/GameQuizManager.ts | 2 + src/features/game/save/GameSaveHelper.ts | 8 ++- src/features/game/save/GameSaveManager.ts | 2 + src/features/game/save/GameSaveTypes.ts | 4 ++ .../game/scenes/gameManager/GameGlobalAPI.ts | 17 +++++ src/features/game/state/GameStateManager.ts | 70 +++++++++++++++++++ src/features/game/state/GameStateTypes.ts | 4 +- 7 files changed, 104 insertions(+), 3 deletions(-) diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index c9fe887ab5..73a522b30c 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -28,6 +28,8 @@ export default class QuizManager { for (let i = 0; i < quiz.questions.length; i++ ) { numOfCorrect += await this.showQuizQuestion(GameGlobalAPI.getInstance().getGameManager(), quiz.questions[i]); } + GameGlobalAPI.getInstance().attemptQuiz(quizId); + if (numOfCorrect === numOfQns) GameGlobalAPI.getInstance().completeQuiz(quizId); await this.showResult(numOfQns, numOfCorrect); } diff --git a/src/features/game/save/GameSaveHelper.ts b/src/features/game/save/GameSaveHelper.ts index 307a39873b..4474f253a8 100644 --- a/src/features/game/save/GameSaveHelper.ts +++ b/src/features/game/save/GameSaveHelper.ts @@ -34,7 +34,9 @@ export function gameStateToJson( completedTasks: gameStateManager.getCompletedTasks(), completedObjectives: gameStateManager.getCompletedObjectives(), triggeredInteractions: gameStateManager.getTriggeredInteractions(), - triggeredStateChangeActions: gameStateManager.getTriggeredStateChangeActions() + triggeredStateChangeActions: gameStateManager.getTriggeredStateChangeActions(), + attemptedQuizzes: gameStateManager.getAttemptedQuizzes(), + completedQuizzes: gameStateManager.getCompletedQuizzes() } }, userSaveState: { @@ -80,7 +82,9 @@ export const createEmptyGameSaveState = (): GameSaveState => { completedTasks: [], completedObjectives: [], triggeredInteractions: [], - triggeredStateChangeActions: [] + triggeredStateChangeActions: [], + attemptedQuizzes: [], + completedQuizzes: [] }; }; diff --git a/src/features/game/save/GameSaveManager.ts b/src/features/game/save/GameSaveManager.ts index 8932827064..1313a0ee52 100644 --- a/src/features/game/save/GameSaveManager.ts +++ b/src/features/game/save/GameSaveManager.ts @@ -150,6 +150,8 @@ export default class GameSaveManager { public getIncompleteTasks = () => this.getGameSaveState().incompleteTasks; public getLoadedPhase = () => this.getGameSaveState().currentPhase; public getChapterNewlyCompleted = () => this.getGameSaveState().chapterNewlyCompleted; + public getAttemptedQuizzes = () => this.getGameSaveState().attemptedQuizzes; + public getCompletedQuizzes = () => this.getGameSaveState().completedQuizzes; public getChapterNum = () => mandatory(this.chapterNum); public getCheckpointNum = () => mandatory(this.checkpointNum); diff --git a/src/features/game/save/GameSaveTypes.ts b/src/features/game/save/GameSaveTypes.ts index 6eba2ff487..b353bef539 100644 --- a/src/features/game/save/GameSaveTypes.ts +++ b/src/features/game/save/GameSaveTypes.ts @@ -20,6 +20,8 @@ export type FullSaveState = { * @prop {string[]} completedObjectives - list of objectives that have been completed by player * @prop {string[]} triggeredInteractions - list of itemIds that have been triggered by player * @prop {string[]} triggeredActions - list of actions that have been triggered by player + * @prop {string[]} attemptedQuizzes - list of quiz ids that have been attempted by player + * @prop {string[]} completedQuizzes - list of quiz ids that have been completed by player */ export type GameSaveState = { lastCheckpointPlayed: number; @@ -32,6 +34,8 @@ export type GameSaveState = { completedObjectives: string[]; triggeredInteractions: string[]; triggeredStateChangeActions: string[]; + attemptedQuizzes: string[]; + completedQuizzes: string[]; }; /** diff --git a/src/features/game/scenes/gameManager/GameGlobalAPI.ts b/src/features/game/scenes/gameManager/GameGlobalAPI.ts index 3f114a7f24..bea71b3a65 100644 --- a/src/features/game/scenes/gameManager/GameGlobalAPI.ts +++ b/src/features/game/scenes/gameManager/GameGlobalAPI.ts @@ -514,9 +514,26 @@ class GameGlobalAPI { ///////////////////// // Game Quiz // ///////////////////// + public async showQuiz(quizId: ItemId) { await this.getGameManager().getQuizManager().showQuiz(quizId); } + + public isQuizAttempted(key: string): boolean { + return this.getGameManager().getStateManager().isQuizAttempted(key); + } + + public isQuizComplete(key: string): boolean { + return this.getGameManager().getStateManager().isQuizComplete(key); + } + + public completeQuiz(key: string): void { + this.getGameManager().getStateManager().completeQuiz(key); + } + + public attemptQuiz(key: string): void { + this.getGameManager().getStateManager().attemptQuiz(key); + } } export default GameGlobalAPI; diff --git a/src/features/game/state/GameStateManager.ts b/src/features/game/state/GameStateManager.ts index 5cb0ecc1ce..c6b7b7a253 100644 --- a/src/features/game/state/GameStateManager.ts +++ b/src/features/game/state/GameStateManager.ts @@ -33,6 +33,8 @@ class GameStateManager { private checkpointObjective: GameObjective; private checkpointTask: GameTask; private chapterNewlyCompleted: boolean; + private completedQuizzes: ItemId[]; + private attemptedQuizzes: ItemId[]; // Triggered Interactions private updatedLocations: Set; @@ -46,6 +48,8 @@ class GameStateManager { this.checkpointObjective = gameCheckpoint.objectives; this.checkpointTask = gameCheckpoint.tasks; this.chapterNewlyCompleted = false; + this.attemptedQuizzes = []; + this.completedQuizzes = []; this.updatedLocations = new Set(this.gameMap.getLocationIds()); this.triggeredInteractions = new Map(); @@ -82,6 +86,8 @@ class GameStateManager { this.checkpointTask.showTask(task); }); + this.completedQuizzes = this.getSaveManager().getCompletedQuizzes(); + this.attemptedQuizzes = this.getSaveManager().getAttemptedQuizzes(); this.chapterNewlyCompleted = this.getSaveManager().getChapterNewlyCompleted(); } @@ -447,6 +453,52 @@ class GameStateManager { return this.checkpointTask.getAllVisibleTaskData(); } + /////////////////////////////// + // Quiz // + /////////////////////////////// + + /** + * Checks whether a specific quiz has been completed. + * + * @param key quiz id + * @returns {boolean} + */ + public isQuizComplete(quizId: string): boolean { + return this.completedQuizzes.includes(quizId); + } + + /** + * Checks whether a specific quiz has been attempted. + * + * @param key quiz id + * @returns {boolean} + */ + public isQuizAttempted(quizId: string): boolean { + return this.attemptedQuizzes.includes(quizId); + } + + /** + * Record that a quiz has been completed. + * A quiz is completed when its maximum score is obtained. + * + * @param key task id + */ + public completeQuiz(quizId: string) { + if (!this.completedQuizzes.includes(quizId)) this.completedQuizzes.push(quizId); + console.log(this.completedQuizzes); + } + + /** + * Record that a quiz has been attempted. + * A quiz is attempted when user answers all questions. + * + * @param key task id + */ + public attemptQuiz(quizId: string) { + if (!this.attemptedQuizzes.includes(quizId)) this.attemptedQuizzes.push(quizId); + console.log(this.attemptedQuizzes); + } + /////////////////////////////// // Saving // /////////////////////////////// @@ -497,6 +549,24 @@ class GameStateManager { return this.triggeredStateChangeActions; } + /** + * Gets array of all quizzes that have been completed. + * + * @returns {ItemId[]} + */ + public getCompletedQuizzes(): ItemId[] { + return this.completedQuizzes; + } + + /** + * Gets array of all quizzes that have been attempted. + * + * @returns {ItemId[]} + */ + public getAttemptedQuizzes(): ItemId[] { + return this.attemptedQuizzes; + } + public getGameMap = () => this.gameMap; public getCharacterAtId = (id: ItemId) => mandatory(this.gameMap.getCharacterMap().get(id)); diff --git a/src/features/game/state/GameStateTypes.ts b/src/features/game/state/GameStateTypes.ts index afb85031e5..451df28144 100644 --- a/src/features/game/state/GameStateTypes.ts +++ b/src/features/game/state/GameStateTypes.ts @@ -3,7 +3,9 @@ import { ItemId } from '../commons/CommonTypes'; export enum GameStateStorage { UserState = 'UserState', ChecklistState = 'ChecklistState', - TasklistState = 'TasklistState' + TasklistState = 'TasklistState', + AttemptedQuizState = 'AttemptedQuizState', + CompletedQuizState = 'CompletedQuizState' } /** From 60c8c0063b72f6085984f5e831794a83fa6bedd5 Mon Sep 17 00:00:00 2001 From: reginateh Date: Fri, 29 Mar 2024 11:26:36 +0800 Subject: [PATCH 23/61] add conditions to check whether a quiz is attempted/completed --- .../game/action/GameActionConditionChecker.ts | 4 ++++ src/features/game/parser/ConditionParser.ts | 19 +++++++++++++++++++ src/features/game/parser/ParserConverter.ts | 4 +++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/features/game/action/GameActionConditionChecker.ts b/src/features/game/action/GameActionConditionChecker.ts index f76f1e4a0f..d66aa3ce63 100644 --- a/src/features/game/action/GameActionConditionChecker.ts +++ b/src/features/game/action/GameActionConditionChecker.ts @@ -42,6 +42,10 @@ export default class ActionConditionChecker { return GameGlobalAPI.getInstance().isObjectiveComplete(conditionParams.id) === boolean; case GameStateStorage.TasklistState: return GameGlobalAPI.getInstance().isTaskComplete(conditionParams.id) === boolean; + case GameStateStorage.AttemptedQuizState: + return GameGlobalAPI.getInstance().isQuizAttempted(conditionParams.id) === boolean; + case GameStateStorage.CompletedQuizState: + return GameGlobalAPI.getInstance().isQuizComplete(conditionParams.id) === boolean; default: return true; } diff --git a/src/features/game/parser/ConditionParser.ts b/src/features/game/parser/ConditionParser.ts index 3529025229..16cc870ba8 100644 --- a/src/features/game/parser/ConditionParser.ts +++ b/src/features/game/parser/ConditionParser.ts @@ -51,6 +51,25 @@ export default class ConditionParser { }, boolean: !hasExclamation }; + + case GameStateStorage.AttemptedQuizState: + return { + state: GameStateStorage.AttemptedQuizState, + conditionParams: { + id: condParams[0] + }, + boolean: !hasExclamation + }; + + case GameStateStorage.CompletedQuizState: + return { + state: GameStateStorage.CompletedQuizState, + conditionParams: { + id: condParams[0] + }, + boolean: !hasExclamation + }; + default: throw new Error('Parsing error: Invalid condition param'); } diff --git a/src/features/game/parser/ParserConverter.ts b/src/features/game/parser/ParserConverter.ts index fbb491d9ec..0e4752dd1a 100644 --- a/src/features/game/parser/ParserConverter.ts +++ b/src/features/game/parser/ParserConverter.ts @@ -66,7 +66,9 @@ const stringToActionTypeMap = { const stringToGameStateStorageMap = { checklist: GameStateStorage.ChecklistState, tasklist: GameStateStorage.TasklistState, - userstate: GameStateStorage.UserState + userstate: GameStateStorage.UserState, + attempted: GameStateStorage.AttemptedQuizState, + completed: GameStateStorage.CompletedQuizState }; const stringToUserStateTypeMap = { From 8ac29d535e6a9e4b8ff204f0aca1aa8fb17fe6fd Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Mon, 1 Apr 2024 18:17:27 +0800 Subject: [PATCH 24/61] modify UI again --- src/features/game/quiz/GameQuizConstants.ts | 4 ++-- src/features/game/quiz/GameQuizManager.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index f3d15565f6..ef9f53e6c0 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -13,7 +13,7 @@ export const resultMsg = { } export const QuizConstants = { - textPad: 250, + textPad: 10, textConfig: { x: 15, y: -15, oriX: 0.5, oriY: 0.5 }, y: 100, width: 450, @@ -28,7 +28,7 @@ export const textStyle = { fill: Color.offWhite, align: 'left', lineSpacing: 10, - wordWrap: { width: 60 } + wordWrap: { width: QuizConstants.width - QuizConstants.textPad * 260 } }; export const questionTextStyle = { diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 73a522b30c..ec1d36c8ff 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -109,8 +109,7 @@ export default class QuizManager { QuizConstants.width / 2 - QuizConstants.width * (quizPartitions - Math.floor(index / 5) - 1) + QuizConstants.textPad, (buttonPositions[index][1] % (5 * QuizConstants.yInterval)) + - quizHeaderBg.getBounds().bottom + - 75 + quizHeaderBg.getBounds().bottom + 75 ) ) );}); From 116e0f3d472555d750ae7545efafeb47509498c4 Mon Sep 17 00:00:00 2001 From: reginateh Date: Mon, 1 Apr 2024 14:41:08 +0800 Subject: [PATCH 25/61] disable keyboard input in a quiz --- src/features/game/action/GameActionExecuter.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/features/game/action/GameActionExecuter.ts b/src/features/game/action/GameActionExecuter.ts index 6bfa89915b..dff127ebe0 100644 --- a/src/features/game/action/GameActionExecuter.ts +++ b/src/features/game/action/GameActionExecuter.ts @@ -103,11 +103,11 @@ export default class GameActionExecuter { await sleep(actionParams.duration); return; case GameActionType.ShowQuiz: - await GameGlobalAPI.getInstance().getGameManager() - .getDialogueManager().hideAll(); + globalAPI.enableKeyboardInput(false); + await globalAPI.getGameManager().getDialogueManager().hideAll(); await globalAPI.showQuiz(actionParams.id); - await GameGlobalAPI.getInstance().getGameManager() - .getDialogueManager().showAll(); + await globalAPI.getGameManager().getDialogueManager().showAll(); + globalAPI.enableKeyboardInput(true); return; default: return; From 4a9b71a1dd6ed58b07e052b633426a96a36b5fda Mon Sep 17 00:00:00 2001 From: reginateh Date: Mon, 1 Apr 2024 14:51:11 +0800 Subject: [PATCH 26/61] move question prompt text to QuizConstants --- src/features/game/quiz/GameQuizConstants.ts | 8 +++----- src/features/game/quiz/GameQuizManager.ts | 9 +++++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index ef9f53e6c0..c1982f31bd 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -3,13 +3,11 @@ import { BitmapFontStyle } from '../commons/CommonTypes'; import { Color } from '../utils/StyleUtils'; import { screenSize } from "../commons/CommonConstants"; -const allCorrect : string = "Well done!"; - -const notAllCorrect : string = "Let's keep going!" +export const questionPrompt = "What is the correct answer?"; export const resultMsg = { - allCorrect: allCorrect, - notAllCorrect: notAllCorrect + allCorrect: "Well done!", + notAllCorrect: "Let's keep going!" } export const QuizConstants = { diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index ec1d36c8ff..2d39065c87 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -1,7 +1,7 @@ import { ItemId } from '../commons/CommonTypes'; import GameGlobalAPI from '../scenes/gameManager/GameGlobalAPI'; import { Question } from './GameQuizType'; -import { QuizConstants, textStyle, quizOptStyle, questionTextStyle } from './GameQuizConstants'; +import { QuizConstants, textStyle, quizOptStyle, questionTextStyle, resultMsg, questionPrompt } from './GameQuizConstants'; import ImageAssets from '../assets/ImageAssets'; import SoundAssets from '../assets/SoundAssets'; import { Constants, screenSize } from '../commons/CommonConstants'; @@ -14,7 +14,6 @@ import { fadeAndDestroy } from '../effects/FadeEffect'; import { rightSideEntryTweenProps, rightSideExitTweenProps } from '../effects/FlyEffect'; import { DialogueObject } from '../dialogue/GameDialogueTypes'; import GameQuizReactionManager from './GameQuizReactionManager'; -import { resultMsg } from './GameQuizConstants'; import { createDialogueBox, createTypewriter } from '../dialogue/GameDialogueHelper'; export default class QuizManager { @@ -34,7 +33,9 @@ export default class QuizManager { } //Display the specific quiz question - public async showQuizQuestion(scene: Phaser.Scene, question: Question, quizResult : QuizResult){ + public async showQuizQuestion(scene: Phaser.Scene, question: Question){ + + GameGlobalAPI.getInstance().getGameManager().getPhaseManager().isCurrentPhaseTerminal(); const choices = question.options; const quizContainer = new Phaser.GameObjects.Container(scene, 0, 0); @@ -52,7 +53,7 @@ export default class QuizManager { scene, screenSize.x - QuizConstants.textPad, QuizConstants.y, - "" , + questionPrompt, textStyle ).setOrigin(1.0, 0.0); From e47a20a507c3cab867b7c2ddd145531bd6a3d8f1 Mon Sep 17 00:00:00 2001 From: reginateh Date: Mon, 1 Apr 2024 15:37:07 +0800 Subject: [PATCH 27/61] QuizParser error handling & add new "speaker" property to QuizType & store quiz questions to dialogue log --- src/features/game/parser/QuizParser.ts | 25 +++++++++++++++-------- src/features/game/quiz/GameQuizManager.ts | 1 + src/features/game/quiz/GameQuizType.ts | 2 ++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/features/game/parser/QuizParser.ts b/src/features/game/parser/QuizParser.ts index 79d5f7a51d..25a8e87b31 100644 --- a/src/features/game/parser/QuizParser.ts +++ b/src/features/game/parser/QuizParser.ts @@ -3,6 +3,7 @@ import { Option, Question, Quiz } from '../quiz/GameQuizType'; import StringUtils from '../utils/StringUtils'; import DialogueParser from './DialogueParser'; import Parser from './Parser'; +import SpeakerParser from './SpeakerParser'; /** * This class parses quizzes and creates Quiz Objects @@ -51,8 +52,7 @@ export default class QuizParser { private static parseQuizQuestions(map: Map): Question[] { const questions: Question[] = new Array(map.size); map.forEach((value: string[], key: string) => { - const question: Question = this.createQuestion(value); - questions[parseInt(key)] = question; + questions[parseInt(key)] = this.createQuestion(value); }); return questions; } @@ -65,11 +65,14 @@ export default class QuizParser { * containing question text, correct answer, and options */ private static createQuestion(questionText: string[]): Question { - const ans = this.getQuizAnswer(questionText[1]); + if (questionText.length < 2) { + throw new Error("Parsing error: Quiz missing question or answer"); + } const question: Question = { question: questionText[0], - answer: ans, - options: this.parseOptions(questionText.slice(2), ans) + speaker: SpeakerParser.parse('@narrator'), + answer: this.getQuizAnswer(questionText[1]), + options: this.parseOptions(questionText.slice(2)) }; return question; } @@ -81,7 +84,11 @@ export default class QuizParser { * @param answer The string containing the correct answer of a question */ private static getQuizAnswer(answer: string): Number { - return parseInt(answer.split(':')[1]); + const ans = answer.split(':'); + if (ans.length < 2 || Number.isNaN(parseInt(ans[1]))) { + throw new Error("Parsing error: Invalid answer for Quiz"); + } + return parseInt(ans[1]); } /** @@ -90,9 +97,8 @@ export default class QuizParser { * * @param optionsText An Array of string containing all options' content, * including option text and reactions - * @param answer The correct answer of the corresponding question */ - private static parseOptions(optionsText: string[], answer: Number): Option[] { + private static parseOptions(optionsText: string[]): Option[] { const optionsParagraph = StringUtils.splitToParagraph(optionsText); const options: Option[] = Array(optionsParagraph.length); optionsParagraph.forEach(([header, content]: [string, string[]], index) => { @@ -112,6 +118,9 @@ export default class QuizParser { content: string[], noReaction: boolean = false ): Option { + if (!content) { + throw new Error("Parsing error: Quiz option not provided") + } if (content.length <= 1) { noReaction = true; } diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 2d39065c87..f9ae4290a6 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -48,6 +48,7 @@ export default class QuizManager { const quizQuestionWriter = createTypewriter(scene, questionTextStyle); quizQuestionWriter.changeLine(question.question); + GameGlobalAPI.getInstance().storeDialogueLine(question.question, question.speaker); const header = new Phaser.GameObjects.Text( scene, diff --git a/src/features/game/quiz/GameQuizType.ts b/src/features/game/quiz/GameQuizType.ts index 3bc217f975..faa16c3494 100644 --- a/src/features/game/quiz/GameQuizType.ts +++ b/src/features/game/quiz/GameQuizType.ts @@ -1,3 +1,4 @@ +import { SpeakerDetail } from '../character/GameCharacterTypes'; import { DialogueObject } from '../dialogue/GameDialogueTypes'; export type Quiz = { @@ -6,6 +7,7 @@ export type Quiz = { export type Question = { question: string; + speaker: SpeakerDetail; answer: Number; options: Option[]; }; From 3f93602fc9711449be6463804ae223d380a92140 Mon Sep 17 00:00:00 2001 From: reginateh Date: Mon, 1 Apr 2024 15:37:22 +0800 Subject: [PATCH 28/61] remove console log --- src/features/game/state/GameStateManager.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/features/game/state/GameStateManager.ts b/src/features/game/state/GameStateManager.ts index c6b7b7a253..b1a09df0c0 100644 --- a/src/features/game/state/GameStateManager.ts +++ b/src/features/game/state/GameStateManager.ts @@ -485,7 +485,6 @@ class GameStateManager { */ public completeQuiz(quizId: string) { if (!this.completedQuizzes.includes(quizId)) this.completedQuizzes.push(quizId); - console.log(this.completedQuizzes); } /** @@ -496,7 +495,6 @@ class GameStateManager { */ public attemptQuiz(quizId: string) { if (!this.attemptedQuizzes.includes(quizId)) this.attemptedQuizzes.push(quizId); - console.log(this.attemptedQuizzes); } /////////////////////////////// From 2d6573e6b45074b908bca4ecf1d91918ed87b19d Mon Sep 17 00:00:00 2001 From: reginateh Date: Mon, 1 Apr 2024 15:46:49 +0800 Subject: [PATCH 29/61] display quiz result message to dialogue log --- src/features/game/quiz/GameQuizManager.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index f9ae4290a6..8977b65b4c 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -15,6 +15,7 @@ import { rightSideEntryTweenProps, rightSideExitTweenProps } from '../effects/Fl import { DialogueObject } from '../dialogue/GameDialogueTypes'; import GameQuizReactionManager from './GameQuizReactionManager'; import { createDialogueBox, createTypewriter } from '../dialogue/GameDialogueHelper'; +import { SpeakerDetail } from '../character/GameCharacterTypes'; export default class QuizManager { private reactionManager? : GameQuizReactionManager; @@ -23,13 +24,16 @@ export default class QuizManager { public async showQuiz(quizId:ItemId) { const quiz = GameGlobalAPI.getInstance().getQuizById(quizId); const numOfQns = quiz.questions.length; + if (numOfQns === 0) { + return; + } let numOfCorrect = 0; - for (let i = 0; i < quiz.questions.length; i++ ) { + for (let i = 0; i < numOfQns; i++) { numOfCorrect += await this.showQuizQuestion(GameGlobalAPI.getInstance().getGameManager(), quiz.questions[i]); } GameGlobalAPI.getInstance().attemptQuiz(quizId); if (numOfCorrect === numOfQns) GameGlobalAPI.getInstance().completeQuiz(quizId); - await this.showResult(numOfQns, numOfCorrect); + await this.showResult(numOfQns, numOfCorrect, quiz.questions[0].speaker); } //Display the specific quiz question @@ -158,8 +162,8 @@ export default class QuizManager { * @param numOfQns The number of questions of the quiz. * @param numOfCorrect The number of correctly answered questions. */ - private async showResult(numOfQns: number, numOfCorrect: number) { - await this.showReaction(this.makeResultMsg(numOfQns, numOfCorrect)); + private async showResult(numOfQns: number, numOfCorrect: number, speaker: SpeakerDetail) { + await this.showReaction(this.makeResultMsg(numOfQns, numOfCorrect, speaker)); } /** @@ -168,11 +172,11 @@ export default class QuizManager { * @param numOfCorrect The number of correctly answered questions. * @returns A DialogueObject containing the message of quiz score. */ - private makeResultMsg(numOfQns: number, numOfCorrect: number): DialogueObject { + private makeResultMsg(numOfQns: number, numOfCorrect: number, speaker: SpeakerDetail): DialogueObject { let line = `You got ${ numOfCorrect } out of ${numOfQns} questions correct. `; line += (numOfCorrect === numOfQns ? resultMsg.allCorrect : resultMsg.notAllCorrect); return new Map([ - ["0", [{line: line}]] + ["0", [{line: line, speakerDetail: speaker}]] ]); } } From 55d4c54191ac553cafa8392c5528b280d90ced5f Mon Sep 17 00:00:00 2001 From: reginateh Date: Mon, 1 Apr 2024 17:44:20 +0800 Subject: [PATCH 30/61] format code --- .../game/dialogue/GameDialogueManager.ts | 10 +- src/features/game/parser/QuizParser.ts | 15 +- src/features/game/quiz/GameQuizConstants.ts | 62 ++--- src/features/game/quiz/GameQuizManager.ts | 261 +++++++++--------- .../game/quiz/GameQuizReactionManager.ts | 137 +++++---- .../game/quiz/GameQuizSpeakerRenderer.ts | 14 +- 6 files changed, 252 insertions(+), 247 deletions(-) diff --git a/src/features/game/dialogue/GameDialogueManager.ts b/src/features/game/dialogue/GameDialogueManager.ts index e2d8065e8f..b922f92ea0 100644 --- a/src/features/game/dialogue/GameDialogueManager.ts +++ b/src/features/game/dialogue/GameDialogueManager.ts @@ -33,8 +33,6 @@ export default class DialogueManager { public async showDialogue(dialogueId: ItemId): Promise { const dialogue = GameGlobalAPI.getInstance().getDialogueById(dialogueId); - - this.dialogueRenderer = new DialogueRenderer(textTypeWriterStyle); this.dialogueGenerator = new DialogueGenerator(dialogue.content); this.speakerRenderer = new DialogueSpeakerRenderer(); @@ -55,7 +53,8 @@ export default class DialogueManager { // add keyboard listener for dialogue box this.getInputManager().registerKeyboardListener(keyboardShortcuts.Next, 'up', async () => { // show the next line if dashboard or escape menu are not displayed - if ( !GameGlobalAPI.getInstance().getGameManager().getPhaseManager().isCurrentPhaseTerminal() + if ( + !GameGlobalAPI.getInstance().getGameManager().getPhaseManager().isCurrentPhaseTerminal() ) { await this.showNextLine(resolve); } @@ -74,15 +73,14 @@ export default class DialogueManager { const lineWithName = line.replace('{name}', this.getUsername()); this.getDialogueRenderer().changeText(lineWithName); this.getSpeakerRenderer().changeSpeakerTo(speakerDetail); - - console.log(GameGlobalAPI.getInstance().getGameManager().getPhaseManager().isCurrentPhaseTerminal()); + // Store the current line into the storage GameGlobalAPI.getInstance().storeDialogueLine(lineWithName, speakerDetail); // Disable interactions while processing actions GameGlobalAPI.getInstance().enableSprite(this.getDialogueRenderer().getDialogueBox(), false); this.getInputManager().enableKeyboardInput(false); - + if (prompt) { // disable keyboard input to prevent continue dialogue this.getInputManager().enableKeyboardInput(false); diff --git a/src/features/game/parser/QuizParser.ts b/src/features/game/parser/QuizParser.ts index 25a8e87b31..485af8ff9c 100644 --- a/src/features/game/parser/QuizParser.ts +++ b/src/features/game/parser/QuizParser.ts @@ -66,7 +66,7 @@ export default class QuizParser { */ private static createQuestion(questionText: string[]): Question { if (questionText.length < 2) { - throw new Error("Parsing error: Quiz missing question or answer"); + throw new Error('Parsing error: Quiz missing question or answer'); } const question: Question = { question: questionText[0], @@ -86,7 +86,7 @@ export default class QuizParser { private static getQuizAnswer(answer: string): Number { const ans = answer.split(':'); if (ans.length < 2 || Number.isNaN(parseInt(ans[1]))) { - throw new Error("Parsing error: Invalid answer for Quiz"); + throw new Error('Parsing error: Invalid answer for Quiz'); } return parseInt(ans[1]); } @@ -114,21 +114,16 @@ export default class QuizParser { * including option text and reaction * @param [noReaction=false] Indicates whether this option provides a reaction */ - private static createOption( - content: string[], - noReaction: boolean = false - ): Option { + private static createOption(content: string[], noReaction: boolean = false): Option { if (!content) { - throw new Error("Parsing error: Quiz option not provided") + throw new Error('Parsing error: Quiz option not provided'); } if (content.length <= 1) { noReaction = true; } const option: Option = { text: content[0], - reaction: noReaction - ? undefined - : DialogueParser.parseQuizReaction(content.slice(1)) + reaction: noReaction ? undefined : DialogueParser.parseQuizReaction(content.slice(1)) }; return option; } diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index c1982f31bd..faf57338b6 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -1,48 +1,48 @@ import FontAssets from '../assets/FontAssets'; +import { screenSize } from '../commons/CommonConstants'; import { BitmapFontStyle } from '../commons/CommonTypes'; import { Color } from '../utils/StyleUtils'; -import { screenSize } from "../commons/CommonConstants"; -export const questionPrompt = "What is the correct answer?"; +export const questionPrompt = 'What is the correct answer?'; export const resultMsg = { - allCorrect: "Well done!", + allCorrect: 'Well done!', notAllCorrect: "Let's keep going!" -} +}; export const QuizConstants = { - textPad: 10, - textConfig: { x: 15, y: -15, oriX: 0.5, oriY: 0.5 }, - y: 100, - width: 450, - yInterval: 100, - headerOff : 60, - speakerTextConfig: { x: 320, y: 745, oriX: 0.5, oriY: 0.5 } + textPad: 20, + textConfig: { x: 15, y: -15, oriX: 0.5, oriY: 0.5 }, + y: 100, + width: 450, + yInterval: 100, + headerOff: 60, + speakerTextConfig: { x: 320, y: 745, oriX: 0.5, oriY: 0.5 } }; - + export const textStyle = { - fontFamily: 'Verdana', - fontSize: '30px', - fill: Color.offWhite, - align: 'left', - lineSpacing: 10, - wordWrap: { width: QuizConstants.width - QuizConstants.textPad * 260 } - }; + fontFamily: 'Verdana', + fontSize: '25px', + fill: Color.offWhite, + align: 'left', + lineSpacing: 10, + wordWrap: { width: QuizConstants.width - QuizConstants.textPad * 2 } +}; export const questionTextStyle = { - fontFamily: 'Verdana', - fontSize: '30px', - fill: Color.lightBlue, - align: 'left', - lineSpacing: 10, - wordWrap: { width: screenSize.x - 240 } - }; - + fontFamily: 'Verdana', + fontSize: '30px', + fill: Color.lightBlue, + align: 'left', + lineSpacing: 10, + wordWrap: { width: screenSize.x - 240 } +}; + export const quizOptStyle: BitmapFontStyle = { - key: FontAssets.zektonFont.key, - size: 25, - align: Phaser.GameObjects.BitmapText.ALIGN_CENTER - }; + key: FontAssets.zektonFont.key, + size: 25, + align: Phaser.GameObjects.BitmapText.ALIGN_CENTER +}; diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 8977b65b4c..ee26832a59 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -1,27 +1,33 @@ -import { ItemId } from '../commons/CommonTypes'; -import GameGlobalAPI from '../scenes/gameManager/GameGlobalAPI'; -import { Question } from './GameQuizType'; -import { QuizConstants, textStyle, quizOptStyle, questionTextStyle, resultMsg, questionPrompt } from './GameQuizConstants'; import ImageAssets from '../assets/ImageAssets'; import SoundAssets from '../assets/SoundAssets'; +import { SpeakerDetail } from '../character/GameCharacterTypes'; import { Constants, screenSize } from '../commons/CommonConstants'; +import { ItemId } from '../commons/CommonTypes'; +import { createDialogueBox, createTypewriter } from '../dialogue/GameDialogueHelper'; +import { DialogueObject } from '../dialogue/GameDialogueTypes'; +import { fadeAndDestroy } from '../effects/FadeEffect'; +import { rightSideEntryTweenProps, rightSideExitTweenProps } from '../effects/FlyEffect'; import { Layer } from '../layer/GameLayerTypes'; +import GameGlobalAPI from '../scenes/gameManager/GameGlobalAPI'; import SourceAcademyGame from '../SourceAcademyGame'; import { createButton } from '../utils/ButtonUtils'; import { sleep } from '../utils/GameUtils'; import { calcListFormatPos, HexColor } from '../utils/StyleUtils'; -import { fadeAndDestroy } from '../effects/FadeEffect'; -import { rightSideEntryTweenProps, rightSideExitTweenProps } from '../effects/FlyEffect'; -import { DialogueObject } from '../dialogue/GameDialogueTypes'; +import { + questionPrompt, + questionTextStyle, + QuizConstants, + quizOptStyle, + resultMsg, + textStyle +} from './GameQuizConstants'; import GameQuizReactionManager from './GameQuizReactionManager'; -import { createDialogueBox, createTypewriter } from '../dialogue/GameDialogueHelper'; -import { SpeakerDetail } from '../character/GameCharacterTypes'; +import { Question } from './GameQuizType'; export default class QuizManager { - private reactionManager? : GameQuizReactionManager; + private reactionManager?: GameQuizReactionManager; - // Print everything. To test if the quiz parser parses correctly. - public async showQuiz(quizId:ItemId) { + public async showQuiz(quizId: ItemId) { const quiz = GameGlobalAPI.getInstance().getQuizById(quizId); const numOfQns = quiz.questions.length; if (numOfQns === 0) { @@ -29,7 +35,10 @@ export default class QuizManager { } let numOfCorrect = 0; for (let i = 0; i < numOfQns; i++) { - numOfCorrect += await this.showQuizQuestion(GameGlobalAPI.getInstance().getGameManager(), quiz.questions[i]); + numOfCorrect += await this.showQuizQuestion( + GameGlobalAPI.getInstance().getGameManager(), + quiz.questions[i] + ); } GameGlobalAPI.getInstance().attemptQuiz(quizId); if (numOfCorrect === numOfQns) GameGlobalAPI.getInstance().completeQuiz(quizId); @@ -37,112 +46,116 @@ export default class QuizManager { } //Display the specific quiz question - public async showQuizQuestion(scene: Phaser.Scene, question: Question){ - - GameGlobalAPI.getInstance().getGameManager().getPhaseManager().isCurrentPhaseTerminal(); - const choices = question.options; - const quizContainer = new Phaser.GameObjects.Container(scene, 0, 0); - - const quizPartitions = Math.ceil(choices.length / 5); - const quizHeight = choices.length; - - //create quiz box contains quiz questions - const quizQuestionBox = createDialogueBox(scene); - - const quizQuestionWriter = createTypewriter(scene, questionTextStyle); - - quizQuestionWriter.changeLine(question.question); - GameGlobalAPI.getInstance().storeDialogueLine(question.question, question.speaker); - - const header = new Phaser.GameObjects.Text( - scene, - screenSize.x - QuizConstants.textPad, - QuizConstants.y, - questionPrompt, - textStyle - ).setOrigin(1.0, 0.0); - - const quizHeaderBg = new Phaser.GameObjects.Rectangle( - scene, - screenSize.x, - QuizConstants.y - QuizConstants.textPad, - QuizConstants.width * quizPartitions, - header.getBounds().bottom * 0.5 + QuizConstants.textPad, - HexColor.darkBlue, - 0.8 - ).setOrigin(1.0, 0.0); - - const quizBg = new Phaser.GameObjects.Rectangle( - scene, - screenSize.x, - QuizConstants.y - QuizConstants.textPad, - QuizConstants.width * quizPartitions, - quizHeaderBg.getBounds().bottom * 0.5 + (quizHeight + 0.5) * QuizConstants.yInterval, - HexColor.lightBlue, - 0.2 - ).setOrigin(1.0, 0.0); - - quizContainer.add([quizBg, quizHeaderBg, header, - quizQuestionBox, quizQuestionWriter.container]); - - const buttonPositions = calcListFormatPos({ - numOfItems: choices.length, - xSpacing: 0, - ySpacing: QuizConstants.yInterval - }); - - GameGlobalAPI.getInstance().addToLayer(Layer.Dialogue, quizContainer); - - const activateQuizContainer: Promise = new Promise(resolve => { - quizContainer.add( - choices.map((response, index) => - createButton(scene, { - assetKey: ImageAssets.mediumButton.key, - message: response.text, - textConfig: QuizConstants.textConfig, - bitMapTextStyle: quizOptStyle, - onUp: async () => { - onUp: async () => { - quizContainer.destroy(); - const isCorrect = (index === question.answer) ? 1 : 0; - if (response.reaction) { - await this.showReaction(response.reaction); - } - resolve(isCorrect); + public async showQuizQuestion(scene: Phaser.Scene, question: Question) { + const choices = question.options; + const quizContainer = new Phaser.GameObjects.Container(scene, 0, 0); + + const quizPartitions = Math.ceil(choices.length / 5); + const quizHeight = choices.length; + + //create quiz box contains quiz questions + const quizQuestionBox = createDialogueBox(scene); + + const quizQuestionWriter = createTypewriter(scene, questionTextStyle); + + quizQuestionWriter.changeLine(question.question); + GameGlobalAPI.getInstance().storeDialogueLine(question.question, question.speaker); + + const header = new Phaser.GameObjects.Text( + scene, + screenSize.x - QuizConstants.textPad, + QuizConstants.y, + questionPrompt, + textStyle + ).setOrigin(1.0, 0.0); + + const quizHeaderBg = new Phaser.GameObjects.Rectangle( + scene, + screenSize.x, + QuizConstants.y - QuizConstants.textPad, + QuizConstants.width * quizPartitions, + header.getBounds().bottom * 0.5 + QuizConstants.textPad, + HexColor.darkBlue, + 0.8 + ).setOrigin(1.0, 0.0); + + const quizBg = new Phaser.GameObjects.Rectangle( + scene, + screenSize.x, + QuizConstants.y - QuizConstants.textPad, + QuizConstants.width * quizPartitions, + quizHeaderBg.getBounds().bottom * 0.5 + (quizHeight + 0.5) * QuizConstants.yInterval, + HexColor.lightBlue, + 0.2 + ).setOrigin(1.0, 0.0); + + quizContainer.add([ + quizBg, + quizHeaderBg, + header, + quizQuestionBox, + quizQuestionWriter.container + ]); + + const buttonPositions = calcListFormatPos({ + numOfItems: choices.length, + xSpacing: 0, + ySpacing: QuizConstants.yInterval + }); + + GameGlobalAPI.getInstance().addToLayer(Layer.Dialogue, quizContainer); + + const activateQuizContainer: Promise = new Promise(resolve => { + quizContainer.add( + choices.map((response, index) => + createButton(scene, { + assetKey: ImageAssets.mediumButton.key, + message: response.text, + textConfig: QuizConstants.textConfig, + bitMapTextStyle: quizOptStyle, + onUp: async () => { + quizContainer.destroy(); + const isCorrect = index === question.answer ? 1 : 0; + if (response.reaction) { + await this.showReaction(response.reaction); } - }).setPosition( - screenSize.x - - QuizConstants.width / 2 - - QuizConstants.width * (quizPartitions - Math.floor(index / 5) - 1) + QuizConstants.textPad, - (buttonPositions[index][1] % (5 * QuizConstants.yInterval)) + - quizHeaderBg.getBounds().bottom + 75 - ) + resolve(isCorrect); + } + }).setPosition( + screenSize.x - + QuizConstants.width / 2 - + QuizConstants.width * (quizPartitions - Math.floor(index / 5) - 1), + (buttonPositions[index][1] % (5 * QuizConstants.yInterval)) + + quizHeaderBg.getBounds().bottom + + 75 ) - );}); - - const response = await activateQuizContainer; - - // Animate in - quizContainer.setPosition(screenSize.x, 0); - SourceAcademyGame.getInstance().getSoundManager().playSound(SoundAssets.notifEnter.key); - scene.add.tween({ - targets: quizContainer, - alpha: 1, - ...rightSideEntryTweenProps - }); - await sleep(rightSideEntryTweenProps.duration); - - // Animate out - SourceAcademyGame.getInstance().getSoundManager().playSound(SoundAssets.notifExit.key); - scene.add.tween({ - targets: quizContainer, - alpha: 1, - ...rightSideExitTweenProps - }); - - //await sleep(rightSideExitTweenProps.duration); - fadeAndDestroy(scene, quizContainer, { fadeDuration: Constants.fadeDuration }); - return response; + ) + ); + }); + + const response = await activateQuizContainer; + + // Animate in + quizContainer.setPosition(screenSize.x, 0); + SourceAcademyGame.getInstance().getSoundManager().playSound(SoundAssets.notifEnter.key); + scene.add.tween({ + targets: quizContainer, + alpha: 1, + ...rightSideEntryTweenProps + }); + await sleep(rightSideEntryTweenProps.duration); + + // Animate out + SourceAcademyGame.getInstance().getSoundManager().playSound(SoundAssets.notifExit.key); + scene.add.tween({ + targets: quizContainer, + alpha: 1, + ...rightSideExitTweenProps + }); + + //await sleep(rightSideExitTweenProps.duration); + fadeAndDestroy(scene, quizContainer, { fadeDuration: Constants.fadeDuration }); + return response; } private async showReaction(scene: Phaser.Scene, question: Question, reaction: DialogueObject, status: QuizResult) { @@ -172,11 +185,13 @@ export default class QuizManager { * @param numOfCorrect The number of correctly answered questions. * @returns A DialogueObject containing the message of quiz score. */ - private makeResultMsg(numOfQns: number, numOfCorrect: number, speaker: SpeakerDetail): DialogueObject { - let line = `You got ${ numOfCorrect } out of ${numOfQns} questions correct. `; - line += (numOfCorrect === numOfQns ? resultMsg.allCorrect : resultMsg.notAllCorrect); - return new Map([ - ["0", [{line: line, speakerDetail: speaker}]] - ]); + private makeResultMsg( + numOfQns: number, + numOfCorrect: number, + speaker: SpeakerDetail + ): DialogueObject { + let line = `You got ${numOfCorrect} out of ${numOfQns} questions correct. `; + line += numOfCorrect === numOfQns ? resultMsg.allCorrect : resultMsg.notAllCorrect; + return new Map([['0', [{ line: line, speakerDetail: speaker }]]]); } } diff --git a/src/features/game/quiz/GameQuizReactionManager.ts b/src/features/game/quiz/GameQuizReactionManager.ts index 2cd99c6478..79cab9ed91 100644 --- a/src/features/game/quiz/GameQuizReactionManager.ts +++ b/src/features/game/quiz/GameQuizReactionManager.ts @@ -1,88 +1,85 @@ import SoundAssets from '../assets/SoundAssets'; -import { promptWithChoices } from '../effects/Prompt'; -import { Layer } from '../layer/GameLayerTypes'; -import GameGlobalAPI from '../scenes/gameManager/GameGlobalAPI'; -import SourceAcademyGame from '../SourceAcademyGame'; //import { textTypeWriterStyle } from '../dialogue/GameDialogueConstants'; import DialogueGenerator from '../dialogue/GameDialogueGenerator'; import DialogueRenderer from '../dialogue/GameDialogueRenderer'; //import DialogueSpeakerRenderer from '../dialogue/GameDialogueSpeakerRenderer'; -import { DialogueObject } from "../dialogue/GameDialogueTypes"; +import { DialogueObject } from '../dialogue/GameDialogueTypes'; +import { promptWithChoices } from '../effects/Prompt'; +import { Layer } from '../layer/GameLayerTypes'; +import GameGlobalAPI from '../scenes/gameManager/GameGlobalAPI'; +import SourceAcademyGame from '../SourceAcademyGame'; import { questionTextStyle } from './GameQuizConstants'; import { QuizSpeakerRenderer } from './GameQuizSpeakerRenderer'; export default class GameQuizReactionManager { - private dialogue: DialogueObject; - private dialogueRenderer? : DialogueRenderer; - private dialogueGenerator? : DialogueGenerator; - private speakerRenderer? : QuizSpeakerRenderer; + private dialogue: DialogueObject; + private dialogueRenderer?: DialogueRenderer; + private dialogueGenerator?: DialogueGenerator; + private speakerRenderer?: QuizSpeakerRenderer; + constructor(dialogue: DialogueObject) { + this.dialogue = dialogue; + } - constructor(dialogue: DialogueObject) { - this.dialogue = dialogue; - } + public async showReaction(): Promise { + this.dialogueRenderer = new DialogueRenderer(questionTextStyle); + this.dialogueGenerator = new DialogueGenerator(this.dialogue); + this.speakerRenderer = new QuizSpeakerRenderer(); - public async showReaction() : Promise { - this.dialogueRenderer = new DialogueRenderer(questionTextStyle); - this.dialogueGenerator = new DialogueGenerator(this.dialogue); - this.speakerRenderer = new QuizSpeakerRenderer(); + GameGlobalAPI.getInstance().addToLayer( + Layer.Dialogue, + this.dialogueRenderer.getDialogueContainer() + ); - GameGlobalAPI.getInstance().addToLayer( - Layer.Dialogue, - this.dialogueRenderer.getDialogueContainer() - ); + GameGlobalAPI.getInstance().fadeInLayer(Layer.Dialogue); + await new Promise(resolve => this.playWholeDialogue(resolve as () => void)); + this.getDialogueRenderer().destroy(); + this.getSpeakerRenderer().changeSpeakerTo(null); + } - GameGlobalAPI.getInstance().fadeInLayer(Layer.Dialogue); - await new Promise(resolve => this.playWholeDialogue(resolve as () => void)); - this.getDialogueRenderer().destroy(); - this.getSpeakerRenderer().changeSpeakerTo(null); - } - - private async playWholeDialogue(resolve: () => void) { + private async playWholeDialogue(resolve: () => void) { + await this.showNextLine(resolve); + this.getDialogueRenderer() + .getDialogueBox() + .on(Phaser.Input.Events.GAMEOBJECT_POINTER_UP, async () => { await this.showNextLine(resolve); - this.getDialogueRenderer() - .getDialogueBox() - .on(Phaser.Input.Events.GAMEOBJECT_POINTER_UP, async () => { - await this.showNextLine(resolve); - }); - } + }); + } + + private async showNextLine(resolve: () => void) { + GameGlobalAPI.getInstance().playSound(SoundAssets.dialogueAdvance.key); + const { line, speakerDetail, actionIds, prompt } = + await this.getDialogueGenerator().generateNextLine(); + const lineWithName = line.replace('{name}', this.getUsername()); + this.getDialogueRenderer().changeText(lineWithName); + this.getSpeakerRenderer().changeSpeakerTo(speakerDetail); + + // Store the current line into the storage + GameGlobalAPI.getInstance().storeDialogueLine(lineWithName, speakerDetail); - private async showNextLine(resolve: () => void) { - GameGlobalAPI.getInstance().playSound(SoundAssets.dialogueAdvance.key); - const { line, speakerDetail, actionIds, prompt } = - await this.getDialogueGenerator().generateNextLine(); - const lineWithName = line.replace('{name}', this.getUsername()); - this.getDialogueRenderer().changeText(lineWithName); - this.getSpeakerRenderer().changeSpeakerTo(speakerDetail); - - // Store the current line into the storage - GameGlobalAPI.getInstance().storeDialogueLine(lineWithName, speakerDetail); - - // Disable interactions while processing actions - GameGlobalAPI.getInstance().enableSprite(this.getDialogueRenderer().getDialogueBox(), false); - - if (prompt) { - // disable keyboard input to prevent continue dialogue - const response = await promptWithChoices( - GameGlobalAPI.getInstance().getGameManager(), - prompt.promptTitle, - prompt.choices.map(choice => choice[0]) - ); + // Disable interactions while processing actions + GameGlobalAPI.getInstance().enableSprite(this.getDialogueRenderer().getDialogueBox(), false); - this.getDialogueGenerator().updateCurrPart(prompt.choices[response][1]); - } - await GameGlobalAPI.getInstance().processGameActionsInSamePhase(actionIds); - GameGlobalAPI.getInstance().enableSprite(this.getDialogueRenderer().getDialogueBox(), true); - - if (!line) { - resolve(); - } - } - - + if (prompt) { + // disable keyboard input to prevent continue dialogue + const response = await promptWithChoices( + GameGlobalAPI.getInstance().getGameManager(), + prompt.promptTitle, + prompt.choices.map(choice => choice[0]) + ); + + this.getDialogueGenerator().updateCurrPart(prompt.choices[response][1]); + } + await GameGlobalAPI.getInstance().processGameActionsInSamePhase(actionIds); + GameGlobalAPI.getInstance().enableSprite(this.getDialogueRenderer().getDialogueBox(), true); + + if (!line) { + resolve(); + } + } - private getDialogueGenerator = () => this.dialogueGenerator as DialogueGenerator; - private getDialogueRenderer = () => this.dialogueRenderer as DialogueRenderer; - private getSpeakerRenderer = () => this.speakerRenderer as QuizSpeakerRenderer; - public getUsername = () => SourceAcademyGame.getInstance().getAccountInfo().name; -} \ No newline at end of file + private getDialogueGenerator = () => this.dialogueGenerator as DialogueGenerator; + private getDialogueRenderer = () => this.dialogueRenderer as DialogueRenderer; + private getSpeakerRenderer = () => this.speakerRenderer as QuizSpeakerRenderer; + public getUsername = () => SourceAcademyGame.getInstance().getAccountInfo().name; +} diff --git a/src/features/game/quiz/GameQuizSpeakerRenderer.ts b/src/features/game/quiz/GameQuizSpeakerRenderer.ts index 73d5467d14..d2896bbcd9 100644 --- a/src/features/game/quiz/GameQuizSpeakerRenderer.ts +++ b/src/features/game/quiz/GameQuizSpeakerRenderer.ts @@ -7,12 +7,12 @@ import GameGlobalAPI from '../scenes/gameManager/GameGlobalAPI'; import SourceAcademyGame from '../SourceAcademyGame'; import StringUtils from '../utils/StringUtils'; import { createBitmapText } from '../utils/TextUtils'; -import {QuizConstants, speakerTextStyle } from './GameQuizConstants'; +import { QuizConstants, speakerTextStyle } from './GameQuizConstants'; export class QuizSpeakerRenderer { - private currentSpeakerId?: string; - private speakerSprite?:Phaser.GameObjects.Image; - private speakerSpriteBox?:Phaser.GameObjects.Container; + private currentSpeakerId?: string; + private speakerSprite?: Phaser.GameObjects.Image; + private speakerSpriteBox?: Phaser.GameObjects.Container; /** * Changes the speaker shown in the speaker box and the speaker rendered on screen @@ -101,7 +101,7 @@ export class QuizSpeakerRenderer { } /** - * Show the hidden speaker box and sprite + * Show the hidden speaker box and sprite */ public async show() { this.getSpeakerSprite().setVisible(true); @@ -110,5 +110,5 @@ export class QuizSpeakerRenderer { public getUsername = () => SourceAcademyGame.getInstance().getAccountInfo().name; public getSpeakerSprite = () => this.speakerSprite as Phaser.GameObjects.Image; - public getSpeakerSpriteBox = () => this.speakerSpriteBox as Phaser.GameObjects.Container; -} \ No newline at end of file + public getSpeakerSpriteBox = () => this.speakerSpriteBox as Phaser.GameObjects.Container; +} From 1554798f369c0cec6517823742f00166d47052bd Mon Sep 17 00:00:00 2001 From: reginateh Date: Mon, 1 Apr 2024 18:12:17 +0800 Subject: [PATCH 31/61] Merge branch 'master' into game-quiz --- src/features/cseMachine/__tests__/CseMachine.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/cseMachine/__tests__/CseMachine.tsx b/src/features/cseMachine/__tests__/CseMachine.tsx index 904c000938..ac2827a432 100644 --- a/src/features/cseMachine/__tests__/CseMachine.tsx +++ b/src/features/cseMachine/__tests__/CseMachine.tsx @@ -184,7 +184,7 @@ const codeSamplesControlStash = [ } create(3)[1](); `, - 32 + 33 ], [ 'global environments are treated correctly', From 7655f787a7bc4d67dbd0595a1db2af6e25c4ac9d Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Mon, 1 Apr 2024 18:58:45 +0800 Subject: [PATCH 32/61] add documentation --- .../game/dialogue/GameDialogueManager.ts | 6 +++++ .../game/dialogue/GameDialogueRenderer.ts | 7 +++--- .../dialogue/GameDialogueSpeakerRenderer.ts | 6 ++--- src/features/game/parser/ConditionParser.ts | 6 ++--- src/features/game/quiz/GameQuizManager.ts | 25 +++++++++++-------- .../game/quiz/GameQuizReactionManager.ts | 10 ++++++-- .../game/quiz/GameQuizSpeakerRenderer.ts | 5 ++++ 7 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/features/game/dialogue/GameDialogueManager.ts b/src/features/game/dialogue/GameDialogueManager.ts index b922f92ea0..9a074d9e8f 100644 --- a/src/features/game/dialogue/GameDialogueManager.ts +++ b/src/features/game/dialogue/GameDialogueManager.ts @@ -104,11 +104,17 @@ export default class DialogueManager { } } + /** + * Hide all dialogue boxes, speaker boxes and speaker sprites + * */ public async hideAll() { await this.getDialogueRenderer().hide(); await this.getSpeakerRenderer().hide(); } + /** + * Make all dialogue boxes, speaker boxes and speaker sprites visible + * */ public async showAll() { await this.getDialogueRenderer().show(); await this.getSpeakerRenderer().show(); diff --git a/src/features/game/dialogue/GameDialogueRenderer.ts b/src/features/game/dialogue/GameDialogueRenderer.ts index d67841fa37..ce573fdce6 100644 --- a/src/features/game/dialogue/GameDialogueRenderer.ts +++ b/src/features/game/dialogue/GameDialogueRenderer.ts @@ -69,7 +69,7 @@ class DialogueRenderer { } /** - * Hide the dialoguebox + * Hide the dialoguebox */ public async hide() { this.typewriter.container.setVisible(false); @@ -78,15 +78,14 @@ class DialogueRenderer { } /** - * Show the hidden dialoguebox + * Make the dialoguebox visible */ public async show() { this.typewriter.container.setVisible(true); this.dialogueBox.setVisible(true); - this.blinkingDiamond.container.setVisible(true); + this.blinkingDiamond.container.setVisible(true); } - /** * Change the text written in the box */ diff --git a/src/features/game/dialogue/GameDialogueSpeakerRenderer.ts b/src/features/game/dialogue/GameDialogueSpeakerRenderer.ts index 970df687dd..083f6b11c1 100644 --- a/src/features/game/dialogue/GameDialogueSpeakerRenderer.ts +++ b/src/features/game/dialogue/GameDialogueSpeakerRenderer.ts @@ -16,8 +16,8 @@ import DialogueConstants, { speakerTextStyle } from './GameDialogueConstants'; */ export default class DialogueSpeakerRenderer { private currentSpeakerId?: string; - private speakerSprite?:Phaser.GameObjects.Image; - private speakerSpriteBox?:Phaser.GameObjects.Container; + private speakerSprite?: Phaser.GameObjects.Image; + private speakerSpriteBox?: Phaser.GameObjects.Container; /** * Changes the speaker shown in the speaker box and the speaker rendered on screen @@ -106,7 +106,7 @@ export default class DialogueSpeakerRenderer { } /** - * Show the hidden speaker box and sprite + * Show the hidden speaker box and sprite */ public async show() { this.getSpeakerSprite().setVisible(true); diff --git a/src/features/game/parser/ConditionParser.ts b/src/features/game/parser/ConditionParser.ts index 16cc870ba8..47396b675e 100644 --- a/src/features/game/parser/ConditionParser.ts +++ b/src/features/game/parser/ConditionParser.ts @@ -51,7 +51,7 @@ export default class ConditionParser { }, boolean: !hasExclamation }; - + case GameStateStorage.AttemptedQuizState: return { state: GameStateStorage.AttemptedQuizState, @@ -60,7 +60,7 @@ export default class ConditionParser { }, boolean: !hasExclamation }; - + case GameStateStorage.CompletedQuizState: return { state: GameStateStorage.CompletedQuizState, @@ -69,7 +69,7 @@ export default class ConditionParser { }, boolean: !hasExclamation }; - + default: throw new Error('Parsing error: Invalid condition param'); } diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index ee26832a59..ad92edae43 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -27,6 +27,10 @@ import { Question } from './GameQuizType'; export default class QuizManager { private reactionManager?: GameQuizReactionManager; + /** + * rendering the quiz section inside a dialogue + * @param quizId the Id of quiz that users will attempt inside a dialogye + */ public async showQuiz(quizId: ItemId) { const quiz = GameGlobalAPI.getInstance().getQuizById(quizId); const numOfQns = quiz.questions.length; @@ -53,14 +57,16 @@ export default class QuizManager { const quizPartitions = Math.ceil(choices.length / 5); const quizHeight = choices.length; - //create quiz box contains quiz questions + //Create quiz box contains quiz questions const quizQuestionBox = createDialogueBox(scene); + //Create text writer to display quiz questions const quizQuestionWriter = createTypewriter(scene, questionTextStyle); - quizQuestionWriter.changeLine(question.question); + GameGlobalAPI.getInstance().storeDialogueLine(question.question, question.speaker); + //Generate UI components for quizzes const header = new Phaser.GameObjects.Text( scene, screenSize.x - QuizConstants.textPad, @@ -105,6 +111,7 @@ export default class QuizManager { GameGlobalAPI.getInstance().addToLayer(Layer.Dialogue, quizContainer); + //Create options for users to select const activateQuizContainer: Promise = new Promise(resolve => { quizContainer.add( choices.map((response, index) => @@ -158,14 +165,12 @@ export default class QuizManager { return response; } - private async showReaction(scene: Phaser.Scene, question: Question, reaction: DialogueObject, status: QuizResult) { - console.log("the number of correct answer: " + status.numberOfQuestions); - await this.showResult(scene, reaction); - //await displayNotification(GameGlobalAPI.getInstance().getGameManager(), "number of correct questions: " + status.numberOfQuestions); - } - - - private async showResult(scene: Phaser.Scene, reaction: DialogueObject) { + /** + * Display the reaction after users selecting an option + * @param reaction the reaction will be displayed based on the choice of users + * + */ + private async showReaction(reaction: DialogueObject) { this.reactionManager = new GameQuizReactionManager(reaction); await this.reactionManager.showReaction(); } diff --git a/src/features/game/quiz/GameQuizReactionManager.ts b/src/features/game/quiz/GameQuizReactionManager.ts index 79cab9ed91..2578c510ab 100644 --- a/src/features/game/quiz/GameQuizReactionManager.ts +++ b/src/features/game/quiz/GameQuizReactionManager.ts @@ -1,8 +1,6 @@ import SoundAssets from '../assets/SoundAssets'; -//import { textTypeWriterStyle } from '../dialogue/GameDialogueConstants'; import DialogueGenerator from '../dialogue/GameDialogueGenerator'; import DialogueRenderer from '../dialogue/GameDialogueRenderer'; -//import DialogueSpeakerRenderer from '../dialogue/GameDialogueSpeakerRenderer'; import { DialogueObject } from '../dialogue/GameDialogueTypes'; import { promptWithChoices } from '../effects/Prompt'; import { Layer } from '../layer/GameLayerTypes'; @@ -11,6 +9,10 @@ import SourceAcademyGame from '../SourceAcademyGame'; import { questionTextStyle } from './GameQuizConstants'; import { QuizSpeakerRenderer } from './GameQuizSpeakerRenderer'; +/** + * A class that manages the reaction after a option is selected or the quiz is complete + * + */ export default class GameQuizReactionManager { private dialogue: DialogueObject; private dialogueRenderer?: DialogueRenderer; @@ -21,6 +23,10 @@ export default class GameQuizReactionManager { this.dialogue = dialogue; } + /** + * It renders the reaction after the selection of an option or the quiz ends + * @returns {Promise} the promise that resolves when the whole reaction is displayed + */ public async showReaction(): Promise { this.dialogueRenderer = new DialogueRenderer(questionTextStyle); this.dialogueGenerator = new DialogueGenerator(this.dialogue); diff --git a/src/features/game/quiz/GameQuizSpeakerRenderer.ts b/src/features/game/quiz/GameQuizSpeakerRenderer.ts index d2896bbcd9..2173521b4e 100644 --- a/src/features/game/quiz/GameQuizSpeakerRenderer.ts +++ b/src/features/game/quiz/GameQuizSpeakerRenderer.ts @@ -9,6 +9,11 @@ import StringUtils from '../utils/StringUtils'; import { createBitmapText } from '../utils/TextUtils'; import { QuizConstants, speakerTextStyle } from './GameQuizConstants'; +/** + * Class that manages speaker box portion of the quiz speaker box + * render characters in QuizSpeaker Layer + * + */ export class QuizSpeakerRenderer { private currentSpeakerId?: string; private speakerSprite?: Phaser.GameObjects.Image; From a8de1e3c056555dbfcfaa372c24c9fe4816e0cd4 Mon Sep 17 00:00:00 2001 From: reginateh Date: Tue, 2 Apr 2024 15:02:50 +0800 Subject: [PATCH 33/61] move quiz result message to QuizConstant --- src/features/game/quiz/GameQuizConstants.ts | 1 + src/features/game/quiz/GameQuizManager.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index faf57338b6..29d894bed9 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -6,6 +6,7 @@ import { Color } from '../utils/StyleUtils'; export const questionPrompt = 'What is the correct answer?'; export const resultMsg = { + message: 'You got {numOfCorrect} out of {numOfQns} questions correct. ', allCorrect: 'Well done!', notAllCorrect: "Let's keep going!" }; diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index ad92edae43..6d68e203bd 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -195,7 +195,7 @@ export default class QuizManager { numOfCorrect: number, speaker: SpeakerDetail ): DialogueObject { - let line = `You got ${numOfCorrect} out of ${numOfQns} questions correct. `; + let line = resultMsg.message.replace('{numOfCorrect}', numOfCorrect.toString()).replace('{numOfQns}', numOfQns.toString()); line += numOfCorrect === numOfQns ? resultMsg.allCorrect : resultMsg.notAllCorrect; return new Map([['0', [{ line: line, speakerDetail: speaker }]]]); } From 65e8912e9ec64fd44feab87a1c1eb0f987babe22 Mon Sep 17 00:00:00 2001 From: reginateh Date: Tue, 2 Apr 2024 15:03:00 +0800 Subject: [PATCH 34/61] change quiz condition names --- src/features/game/parser/ParserConverter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/game/parser/ParserConverter.ts b/src/features/game/parser/ParserConverter.ts index 0e4752dd1a..85ad3025af 100644 --- a/src/features/game/parser/ParserConverter.ts +++ b/src/features/game/parser/ParserConverter.ts @@ -67,8 +67,8 @@ const stringToGameStateStorageMap = { checklist: GameStateStorage.ChecklistState, tasklist: GameStateStorage.TasklistState, userstate: GameStateStorage.UserState, - attempted: GameStateStorage.AttemptedQuizState, - completed: GameStateStorage.CompletedQuizState + attemptedQuiz: GameStateStorage.AttemptedQuizState, + completedQuiz: GameStateStorage.CompletedQuizState }; const stringToUserStateTypeMap = { From 01824e6ae8f6499fef07aab6291e0facbf4de09e Mon Sep 17 00:00:00 2001 From: reginateh Date: Fri, 5 Apr 2024 10:01:36 +0800 Subject: [PATCH 35/61] Add a prompt before a quiz & proceed to the next dialogue line when a quiz ends --- .../game/action/GameActionExecuter.ts | 2 - .../game/dialogue/GameDialogueManager.ts | 2 +- src/features/game/quiz/GameQuizConstants.ts | 9 +++- src/features/game/quiz/GameQuizManager.ts | 48 +++++++++++++++---- .../game/scenes/gameManager/GameGlobalAPI.ts | 6 ++- 5 files changed, 52 insertions(+), 15 deletions(-) diff --git a/src/features/game/action/GameActionExecuter.ts b/src/features/game/action/GameActionExecuter.ts index dff127ebe0..e964b1db26 100644 --- a/src/features/game/action/GameActionExecuter.ts +++ b/src/features/game/action/GameActionExecuter.ts @@ -104,9 +104,7 @@ export default class GameActionExecuter { return; case GameActionType.ShowQuiz: globalAPI.enableKeyboardInput(false); - await globalAPI.getGameManager().getDialogueManager().hideAll(); await globalAPI.showQuiz(actionParams.id); - await globalAPI.getGameManager().getDialogueManager().showAll(); globalAPI.enableKeyboardInput(true); return; default: diff --git a/src/features/game/dialogue/GameDialogueManager.ts b/src/features/game/dialogue/GameDialogueManager.ts index 9a074d9e8f..4966b37b3f 100644 --- a/src/features/game/dialogue/GameDialogueManager.ts +++ b/src/features/game/dialogue/GameDialogueManager.ts @@ -66,7 +66,7 @@ export default class DialogueManager { }); } - private async showNextLine(resolve: () => void) { + public async showNextLine(resolve: () => void) { GameGlobalAPI.getInstance().playSound(SoundAssets.dialogueAdvance.key); const { line, speakerDetail, actionIds, prompt } = await this.getDialogueGenerator().generateNextLine(); diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index 29d894bed9..c2832b4cb2 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -3,6 +3,11 @@ import { screenSize } from '../commons/CommonConstants'; import { BitmapFontStyle } from '../commons/CommonTypes'; import { Color } from '../utils/StyleUtils'; +export const startPrompt = { + text: "Start the quiz?", + options: ["Yes", "No"] +} + export const questionPrompt = 'What is the correct answer?'; export const resultMsg = { @@ -21,9 +26,9 @@ export const QuizConstants = { speakerTextConfig: { x: 320, y: 745, oriX: 0.5, oriY: 0.5 } }; -export const textStyle = { +export const quizTextStyle = { fontFamily: 'Verdana', - fontSize: '25px', + fontSize: '20px', fill: Color.offWhite, align: 'left', lineSpacing: 10, diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 6d68e203bd..018abaa9a1 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -15,21 +15,24 @@ import { sleep } from '../utils/GameUtils'; import { calcListFormatPos, HexColor } from '../utils/StyleUtils'; import { questionPrompt, + startPrompt, + resultMsg, questionTextStyle, QuizConstants, quizOptStyle, - resultMsg, - textStyle + quizTextStyle } from './GameQuizConstants'; import GameQuizReactionManager from './GameQuizReactionManager'; import { Question } from './GameQuizType'; +import { promptWithChoices } from '../effects/Prompt'; export default class QuizManager { private reactionManager?: GameQuizReactionManager; /** - * rendering the quiz section inside a dialogue - * @param quizId the Id of quiz that users will attempt inside a dialogye + * Rendering the quiz section inside a dialogue. + * + * @param quizId The Id of quiz that users will attempt inside a dialogue. */ public async showQuiz(quizId: ItemId) { const quiz = GameGlobalAPI.getInstance().getQuizById(quizId); @@ -37,6 +40,11 @@ export default class QuizManager { if (numOfQns === 0) { return; } + if (!await this.showStartPrompt(GameGlobalAPI.getInstance().getGameManager())) { + await GameGlobalAPI.getInstance().showNextLine(); + return; + } + await GameGlobalAPI.getInstance().getGameManager().getDialogueManager().hideAll(); let numOfCorrect = 0; for (let i = 0; i < numOfQns; i++) { numOfCorrect += await this.showQuizQuestion( @@ -47,9 +55,29 @@ export default class QuizManager { GameGlobalAPI.getInstance().attemptQuiz(quizId); if (numOfCorrect === numOfQns) GameGlobalAPI.getInstance().completeQuiz(quizId); await this.showResult(numOfQns, numOfCorrect, quiz.questions[0].speaker); + await GameGlobalAPI.getInstance().showNextLine(); + await GameGlobalAPI.getInstance().getGameManager().getDialogueManager().showAll(); + } + + /** + * Display a prompt before a quiz starts. + * Player can choose to proceed and do the quiz, + * or to not do the quiz and exit. + * + * @param scene The Game Manager. + * @returns true if the player chooses to start the quiz. + */ + private async showStartPrompt(scene: Phaser.Scene) { + const response = await promptWithChoices(scene, startPrompt.text, startPrompt.options); + return (response === 0); } - //Display the specific quiz question + /** + * Display the specific quiz question. + * + * @param scene The game manager. + * @param question The question to be displayed. + */ public async showQuizQuestion(scene: Phaser.Scene, question: Question) { const choices = question.options; const quizContainer = new Phaser.GameObjects.Container(scene, 0, 0); @@ -72,7 +100,7 @@ export default class QuizManager { screenSize.x - QuizConstants.textPad, QuizConstants.y, questionPrompt, - textStyle + quizTextStyle ).setOrigin(1.0, 0.0); const quizHeaderBg = new Phaser.GameObjects.Rectangle( @@ -166,9 +194,9 @@ export default class QuizManager { } /** - * Display the reaction after users selecting an option - * @param reaction the reaction will be displayed based on the choice of users - * + * Display the reaction after users selecting an option. + * + * @param reaction The reaction to be displayed. */ private async showReaction(reaction: DialogueObject) { this.reactionManager = new GameQuizReactionManager(reaction); @@ -177,6 +205,7 @@ export default class QuizManager { /** * Show the final score of the quiz as a quiz reaction. + * * @param numOfQns The number of questions of the quiz. * @param numOfCorrect The number of correctly answered questions. */ @@ -186,6 +215,7 @@ export default class QuizManager { /** * Create DialogueObject containing the message of quiz score. + * * @param numOfQns The number of questions of the quiz. * @param numOfCorrect The number of correctly answered questions. * @returns A DialogueObject containing the message of quiz score. diff --git a/src/features/game/scenes/gameManager/GameGlobalAPI.ts b/src/features/game/scenes/gameManager/GameGlobalAPI.ts index bea71b3a65..dfff0c3a7e 100644 --- a/src/features/game/scenes/gameManager/GameGlobalAPI.ts +++ b/src/features/game/scenes/gameManager/GameGlobalAPI.ts @@ -284,6 +284,10 @@ class GameGlobalAPI { await this.getGameManager().getDialogueManager().showDialogue(dialogueId); } + public async showNextLine() { + await this.getGameManager().getDialogueManager().showNextLine(() => {}); + } + ///////////////////// // Storage // ///////////////////// @@ -512,7 +516,7 @@ class GameGlobalAPI { } ///////////////////// - // Game Quiz // + // Quiz // ///////////////////// public async showQuiz(quizId: ItemId) { From a5020e424879b531200995b67d550d845368874b Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Fri, 5 Apr 2024 11:10:52 +0800 Subject: [PATCH 36/61] dialogue line bug fix --- src/features/game/quiz/GameQuizConstants.ts | 6 ++-- src/features/game/quiz/GameQuizManager.ts | 30 ++++++++++--------- .../game/scenes/gameManager/GameGlobalAPI.ts | 4 ++- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index c2832b4cb2..7f4c73e629 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -4,9 +4,9 @@ import { BitmapFontStyle } from '../commons/CommonTypes'; import { Color } from '../utils/StyleUtils'; export const startPrompt = { - text: "Start the quiz?", - options: ["Yes", "No"] -} + text: 'Start the quiz?', + options: ['Yes', 'No'] +}; export const questionPrompt = 'What is the correct answer?'; diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 018abaa9a1..894e97b9d3 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -7,6 +7,7 @@ import { createDialogueBox, createTypewriter } from '../dialogue/GameDialogueHel import { DialogueObject } from '../dialogue/GameDialogueTypes'; import { fadeAndDestroy } from '../effects/FadeEffect'; import { rightSideEntryTweenProps, rightSideExitTweenProps } from '../effects/FlyEffect'; +import { promptWithChoices } from '../effects/Prompt'; import { Layer } from '../layer/GameLayerTypes'; import GameGlobalAPI from '../scenes/gameManager/GameGlobalAPI'; import SourceAcademyGame from '../SourceAcademyGame'; @@ -15,23 +16,22 @@ import { sleep } from '../utils/GameUtils'; import { calcListFormatPos, HexColor } from '../utils/StyleUtils'; import { questionPrompt, - startPrompt, - resultMsg, questionTextStyle, QuizConstants, quizOptStyle, - quizTextStyle + quizTextStyle, + resultMsg, + startPrompt } from './GameQuizConstants'; import GameQuizReactionManager from './GameQuizReactionManager'; import { Question } from './GameQuizType'; -import { promptWithChoices } from '../effects/Prompt'; export default class QuizManager { private reactionManager?: GameQuizReactionManager; /** * Rendering the quiz section inside a dialogue. - * + * * @param quizId The Id of quiz that users will attempt inside a dialogue. */ public async showQuiz(quizId: ItemId) { @@ -40,7 +40,7 @@ export default class QuizManager { if (numOfQns === 0) { return; } - if (!await this.showStartPrompt(GameGlobalAPI.getInstance().getGameManager())) { + if (!(await this.showStartPrompt(GameGlobalAPI.getInstance().getGameManager()))) { await GameGlobalAPI.getInstance().showNextLine(); return; } @@ -61,20 +61,20 @@ export default class QuizManager { /** * Display a prompt before a quiz starts. - * Player can choose to proceed and do the quiz, + * Player can choose to proceed and do the quiz, * or to not do the quiz and exit. - * + * * @param scene The Game Manager. * @returns true if the player chooses to start the quiz. */ private async showStartPrompt(scene: Phaser.Scene) { const response = await promptWithChoices(scene, startPrompt.text, startPrompt.options); - return (response === 0); + return response === 0; } /** * Display the specific quiz question. - * + * * @param scene The game manager. * @param question The question to be displayed. */ @@ -195,7 +195,7 @@ export default class QuizManager { /** * Display the reaction after users selecting an option. - * + * * @param reaction The reaction to be displayed. */ private async showReaction(reaction: DialogueObject) { @@ -205,7 +205,7 @@ export default class QuizManager { /** * Show the final score of the quiz as a quiz reaction. - * + * * @param numOfQns The number of questions of the quiz. * @param numOfCorrect The number of correctly answered questions. */ @@ -215,7 +215,7 @@ export default class QuizManager { /** * Create DialogueObject containing the message of quiz score. - * + * * @param numOfQns The number of questions of the quiz. * @param numOfCorrect The number of correctly answered questions. * @returns A DialogueObject containing the message of quiz score. @@ -225,7 +225,9 @@ export default class QuizManager { numOfCorrect: number, speaker: SpeakerDetail ): DialogueObject { - let line = resultMsg.message.replace('{numOfCorrect}', numOfCorrect.toString()).replace('{numOfQns}', numOfQns.toString()); + let line = resultMsg.message + .replace('{numOfCorrect}', numOfCorrect.toString()) + .replace('{numOfQns}', numOfQns.toString()); line += numOfCorrect === numOfQns ? resultMsg.allCorrect : resultMsg.notAllCorrect; return new Map([['0', [{ line: line, speakerDetail: speaker }]]]); } diff --git a/src/features/game/scenes/gameManager/GameGlobalAPI.ts b/src/features/game/scenes/gameManager/GameGlobalAPI.ts index dfff0c3a7e..74fbea7607 100644 --- a/src/features/game/scenes/gameManager/GameGlobalAPI.ts +++ b/src/features/game/scenes/gameManager/GameGlobalAPI.ts @@ -285,7 +285,9 @@ class GameGlobalAPI { } public async showNextLine() { - await this.getGameManager().getDialogueManager().showNextLine(() => {}); + await this.getGameManager() + .getDialogueManager() + .showNextLine(() => {}); } ///////////////////// From 04974d3b62d951e558d6f2a1d906f6f695321b0b Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Wed, 10 Apr 2024 17:54:04 +0800 Subject: [PATCH 37/61] reformatting --- src/features/game/quiz/GameQuizConstants.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index 7f4c73e629..1d05fe326e 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -50,10 +50,8 @@ export const quizOptStyle: BitmapFontStyle = { align: Phaser.GameObjects.BitmapText.ALIGN_CENTER }; - - export const speakerTextStyle: BitmapFontStyle = { key: FontAssets.zektonFont.key, size: 36, align: Phaser.GameObjects.BitmapText.ALIGN_CENTER -}; \ No newline at end of file +}; From 43c497955ab3aebd8366c477c87a0e32ffbc8dd6 Mon Sep 17 00:00:00 2001 From: reginateh Date: Sun, 14 Apr 2024 12:37:57 +0800 Subject: [PATCH 38/61] add saving quizzes score --- src/features/game/quiz/GameQuizManager.ts | 13 +++- src/features/game/save/GameSaveHelper.ts | 6 +- src/features/game/save/GameSaveManager.ts | 3 +- src/features/game/save/GameSaveTypes.ts | 3 +- .../game/scenes/gameManager/GameGlobalAPI.ts | 12 ++-- src/features/game/state/GameStateManager.ts | 70 +++++++++---------- 6 files changed, 55 insertions(+), 52 deletions(-) diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 894e97b9d3..4a67679be0 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -52,8 +52,7 @@ export default class QuizManager { quiz.questions[i] ); } - GameGlobalAPI.getInstance().attemptQuiz(quizId); - if (numOfCorrect === numOfQns) GameGlobalAPI.getInstance().completeQuiz(quizId); + GameGlobalAPI.getInstance().setQuizScore(quizId, numOfCorrect); await this.showResult(numOfQns, numOfCorrect, quiz.questions[0].speaker); await GameGlobalAPI.getInstance().showNextLine(); await GameGlobalAPI.getInstance().getGameManager().getDialogueManager().showAll(); @@ -231,4 +230,14 @@ export default class QuizManager { line += numOfCorrect === numOfQns ? resultMsg.allCorrect : resultMsg.notAllCorrect; return new Map([['0', [{ line: line, speakerDetail: speaker }]]]); } + + /** + * Get the number of questions of a quiz. + * + * @param quizId The Id of quiz. + */ + public getNumOfQns(quizId: ItemId): number { + const quiz = GameGlobalAPI.getInstance().getQuizById(quizId); + return quiz.questions.length; + } } diff --git a/src/features/game/save/GameSaveHelper.ts b/src/features/game/save/GameSaveHelper.ts index 4474f253a8..c53facb172 100644 --- a/src/features/game/save/GameSaveHelper.ts +++ b/src/features/game/save/GameSaveHelper.ts @@ -35,8 +35,7 @@ export function gameStateToJson( completedObjectives: gameStateManager.getCompletedObjectives(), triggeredInteractions: gameStateManager.getTriggeredInteractions(), triggeredStateChangeActions: gameStateManager.getTriggeredStateChangeActions(), - attemptedQuizzes: gameStateManager.getAttemptedQuizzes(), - completedQuizzes: gameStateManager.getCompletedQuizzes() + quizScores: gameStateManager.getQuizScores() } }, userSaveState: { @@ -83,8 +82,7 @@ export const createEmptyGameSaveState = (): GameSaveState => { completedObjectives: [], triggeredInteractions: [], triggeredStateChangeActions: [], - attemptedQuizzes: [], - completedQuizzes: [] + quizScores: [] }; }; diff --git a/src/features/game/save/GameSaveManager.ts b/src/features/game/save/GameSaveManager.ts index 1313a0ee52..f4854de030 100644 --- a/src/features/game/save/GameSaveManager.ts +++ b/src/features/game/save/GameSaveManager.ts @@ -150,8 +150,7 @@ export default class GameSaveManager { public getIncompleteTasks = () => this.getGameSaveState().incompleteTasks; public getLoadedPhase = () => this.getGameSaveState().currentPhase; public getChapterNewlyCompleted = () => this.getGameSaveState().chapterNewlyCompleted; - public getAttemptedQuizzes = () => this.getGameSaveState().attemptedQuizzes; - public getCompletedQuizzes = () => this.getGameSaveState().completedQuizzes; + public getQuizScores = () => this.getGameSaveState().quizScores; public getChapterNum = () => mandatory(this.chapterNum); public getCheckpointNum = () => mandatory(this.checkpointNum); diff --git a/src/features/game/save/GameSaveTypes.ts b/src/features/game/save/GameSaveTypes.ts index b353bef539..65ddb05dcf 100644 --- a/src/features/game/save/GameSaveTypes.ts +++ b/src/features/game/save/GameSaveTypes.ts @@ -34,8 +34,7 @@ export type GameSaveState = { completedObjectives: string[]; triggeredInteractions: string[]; triggeredStateChangeActions: string[]; - attemptedQuizzes: string[]; - completedQuizzes: string[]; + quizScores: [string, number][]; }; /** diff --git a/src/features/game/scenes/gameManager/GameGlobalAPI.ts b/src/features/game/scenes/gameManager/GameGlobalAPI.ts index 74fbea7607..dbad9047ee 100644 --- a/src/features/game/scenes/gameManager/GameGlobalAPI.ts +++ b/src/features/game/scenes/gameManager/GameGlobalAPI.ts @@ -525,6 +525,10 @@ class GameGlobalAPI { await this.getGameManager().getQuizManager().showQuiz(quizId); } + public getQuizLength(quizId: ItemId): number { + return this.getGameManager().getQuizManager().getNumOfQns(quizId); + } + public isQuizAttempted(key: string): boolean { return this.getGameManager().getStateManager().isQuizAttempted(key); } @@ -533,12 +537,8 @@ class GameGlobalAPI { return this.getGameManager().getStateManager().isQuizComplete(key); } - public completeQuiz(key: string): void { - this.getGameManager().getStateManager().completeQuiz(key); - } - - public attemptQuiz(key: string): void { - this.getGameManager().getStateManager().attemptQuiz(key); + public setQuizScore(key: string, score: number): void { + this.getGameManager().getStateManager().setQuizScore(key, score); } } diff --git a/src/features/game/state/GameStateManager.ts b/src/features/game/state/GameStateManager.ts index b1a09df0c0..29c6222617 100644 --- a/src/features/game/state/GameStateManager.ts +++ b/src/features/game/state/GameStateManager.ts @@ -33,8 +33,7 @@ class GameStateManager { private checkpointObjective: GameObjective; private checkpointTask: GameTask; private chapterNewlyCompleted: boolean; - private completedQuizzes: ItemId[]; - private attemptedQuizzes: ItemId[]; + private quizScores: Map; // Triggered Interactions private updatedLocations: Set; @@ -48,8 +47,7 @@ class GameStateManager { this.checkpointObjective = gameCheckpoint.objectives; this.checkpointTask = gameCheckpoint.tasks; this.chapterNewlyCompleted = false; - this.attemptedQuizzes = []; - this.completedQuizzes = []; + this.quizScores = new Map(); this.updatedLocations = new Set(this.gameMap.getLocationIds()); this.triggeredInteractions = new Map(); @@ -86,8 +84,8 @@ class GameStateManager { this.checkpointTask.showTask(task); }); - this.completedQuizzes = this.getSaveManager().getCompletedQuizzes(); - this.attemptedQuizzes = this.getSaveManager().getAttemptedQuizzes(); + this.quizScores = new Map(this.getSaveManager().getQuizScores()); + this.chapterNewlyCompleted = this.getSaveManager().getChapterNewlyCompleted(); } @@ -458,43 +456,51 @@ class GameStateManager { /////////////////////////////// /** - * Checks whether a specific quiz has been completed. + * Checks whether a quiz has been obtained full marks. * * @param key quiz id * @returns {boolean} */ public isQuizComplete(quizId: string): boolean { - return this.completedQuizzes.includes(quizId); + return this.quizScores.get(quizId) === GameGlobalAPI.getInstance().getQuizLength(quizId); } /** - * Checks whether a specific quiz has been attempted. + * Checks whether a specific quiz has been played. * * @param key quiz id * @returns {boolean} */ public isQuizAttempted(quizId: string): boolean { - return this.attemptedQuizzes.includes(quizId); + return this.quizScores.has(quizId); } /** - * Record that a quiz has been completed. - * A quiz is completed when its maximum score is obtained. - * - * @param key task id + * Get the score of a quiz. + * Return undefined if the quiz has not been played. + * + * @param quizId + * @returns */ - public completeQuiz(quizId: string) { - if (!this.completedQuizzes.includes(quizId)) this.completedQuizzes.push(quizId); + public getQuizScore(quizId: ItemId) { + return this.quizScores.get(quizId); } /** - * Record that a quiz has been attempted. - * A quiz is attempted when user answers all questions. - * - * @param key task id + * Set the score of a quiz to a given number + * if the new score is higher than the current score, + * or the quiz has not been played. + * + * @param quizId The id of the quiz. + * @param newScore The new score to be set. */ - public attemptQuiz(quizId: string) { - if (!this.attemptedQuizzes.includes(quizId)) this.attemptedQuizzes.push(quizId); + public setQuizScore(quizId: string, newScore: number) { + const currentScore = this.getQuizScore(quizId); + if (currentScore && currentScore < newScore) { + this.quizScores.set(quizId, newScore); + } else if (!currentScore) { + this.quizScores.set(quizId, newScore); + } } /////////////////////////////// @@ -548,21 +554,13 @@ class GameStateManager { } /** - * Gets array of all quizzes that have been completed. - * - * @returns {ItemId[]} - */ - public getCompletedQuizzes(): ItemId[] { - return this.completedQuizzes; - } - - /** - * Gets array of all quizzes that have been attempted. - * - * @returns {ItemId[]} + * Return an array containing [string, number] pairs + * representing quizzes and the corresponding scores. + * + * @returns {[string, number][]} */ - public getAttemptedQuizzes(): ItemId[] { - return this.attemptedQuizzes; + public getQuizScores(): [string, number][] { + return [...this.quizScores]; } public getGameMap = () => this.gameMap; From 01ea0e04e13d2049ae4cbd366654822a05083ebf Mon Sep 17 00:00:00 2001 From: reginateh Date: Sun, 14 Apr 2024 14:41:20 +0800 Subject: [PATCH 39/61] add validation for quiz conditions parameters --- src/features/game/parser/ConditionParser.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/features/game/parser/ConditionParser.ts b/src/features/game/parser/ConditionParser.ts index 47396b675e..7e5d1ac457 100644 --- a/src/features/game/parser/ConditionParser.ts +++ b/src/features/game/parser/ConditionParser.ts @@ -1,4 +1,5 @@ import { ActionCondition } from '../action/GameActionTypes'; +import { GameItemType } from '../location/GameMapTypes'; import { GameStateStorage } from '../state/GameStateTypes'; import StringUtils from '../utils/StringUtils'; import Parser from './Parser'; @@ -53,6 +54,7 @@ export default class ConditionParser { }; case GameStateStorage.AttemptedQuizState: + Parser.validator.assertItemType(GameItemType.quizzes, condParams[0]); return { state: GameStateStorage.AttemptedQuizState, conditionParams: { @@ -62,6 +64,7 @@ export default class ConditionParser { }; case GameStateStorage.CompletedQuizState: + Parser.validator.assertItemType(GameItemType.quizzes, condParams[0]); return { state: GameStateStorage.CompletedQuizState, conditionParams: { From 7175104dde71124c82ac4a8d2559b481acdf2401 Mon Sep 17 00:00:00 2001 From: reginateh Date: Sun, 14 Apr 2024 15:41:03 +0800 Subject: [PATCH 40/61] add new condition quizScoreAtLeast to check the status of quiz scores --- .../game/action/GameActionConditionChecker.ts | 4 +++- src/features/game/parser/ConditionParser.ts | 18 ++++++++++++++-- src/features/game/parser/ParserConverter.ts | 3 ++- .../game/scenes/gameManager/GameGlobalAPI.ts | 4 ++++ src/features/game/state/GameStateManager.ts | 21 ++++++++----------- src/features/game/state/GameStateTypes.ts | 3 ++- 6 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/features/game/action/GameActionConditionChecker.ts b/src/features/game/action/GameActionConditionChecker.ts index d66aa3ce63..d97a4eb58f 100644 --- a/src/features/game/action/GameActionConditionChecker.ts +++ b/src/features/game/action/GameActionConditionChecker.ts @@ -44,8 +44,10 @@ export default class ActionConditionChecker { return GameGlobalAPI.getInstance().isTaskComplete(conditionParams.id) === boolean; case GameStateStorage.AttemptedQuizState: return GameGlobalAPI.getInstance().isQuizAttempted(conditionParams.id) === boolean; - case GameStateStorage.CompletedQuizState: + case GameStateStorage.PassedQuizState: return GameGlobalAPI.getInstance().isQuizComplete(conditionParams.id) === boolean; + case GameStateStorage.QuizScoreState: + return (GameGlobalAPI.getInstance().getQuizScore(conditionParams.id) >= parseInt(conditionParams.score)) === boolean; default: return true; } diff --git a/src/features/game/parser/ConditionParser.ts b/src/features/game/parser/ConditionParser.ts index 7e5d1ac457..a8b2821497 100644 --- a/src/features/game/parser/ConditionParser.ts +++ b/src/features/game/parser/ConditionParser.ts @@ -63,16 +63,30 @@ export default class ConditionParser { boolean: !hasExclamation }; - case GameStateStorage.CompletedQuizState: + case GameStateStorage.PassedQuizState: Parser.validator.assertItemType(GameItemType.quizzes, condParams[0]); return { - state: GameStateStorage.CompletedQuizState, + state: GameStateStorage.PassedQuizState, conditionParams: { id: condParams[0] }, boolean: !hasExclamation }; + case GameStateStorage.QuizScoreState: + Parser.validator.assertItemType(GameItemType.quizzes, condParams[0]); + if (Number.isNaN(parseInt(condParams[1]))) { + throw new Error('Parsing error: quiz score condition requires number as second param'); + } + return { + state: GameStateStorage.QuizScoreState, + conditionParams: { + id: condParams[0], + score: condParams[1] + }, + boolean: !hasExclamation + }; + default: throw new Error('Parsing error: Invalid condition param'); } diff --git a/src/features/game/parser/ParserConverter.ts b/src/features/game/parser/ParserConverter.ts index 85ad3025af..af75da9f7f 100644 --- a/src/features/game/parser/ParserConverter.ts +++ b/src/features/game/parser/ParserConverter.ts @@ -68,7 +68,8 @@ const stringToGameStateStorageMap = { tasklist: GameStateStorage.TasklistState, userstate: GameStateStorage.UserState, attemptedQuiz: GameStateStorage.AttemptedQuizState, - completedQuiz: GameStateStorage.CompletedQuizState + passedQuiz: GameStateStorage.PassedQuizState, + quizScoreAtLeast: GameStateStorage.QuizScoreState }; const stringToUserStateTypeMap = { diff --git a/src/features/game/scenes/gameManager/GameGlobalAPI.ts b/src/features/game/scenes/gameManager/GameGlobalAPI.ts index dbad9047ee..297bfda02e 100644 --- a/src/features/game/scenes/gameManager/GameGlobalAPI.ts +++ b/src/features/game/scenes/gameManager/GameGlobalAPI.ts @@ -540,6 +540,10 @@ class GameGlobalAPI { public setQuizScore(key: string, score: number): void { this.getGameManager().getStateManager().setQuizScore(key, score); } + + public getQuizScore(key: string): number { + return this.getGameManager().getStateManager().getQuizScore(key); + } } export default GameGlobalAPI; diff --git a/src/features/game/state/GameStateManager.ts b/src/features/game/state/GameStateManager.ts index 29c6222617..78408689ac 100644 --- a/src/features/game/state/GameStateManager.ts +++ b/src/features/game/state/GameStateManager.ts @@ -477,30 +477,27 @@ class GameStateManager { /** * Get the score of a quiz. - * Return undefined if the quiz has not been played. + * Return 0 if the quiz has not been played. * * @param quizId * @returns */ - public getQuizScore(quizId: ItemId) { - return this.quizScores.get(quizId); + public getQuizScore(quizId: ItemId): number { + const score = this.quizScores.get(quizId); + if (score) { + return score; + } + return 0; } /** - * Set the score of a quiz to a given number - * if the new score is higher than the current score, - * or the quiz has not been played. + * Set the score of a quiz to a given number. * * @param quizId The id of the quiz. * @param newScore The new score to be set. */ public setQuizScore(quizId: string, newScore: number) { - const currentScore = this.getQuizScore(quizId); - if (currentScore && currentScore < newScore) { - this.quizScores.set(quizId, newScore); - } else if (!currentScore) { - this.quizScores.set(quizId, newScore); - } + this.quizScores.set(quizId, newScore); } /////////////////////////////// diff --git a/src/features/game/state/GameStateTypes.ts b/src/features/game/state/GameStateTypes.ts index 451df28144..b881e5ef34 100644 --- a/src/features/game/state/GameStateTypes.ts +++ b/src/features/game/state/GameStateTypes.ts @@ -5,7 +5,8 @@ export enum GameStateStorage { ChecklistState = 'ChecklistState', TasklistState = 'TasklistState', AttemptedQuizState = 'AttemptedQuizState', - CompletedQuizState = 'CompletedQuizState' + PassedQuizState = 'PassedQuizState', + QuizScoreState = 'QuizScoreState' } /** From 152dd6872056eff1226e0dd516d4803b03033d67 Mon Sep 17 00:00:00 2001 From: reginateh Date: Sun, 14 Apr 2024 15:56:33 +0800 Subject: [PATCH 41/61] support Interpolation of player's name in quiz questions --- src/features/game/quiz/GameQuizManager.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 4a67679be0..88aa529516 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -89,7 +89,8 @@ export default class QuizManager { //Create text writer to display quiz questions const quizQuestionWriter = createTypewriter(scene, questionTextStyle); - quizQuestionWriter.changeLine(question.question); + const lineWithName = question.question.replace('{name}', this.getUsername()); + quizQuestionWriter.changeLine(lineWithName); GameGlobalAPI.getInstance().storeDialogueLine(question.question, question.speaker); @@ -240,4 +241,6 @@ export default class QuizManager { const quiz = GameGlobalAPI.getInstance().getQuizById(quizId); return quiz.questions.length; } + + private getUsername = () => SourceAcademyGame.getInstance().getAccountInfo().name; } From ff6eb381a0f6b80156d883d3e4f08d6a9eb5555c Mon Sep 17 00:00:00 2001 From: reginateh Date: Sun, 14 Apr 2024 15:59:45 +0800 Subject: [PATCH 42/61] minor change --- src/features/game/quiz/GameQuizManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 88aa529516..483821d627 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -92,7 +92,7 @@ export default class QuizManager { const lineWithName = question.question.replace('{name}', this.getUsername()); quizQuestionWriter.changeLine(lineWithName); - GameGlobalAPI.getInstance().storeDialogueLine(question.question, question.speaker); + GameGlobalAPI.getInstance().storeDialogueLine(lineWithName, question.speaker); //Generate UI components for quizzes const header = new Phaser.GameObjects.Text( From 5572aef8053e745c18c5f144d4974f0b696b7b0b Mon Sep 17 00:00:00 2001 From: reginateh Date: Sun, 14 Apr 2024 16:44:28 +0800 Subject: [PATCH 43/61] support interpolation of quiz scores in dialogue lines --- .../game/dialogue/GameDialogueManager.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/features/game/dialogue/GameDialogueManager.ts b/src/features/game/dialogue/GameDialogueManager.ts index 4966b37b3f..528b9d651d 100644 --- a/src/features/game/dialogue/GameDialogueManager.ts +++ b/src/features/game/dialogue/GameDialogueManager.ts @@ -71,7 +71,7 @@ export default class DialogueManager { const { line, speakerDetail, actionIds, prompt } = await this.getDialogueGenerator().generateNextLine(); const lineWithName = line.replace('{name}', this.getUsername()); - this.getDialogueRenderer().changeText(lineWithName); + this.getDialogueRenderer().changeText(this.lineWithQuizScores(lineWithName)); this.getSpeakerRenderer().changeSpeakerTo(speakerDetail); // Store the current line into the storage @@ -120,6 +120,25 @@ export default class DialogueManager { await this.getSpeakerRenderer().show(); } + /** + * Find patterns of quiz score interpolation in a dialogue line, + * and replace them by actual scores. + * The pattern: "{.score}" + * + * @param line + * @returns {string} the given line with all quiz score interpolation replaced by actual scores. + */ + public lineWithQuizScores(line: string) { + const quizScores = line.match(/\{.+?\.score\}/g); + if (quizScores) { + quizScores.forEach(match => { + const quizId = match.substring(1, match.lastIndexOf('.')); + line = line.replace(match, GameGlobalAPI.getInstance().getQuizScore(quizId).toString()); + }) + } + return line; + } + private getDialogueGenerator = () => this.dialogueGenerator as DialogueGenerator; public getDialogueRenderer = () => this.dialogueRenderer as DialogueRenderer; private getSpeakerRenderer = () => this.speakerRenderer as DialogueSpeakerRenderer; From 66c68b25cd5a2e0e046e2f60b72ce004efdae3d5 Mon Sep 17 00:00:00 2001 From: reginateh Date: Sun, 14 Apr 2024 22:56:45 +0800 Subject: [PATCH 44/61] remove default result message & minor changes --- .../game/dialogue/GameDialogueManager.ts | 7 ++-- src/features/game/quiz/GameQuizConstants.ts | 6 ---- src/features/game/quiz/GameQuizManager.ts | 32 ------------------- 3 files changed, 4 insertions(+), 41 deletions(-) diff --git a/src/features/game/dialogue/GameDialogueManager.ts b/src/features/game/dialogue/GameDialogueManager.ts index 528b9d651d..09870036e6 100644 --- a/src/features/game/dialogue/GameDialogueManager.ts +++ b/src/features/game/dialogue/GameDialogueManager.ts @@ -70,8 +70,9 @@ export default class DialogueManager { GameGlobalAPI.getInstance().playSound(SoundAssets.dialogueAdvance.key); const { line, speakerDetail, actionIds, prompt } = await this.getDialogueGenerator().generateNextLine(); - const lineWithName = line.replace('{name}', this.getUsername()); - this.getDialogueRenderer().changeText(this.lineWithQuizScores(lineWithName)); + const lineWithQuizScores = this.makeLineWithQuizScores(line); + const lineWithName = lineWithQuizScores.replace('{name}', this.getUsername()); + this.getDialogueRenderer().changeText(lineWithName); this.getSpeakerRenderer().changeSpeakerTo(speakerDetail); // Store the current line into the storage @@ -128,7 +129,7 @@ export default class DialogueManager { * @param line * @returns {string} the given line with all quiz score interpolation replaced by actual scores. */ - public lineWithQuizScores(line: string) { + public makeLineWithQuizScores(line: string) { const quizScores = line.match(/\{.+?\.score\}/g); if (quizScores) { quizScores.forEach(match => { diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index 1d05fe326e..c454af7be5 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -10,12 +10,6 @@ export const startPrompt = { export const questionPrompt = 'What is the correct answer?'; -export const resultMsg = { - message: 'You got {numOfCorrect} out of {numOfQns} questions correct. ', - allCorrect: 'Well done!', - notAllCorrect: "Let's keep going!" -}; - export const QuizConstants = { textPad: 20, textConfig: { x: 15, y: -15, oriX: 0.5, oriY: 0.5 }, diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 483821d627..9a7cefb928 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -1,6 +1,5 @@ import ImageAssets from '../assets/ImageAssets'; import SoundAssets from '../assets/SoundAssets'; -import { SpeakerDetail } from '../character/GameCharacterTypes'; import { Constants, screenSize } from '../commons/CommonConstants'; import { ItemId } from '../commons/CommonTypes'; import { createDialogueBox, createTypewriter } from '../dialogue/GameDialogueHelper'; @@ -20,7 +19,6 @@ import { QuizConstants, quizOptStyle, quizTextStyle, - resultMsg, startPrompt } from './GameQuizConstants'; import GameQuizReactionManager from './GameQuizReactionManager'; @@ -53,7 +51,6 @@ export default class QuizManager { ); } GameGlobalAPI.getInstance().setQuizScore(quizId, numOfCorrect); - await this.showResult(numOfQns, numOfCorrect, quiz.questions[0].speaker); await GameGlobalAPI.getInstance().showNextLine(); await GameGlobalAPI.getInstance().getGameManager().getDialogueManager().showAll(); } @@ -203,35 +200,6 @@ export default class QuizManager { await this.reactionManager.showReaction(); } - /** - * Show the final score of the quiz as a quiz reaction. - * - * @param numOfQns The number of questions of the quiz. - * @param numOfCorrect The number of correctly answered questions. - */ - private async showResult(numOfQns: number, numOfCorrect: number, speaker: SpeakerDetail) { - await this.showReaction(this.makeResultMsg(numOfQns, numOfCorrect, speaker)); - } - - /** - * Create DialogueObject containing the message of quiz score. - * - * @param numOfQns The number of questions of the quiz. - * @param numOfCorrect The number of correctly answered questions. - * @returns A DialogueObject containing the message of quiz score. - */ - private makeResultMsg( - numOfQns: number, - numOfCorrect: number, - speaker: SpeakerDetail - ): DialogueObject { - let line = resultMsg.message - .replace('{numOfCorrect}', numOfCorrect.toString()) - .replace('{numOfQns}', numOfQns.toString()); - line += numOfCorrect === numOfQns ? resultMsg.allCorrect : resultMsg.notAllCorrect; - return new Map([['0', [{ line: line, speakerDetail: speaker }]]]); - } - /** * Get the number of questions of a quiz. * From 1df31b919fcc0a7df0b9167e91327881e737c56b Mon Sep 17 00:00:00 2001 From: reginateh Date: Sun, 14 Apr 2024 23:06:33 +0800 Subject: [PATCH 45/61] format --- src/features/game/action/GameActionConditionChecker.ts | 6 +++++- src/features/game/dialogue/GameDialogueManager.ts | 6 +++--- src/features/game/quiz/GameQuizManager.ts | 2 +- src/features/game/state/GameStateManager.ts | 10 +++++----- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/features/game/action/GameActionConditionChecker.ts b/src/features/game/action/GameActionConditionChecker.ts index d97a4eb58f..a112a63202 100644 --- a/src/features/game/action/GameActionConditionChecker.ts +++ b/src/features/game/action/GameActionConditionChecker.ts @@ -47,7 +47,11 @@ export default class ActionConditionChecker { case GameStateStorage.PassedQuizState: return GameGlobalAPI.getInstance().isQuizComplete(conditionParams.id) === boolean; case GameStateStorage.QuizScoreState: - return (GameGlobalAPI.getInstance().getQuizScore(conditionParams.id) >= parseInt(conditionParams.score)) === boolean; + return ( + GameGlobalAPI.getInstance().getQuizScore(conditionParams.id) >= + parseInt(conditionParams.score) === + boolean + ); default: return true; } diff --git a/src/features/game/dialogue/GameDialogueManager.ts b/src/features/game/dialogue/GameDialogueManager.ts index 09870036e6..ede65241d7 100644 --- a/src/features/game/dialogue/GameDialogueManager.ts +++ b/src/features/game/dialogue/GameDialogueManager.ts @@ -125,8 +125,8 @@ export default class DialogueManager { * Find patterns of quiz score interpolation in a dialogue line, * and replace them by actual scores. * The pattern: "{.score}" - * - * @param line + * + * @param line * @returns {string} the given line with all quiz score interpolation replaced by actual scores. */ public makeLineWithQuizScores(line: string) { @@ -135,7 +135,7 @@ export default class DialogueManager { quizScores.forEach(match => { const quizId = match.substring(1, match.lastIndexOf('.')); line = line.replace(match, GameGlobalAPI.getInstance().getQuizScore(quizId).toString()); - }) + }); } return line; } diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 9a7cefb928..b4bbd60cb8 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -202,7 +202,7 @@ export default class QuizManager { /** * Get the number of questions of a quiz. - * + * * @param quizId The Id of quiz. */ public getNumOfQns(quizId: ItemId): number { diff --git a/src/features/game/state/GameStateManager.ts b/src/features/game/state/GameStateManager.ts index 78408689ac..b2397dec02 100644 --- a/src/features/game/state/GameStateManager.ts +++ b/src/features/game/state/GameStateManager.ts @@ -478,9 +478,9 @@ class GameStateManager { /** * Get the score of a quiz. * Return 0 if the quiz has not been played. - * - * @param quizId - * @returns + * + * @param quizId + * @returns */ public getQuizScore(quizId: ItemId): number { const score = this.quizScores.get(quizId); @@ -492,7 +492,7 @@ class GameStateManager { /** * Set the score of a quiz to a given number. - * + * * @param quizId The id of the quiz. * @param newScore The new score to be set. */ @@ -553,7 +553,7 @@ class GameStateManager { /** * Return an array containing [string, number] pairs * representing quizzes and the corresponding scores. - * + * * @returns {[string, number][]} */ public getQuizScores(): [string, number][] { From 6119365c35635b29654d7a9b546f6f9bede74d1f Mon Sep 17 00:00:00 2001 From: reginateh Date: Sun, 14 Apr 2024 23:18:16 +0800 Subject: [PATCH 46/61] minor change --- src/features/game/parser/ParserConverter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/game/parser/ParserConverter.ts b/src/features/game/parser/ParserConverter.ts index af75da9f7f..05c48366c0 100644 --- a/src/features/game/parser/ParserConverter.ts +++ b/src/features/game/parser/ParserConverter.ts @@ -69,7 +69,7 @@ const stringToGameStateStorageMap = { userstate: GameStateStorage.UserState, attemptedQuiz: GameStateStorage.AttemptedQuizState, passedQuiz: GameStateStorage.PassedQuizState, - quizScoreAtLeast: GameStateStorage.QuizScoreState + quizScore: GameStateStorage.QuizScoreState }; const stringToUserStateTypeMap = { From 6953459107c1578548597535bf47d0b13cecf7cc Mon Sep 17 00:00:00 2001 From: CHEN YIXUN <138369841+CYX22222003@users.noreply.github.com> Date: Sun, 12 May 2024 20:32:00 +0800 Subject: [PATCH 47/61] Update GameDialogueManager.ts Modify getDialogueRenderer method access modifier --- src/features/game/dialogue/GameDialogueManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/game/dialogue/GameDialogueManager.ts b/src/features/game/dialogue/GameDialogueManager.ts index ede65241d7..06a9fe0d37 100644 --- a/src/features/game/dialogue/GameDialogueManager.ts +++ b/src/features/game/dialogue/GameDialogueManager.ts @@ -141,7 +141,7 @@ export default class DialogueManager { } private getDialogueGenerator = () => this.dialogueGenerator as DialogueGenerator; - public getDialogueRenderer = () => this.dialogueRenderer as DialogueRenderer; + private getDialogueRenderer = () => this.dialogueRenderer as DialogueRenderer; private getSpeakerRenderer = () => this.speakerRenderer as DialogueSpeakerRenderer; private getInputManager = () => this.gameInputManager as GameInputManager; From e005eb73b47901010f4aefab9f3caca4ae83332a Mon Sep 17 00:00:00 2001 From: CHEN YIXUN <138369841+CYX22222003@users.noreply.github.com> Date: Sun, 12 May 2024 20:34:18 +0800 Subject: [PATCH 48/61] Update GameSaveTypes.ts correct documentation errors --- src/features/game/save/GameSaveTypes.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/features/game/save/GameSaveTypes.ts b/src/features/game/save/GameSaveTypes.ts index 65ddb05dcf..f520f39e17 100644 --- a/src/features/game/save/GameSaveTypes.ts +++ b/src/features/game/save/GameSaveTypes.ts @@ -20,8 +20,7 @@ export type FullSaveState = { * @prop {string[]} completedObjectives - list of objectives that have been completed by player * @prop {string[]} triggeredInteractions - list of itemIds that have been triggered by player * @prop {string[]} triggeredActions - list of actions that have been triggered by player - * @prop {string[]} attemptedQuizzes - list of quiz ids that have been attempted by player - * @prop {string[]} completedQuizzes - list of quiz ids that have been completed by player + * @prop {[string, number][]} quizScores - list of quiz ids and the corresponding quiz scores for a user */ export type GameSaveState = { lastCheckpointPlayed: number; From cacd2f2d98f27ef944ef885fbd582464269edd31 Mon Sep 17 00:00:00 2001 From: CHEN YIXUN <138369841+CYX22222003@users.noreply.github.com> Date: Sun, 12 May 2024 20:40:16 +0800 Subject: [PATCH 49/61] Update GameStateManager.ts Remove the unnecessary use of parameterized type --- src/features/game/state/GameStateManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/game/state/GameStateManager.ts b/src/features/game/state/GameStateManager.ts index a8e604b2ac..92f615bc31 100644 --- a/src/features/game/state/GameStateManager.ts +++ b/src/features/game/state/GameStateManager.ts @@ -84,7 +84,7 @@ class GameStateManager { this.checkpointTask.showTask(task); }); - this.quizScores = new Map(this.getSaveManager().getQuizScores()); + this.quizScores = new Map(this.getSaveManager().getQuizScores()); this.chapterNewlyCompleted = this.getSaveManager().getChapterNewlyCompleted(); } From 0b232eaa0ed12e096f1469e09d5ee1762e4a018a Mon Sep 17 00:00:00 2001 From: CHEN YIXUN <138369841+CYX22222003@users.noreply.github.com> Date: Sun, 12 May 2024 20:42:27 +0800 Subject: [PATCH 50/61] Update src/features/game/state/GameStateManager.ts simplify unnecessary conditional statement Co-authored-by: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> --- src/features/game/state/GameStateManager.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/features/game/state/GameStateManager.ts b/src/features/game/state/GameStateManager.ts index 92f615bc31..b9c708ad6d 100644 --- a/src/features/game/state/GameStateManager.ts +++ b/src/features/game/state/GameStateManager.ts @@ -491,10 +491,7 @@ class GameStateManager { */ public getQuizScore(quizId: ItemId): number { const score = this.quizScores.get(quizId); - if (score) { - return score; - } - return 0; + return score ?? 0; } /** From 54ec50eef4c6feba24771f946e419cbe19737e2a Mon Sep 17 00:00:00 2001 From: CHEN YIXUN <138369841+CYX22222003@users.noreply.github.com> Date: Thu, 16 May 2024 12:18:50 +0800 Subject: [PATCH 51/61] Update src/features/game/dialogue/GameDialogueManager.ts refactor the logic of makeLineQuizScores method Co-authored-by: reginateh <154109919+reginateh@users.noreply.github.com> --- src/features/game/dialogue/GameDialogueManager.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/features/game/dialogue/GameDialogueManager.ts b/src/features/game/dialogue/GameDialogueManager.ts index 06a9fe0d37..8d6830eaf5 100644 --- a/src/features/game/dialogue/GameDialogueManager.ts +++ b/src/features/game/dialogue/GameDialogueManager.ts @@ -130,12 +130,9 @@ export default class DialogueManager { * @returns {string} the given line with all quiz score interpolation replaced by actual scores. */ public makeLineWithQuizScores(line: string) { - const quizScores = line.match(/\{.+?\.score\}/g); - if (quizScores) { - quizScores.forEach(match => { - const quizId = match.substring(1, match.lastIndexOf('.')); - line = line.replace(match, GameGlobalAPI.getInstance().getQuizScore(quizId).toString()); - }); + const quizScores = line.matchAll(/\{(.+?)\.score\}/g); + for (const match of quizScores) { + line = line.replace(match[0], GameGlobalAPI.getInstance().getQuizScore(match[1]).toString()); } return line; } From 1feee8bb7c87dabf2c4032f1cd3d91e5e9065d8c Mon Sep 17 00:00:00 2001 From: CHEN YIXUN <138369841+CYX22222003@users.noreply.github.com> Date: Thu, 16 May 2024 12:22:22 +0800 Subject: [PATCH 52/61] Update GameQuizManager.ts remove unnecessary comment line --- src/features/game/quiz/GameQuizManager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index b4bbd60cb8..92cab93dc0 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -185,7 +185,6 @@ export default class QuizManager { ...rightSideExitTweenProps }); - //await sleep(rightSideExitTweenProps.duration); fadeAndDestroy(scene, quizContainer, { fadeDuration: Constants.fadeDuration }); return response; } From 7c5b4bd40fe45e9e53c33ce8b369844258806014 Mon Sep 17 00:00:00 2001 From: CHEN YIXUN <138369841+CYX22222003@users.noreply.github.com> Date: Thu, 16 May 2024 12:30:04 +0800 Subject: [PATCH 53/61] Update GameDialogueSpeakerRenderer.ts change method access modifiers --- src/features/game/dialogue/GameDialogueSpeakerRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/game/dialogue/GameDialogueSpeakerRenderer.ts b/src/features/game/dialogue/GameDialogueSpeakerRenderer.ts index 083f6b11c1..385cfcfeae 100644 --- a/src/features/game/dialogue/GameDialogueSpeakerRenderer.ts +++ b/src/features/game/dialogue/GameDialogueSpeakerRenderer.ts @@ -114,6 +114,6 @@ export default class DialogueSpeakerRenderer { } public getUsername = () => SourceAcademyGame.getInstance().getAccountInfo().name; - public getSpeakerSprite = () => this.speakerSprite as Phaser.GameObjects.Image; - public getSpeakerSpriteBox = () => this.speakerSpriteBox as Phaser.GameObjects.Container; + private getSpeakerSprite = () => this.speakerSprite as Phaser.GameObjects.Image; + private getSpeakerSpriteBox = () => this.speakerSpriteBox as Phaser.GameObjects.Container; } From 4960cb98efd2161b15918f6cf42ba024960c48dd Mon Sep 17 00:00:00 2001 From: CHEN YIXUN <138369841+CYX22222003@users.noreply.github.com> Date: Thu, 16 May 2024 12:30:53 +0800 Subject: [PATCH 54/61] Update GameQuizSpeakerRenderer.ts change method access modifiers --- src/features/game/quiz/GameQuizSpeakerRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/game/quiz/GameQuizSpeakerRenderer.ts b/src/features/game/quiz/GameQuizSpeakerRenderer.ts index 2173521b4e..29b212abc8 100644 --- a/src/features/game/quiz/GameQuizSpeakerRenderer.ts +++ b/src/features/game/quiz/GameQuizSpeakerRenderer.ts @@ -114,6 +114,6 @@ export class QuizSpeakerRenderer { } public getUsername = () => SourceAcademyGame.getInstance().getAccountInfo().name; - public getSpeakerSprite = () => this.speakerSprite as Phaser.GameObjects.Image; - public getSpeakerSpriteBox = () => this.speakerSpriteBox as Phaser.GameObjects.Container; + private getSpeakerSprite = () => this.speakerSprite as Phaser.GameObjects.Image; + private getSpeakerSpriteBox = () => this.speakerSpriteBox as Phaser.GameObjects.Container; } From c455348fb9c03a043b428de28a935ca50dc5753d Mon Sep 17 00:00:00 2001 From: CHEN YIXUN <138369841+CYX22222003@users.noreply.github.com> Date: Thu, 16 May 2024 12:36:04 +0800 Subject: [PATCH 55/61] Update src/features/game/dialogue/GameDialogueRenderer.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit reformatting Co-authored-by: Lee Hyung Woon / 이형운 --- src/features/game/dialogue/GameDialogueRenderer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/features/game/dialogue/GameDialogueRenderer.ts b/src/features/game/dialogue/GameDialogueRenderer.ts index ce573fdce6..45ba3e6f68 100644 --- a/src/features/game/dialogue/GameDialogueRenderer.ts +++ b/src/features/game/dialogue/GameDialogueRenderer.ts @@ -19,7 +19,6 @@ class DialogueRenderer { */ constructor(typewriterStyle: Phaser.Types.GameObjects.Text.TextStyle) { const gameManager = GameGlobalAPI.getInstance().getGameManager(); - this.dialogueBox = createDialogueBox(gameManager).setInteractive({ useHandCursor: true, pixelPerfect: true From e21f29266c1f39ed6f626af0bbc311e7679178d5 Mon Sep 17 00:00:00 2001 From: CHEN YIXUN <138369841+CYX22222003@users.noreply.github.com> Date: Thu, 16 May 2024 18:13:51 +0800 Subject: [PATCH 56/61] Update GameQuizManager.ts change variable name --- src/features/game/quiz/GameQuizManager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 92cab93dc0..edaa3fbafb 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -34,8 +34,8 @@ export default class QuizManager { */ public async showQuiz(quizId: ItemId) { const quiz = GameGlobalAPI.getInstance().getQuizById(quizId); - const numOfQns = quiz.questions.length; - if (numOfQns === 0) { + const numOfQuestions = quiz.questions.length; + if (numOfQuestions === 0) { return; } if (!(await this.showStartPrompt(GameGlobalAPI.getInstance().getGameManager()))) { @@ -44,7 +44,7 @@ export default class QuizManager { } await GameGlobalAPI.getInstance().getGameManager().getDialogueManager().hideAll(); let numOfCorrect = 0; - for (let i = 0; i < numOfQns; i++) { + for (let i = 0; i < numOfQuestions; i++) { numOfCorrect += await this.showQuizQuestion( GameGlobalAPI.getInstance().getGameManager(), quiz.questions[i] From e33cb15ab4d7c8c1ff47a4a8b1782b51747d523b Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Thu, 16 May 2024 18:24:16 +0800 Subject: [PATCH 57/61] Update GameQuizManager and GameQuizConstants change the parameters of game components setting --- src/features/game/quiz/GameQuizConstants.ts | 3 ++- src/features/game/quiz/GameQuizManager.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/features/game/quiz/GameQuizConstants.ts b/src/features/game/quiz/GameQuizConstants.ts index c454af7be5..9f5a34337b 100644 --- a/src/features/game/quiz/GameQuizConstants.ts +++ b/src/features/game/quiz/GameQuizConstants.ts @@ -17,7 +17,8 @@ export const QuizConstants = { width: 450, yInterval: 100, headerOff: 60, - speakerTextConfig: { x: 320, y: 745, oriX: 0.5, oriY: 0.5 } + speakerTextConfig: { x: 320, y: 745, oriX: 0.5, oriY: 0.5 }, + optionsYOffSet: 75 }; export const quizTextStyle = { diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index edaa3fbafb..a18f81de90 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -159,7 +159,7 @@ export default class QuizManager { QuizConstants.width * (quizPartitions - Math.floor(index / 5) - 1), (buttonPositions[index][1] % (5 * QuizConstants.yInterval)) + quizHeaderBg.getBounds().bottom + - 75 + QuizConstants.optionsYOffSet ) ) ); From 3ba899190aa5f5c00d10b9c6a950f3dc6682a1df Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Thu, 16 May 2024 22:59:06 +0800 Subject: [PATCH 58/61] Modifying showNextLine method in GameGlobalAPI --- src/features/game/quiz/GameQuizManager.ts | 4 ++-- src/features/game/scenes/gameManager/GameGlobalAPI.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index a18f81de90..8cc9f98b83 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -39,7 +39,7 @@ export default class QuizManager { return; } if (!(await this.showStartPrompt(GameGlobalAPI.getInstance().getGameManager()))) { - await GameGlobalAPI.getInstance().showNextLine(); + await GameGlobalAPI.getInstance().showNextLine(() => {}); return; } await GameGlobalAPI.getInstance().getGameManager().getDialogueManager().hideAll(); @@ -51,7 +51,7 @@ export default class QuizManager { ); } GameGlobalAPI.getInstance().setQuizScore(quizId, numOfCorrect); - await GameGlobalAPI.getInstance().showNextLine(); + await GameGlobalAPI.getInstance().showNextLine(() => {}); await GameGlobalAPI.getInstance().getGameManager().getDialogueManager().showAll(); } diff --git a/src/features/game/scenes/gameManager/GameGlobalAPI.ts b/src/features/game/scenes/gameManager/GameGlobalAPI.ts index 297bfda02e..1ee931f8e0 100644 --- a/src/features/game/scenes/gameManager/GameGlobalAPI.ts +++ b/src/features/game/scenes/gameManager/GameGlobalAPI.ts @@ -284,10 +284,10 @@ class GameGlobalAPI { await this.getGameManager().getDialogueManager().showDialogue(dialogueId); } - public async showNextLine() { + public async showNextLine(resolve : () => void) { await this.getGameManager() .getDialogueManager() - .showNextLine(() => {}); + .showNextLine(resolve); } ///////////////////// From b3f268f3f6e50a5c9c2e30b9e828f79b943c5f64 Mon Sep 17 00:00:00 2001 From: "guest1@CHENYIXUN" Date: Thu, 16 May 2024 23:11:44 +0800 Subject: [PATCH 59/61] Reformatting --- src/features/game/scenes/gameManager/GameGlobalAPI.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/features/game/scenes/gameManager/GameGlobalAPI.ts b/src/features/game/scenes/gameManager/GameGlobalAPI.ts index 1ee931f8e0..2b9e334c3f 100644 --- a/src/features/game/scenes/gameManager/GameGlobalAPI.ts +++ b/src/features/game/scenes/gameManager/GameGlobalAPI.ts @@ -284,10 +284,8 @@ class GameGlobalAPI { await this.getGameManager().getDialogueManager().showDialogue(dialogueId); } - public async showNextLine(resolve : () => void) { - await this.getGameManager() - .getDialogueManager() - .showNextLine(resolve); + public async showNextLine(resolve: () => void) { + await this.getGameManager().getDialogueManager().showNextLine(resolve); } ///////////////////// From 4e639f4465fee7367819bbbe45ad0206db81b8f3 Mon Sep 17 00:00:00 2001 From: CHEN YIXUN <138369841+CYX22222003@users.noreply.github.com> Date: Mon, 20 May 2024 11:09:29 +0800 Subject: [PATCH 60/61] Update src/features/game/parser/QuizParser.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct parsers' behavior for errors Co-authored-by: Lee Hyung Woon / 이형운 --- src/features/game/parser/QuizParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/game/parser/QuizParser.ts b/src/features/game/parser/QuizParser.ts index 485af8ff9c..05db7bb019 100644 --- a/src/features/game/parser/QuizParser.ts +++ b/src/features/game/parser/QuizParser.ts @@ -21,7 +21,7 @@ export default class QuizParser { const quizParagraphs = StringUtils.splitToParagraph(quizText); quizParagraphs.forEach(([quizId, quizBody]: [string, string[]]) => { if (quizBody.length === 0) { - console.error('No quiz content found for quizId'); + throw new Error('Parsing error: No quiz content found for quizId'); return; } this.parseQuiz(quizId, quizBody); From 86da80bb945e809dd28c0fb3bd5036b067db6177 Mon Sep 17 00:00:00 2001 From: CHENYIXUN Date: Mon, 5 Aug 2024 00:19:33 +0800 Subject: [PATCH 61/61] Update Prompt field for game object --- src/features/game/quiz/GameQuizManager.ts | 4 ++-- src/features/game/quiz/GameQuizType.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/features/game/quiz/GameQuizManager.ts b/src/features/game/quiz/GameQuizManager.ts index 8cc9f98b83..58b575b78c 100644 --- a/src/features/game/quiz/GameQuizManager.ts +++ b/src/features/game/quiz/GameQuizManager.ts @@ -77,7 +77,7 @@ export default class QuizManager { public async showQuizQuestion(scene: Phaser.Scene, question: Question) { const choices = question.options; const quizContainer = new Phaser.GameObjects.Container(scene, 0, 0); - + const selfQuestionPrompt = question.prompt ?? questionPrompt; const quizPartitions = Math.ceil(choices.length / 5); const quizHeight = choices.length; @@ -96,7 +96,7 @@ export default class QuizManager { scene, screenSize.x - QuizConstants.textPad, QuizConstants.y, - questionPrompt, + selfQuestionPrompt, quizTextStyle ).setOrigin(1.0, 0.0); diff --git a/src/features/game/quiz/GameQuizType.ts b/src/features/game/quiz/GameQuizType.ts index faa16c3494..34ee7c91ed 100644 --- a/src/features/game/quiz/GameQuizType.ts +++ b/src/features/game/quiz/GameQuizType.ts @@ -7,6 +7,7 @@ export type Quiz = { export type Question = { question: string; + prompt?: string; speaker: SpeakerDetail; answer: Number; options: Option[];