From afcf635934911a628db3fd7274f2b6d045a9b789 Mon Sep 17 00:00:00 2001 From: Jasmine Woon Date: Fri, 18 Jun 2021 17:29:07 -0700 Subject: [PATCH] initial framework for overlap rule --- localtypings/pxtarget.d.ts | 2 ++ pxtlib/tutorial.ts | 20 +++++++++--- pxtlib/tutorialValidator.ts | 47 ++++++++++++++++++++++----- webapp/src/tutorialCodeValidation.tsx | 2 +- 4 files changed, 57 insertions(+), 14 deletions(-) diff --git a/localtypings/pxtarget.d.ts b/localtypings/pxtarget.d.ts index 52b35990fce..166308709cc 100644 --- a/localtypings/pxtarget.d.ts +++ b/localtypings/pxtarget.d.ts @@ -1045,6 +1045,7 @@ declare namespace pxt.tutorial { jres?: string; // JRES to be used when generating hints; necessary for tilemaps customTs?: string; // custom typescript code loaded in a separate file for the tutorial tutorialValidationRules?: pxt.Map; //a map of rules used in a tutorial and if the rules are activated + validationOverlapBlocks?: pxt.Map; // a map of dropdown kinds used for a overlap block in a single tutorial } interface TutorialMetadata { @@ -1112,6 +1113,7 @@ declare namespace pxt.tutorial { jres?: string; // JRES to be used when generating hints; necessary for tilemaps customTs?: string; // custom typescript code loaded in a separate file for the tutorial tutorialValidationRules?: pxt.Map; //a map of rules used in a tutorial and if the rules are activated + validationOverlapBlocks?: pxt.Map; // a map of dropdown kinds used for a overlap block in a single tutorial } interface TutorialCompletionInfo { // id of the tutorial diff --git a/pxtlib/tutorial.ts b/pxtlib/tutorial.ts index 9b89705b294..12a1d011c33 100644 --- a/pxtlib/tutorial.ts +++ b/pxtlib/tutorial.ts @@ -10,13 +10,17 @@ namespace pxt.tutorial { return undefined; // error parsing steps // collect code and infer editor - const { code, templateCode, editor, language, jres, assetJson, customTs, tutorialValidationRulesStr } = computeBodyMetadata(body); + const { code, templateCode, editor, language, jres, assetJson, customTs, tutorialValidationRulesStr, validationOverlapStr } = computeBodyMetadata(body); // parses tutorial rules string into a map of rules and enablement flag let tutorialValidationRules: pxt.Map; + let validationOverlapBlocks: pxt.Map; if (metadata.tutorialCodeValidation) { tutorialValidationRules = pxt.Util.jsonTryParse(tutorialValidationRulesStr); categorizingValidationRules(tutorialValidationRules, title); + if (validationOverlapStr) { + validationOverlapBlocks = pxt.Util.jsonTryParse(validationOverlapStr); + } } // noDiffs legacy @@ -52,12 +56,13 @@ namespace pxt.tutorial { jres, assetFiles, customTs, - tutorialValidationRules + tutorialValidationRules, + validationOverlapBlocks }; } export function getMetadataRegex(): RegExp { - return /``` *(sim|block|blocks|filterblocks|spy|ghost|typescript|ts|js|javascript|template|python|jres|assetjson|customts|tutorialValidationRules|requiredTutorialBlock)\s*\n([\s\S]*?)\n```/gmi; + return /``` *(sim|block|blocks|filterblocks|spy|ghost|typescript|ts|js|javascript|template|python|jres|assetjson|customts|tutorialValidationRules|requiredTutorialBlock|validationOverlap)\s*\n([\s\S]*?)\n```/gmi; } function computeBodyMetadata(body: string) { @@ -73,6 +78,7 @@ namespace pxt.tutorial { let assetJson: string; let customTs: string; let tutorialValidationRulesStr: string; + let validationOverlapStr: string; // Concatenate all blocks in separate code blocks and decompile so we can detect what blocks are used (for the toolbox) body .replace(/((?!.)\s)+/g, "\n") @@ -115,6 +121,9 @@ namespace pxt.tutorial { case "tutorialValidationRules": tutorialValidationRulesStr = m2; break; + case "validationOverlap": + validationOverlapStr = m2; + break; } code.push(m1 == "python" ? `\n${m2}\n` : `{\n${m2}\n}`); idx++ @@ -122,7 +131,7 @@ namespace pxt.tutorial { }); // default to blocks editor = editor || pxt.BLOCKS_PROJECT_NAME - return { code, templateCode, editor, language, jres, assetJson, customTs, tutorialValidationRulesStr } + return { code, templateCode, editor, language, jres, assetJson, customTs, tutorialValidationRulesStr, validationOverlapStr } function checkTutorialEditor(expected: string) { if (editor && editor != expected) { @@ -409,7 +418,8 @@ ${code} jres: tutorialInfo.jres, assetFiles: tutorialInfo.assetFiles, customTs: tutorialInfo.customTs, - tutorialValidationRules: tutorialInfo.tutorialValidationRules + tutorialValidationRules: tutorialInfo.tutorialValidationRules, + validationOverlapBlocks: tutorialInfo.validationOverlapBlocks }; return { options: tutorialOptions, editor: tutorialInfo.editor }; diff --git a/pxtlib/tutorialValidator.ts b/pxtlib/tutorialValidator.ts index a91dcaf9cd8..af18e41c677 100644 --- a/pxtlib/tutorialValidator.ts +++ b/pxtlib/tutorialValidator.ts @@ -47,6 +47,12 @@ namespace pxt.tutorial { const requiredBlocksList = extractRequiredBlockSnippet(tutorial, indexdb); currRuleToValidate = validateMeetRequiredBlocks(usersBlockUsed, requiredBlocksList, currRuleToValidate); break; + case "overlap": + const tutorialDropdowns = tutorial.validationOverlapBlocks; + // TODO: Shakao - need method + const userDropdowns: pxt.Map = {}; + currRuleToValidate = validateOverlapBlock(usersBlockUsed, tutorialBlockUsed, tutorialDropdowns, userDropdowns, currRuleToValidate); + break; } } } @@ -117,7 +123,7 @@ namespace pxt.tutorial { */ function extractBlockSnippet(tutorial: TutorialOptions, indexdb: pxt.Map>) { const { tutorialStepInfo, tutorialStep } = tutorial; - const body = tutorial.tutorialStepInfo[tutorialStep].hintContentMd; + const body = tutorialStepInfo[tutorialStep].hintContentMd; let hintCode = ""; if (body != undefined) { body.replace(/((?!.)\s)+/g, "\n").replace(/``` *(block|blocks)\s*\n([\s\S]*?)\n```/gmi, function (m0, m1, m2) { @@ -153,8 +159,8 @@ namespace pxt.tutorial { /** * Strict Rule: Checks if the all required number of blocks for a tutorial step is used, returns a TutorialRuleStatus - * @param usersBlockUsed an array of strings - * @param tutorialBlockUsed the next available index + * @param usersBlockUsed a map of user's blocks and count + * @param tutorialBlockUsed a map of tutorial blocks and count * @param currRule the current rule with its TutorialRuleStatus * @return a tutorial rule status for currRule */ @@ -184,8 +190,8 @@ namespace pxt.tutorial { /** * Passive Rule: Checks if the users has at least one block type for each rule - * @param usersBlockUsed an array of strings - * @param tutorialBlockUsed the next available index + * @param usersBlockUsed a map of user's blocks and count + * @param tutorialBlockUsed a map of tutorial blocks and count * @param currRule the current rule with its TutorialRuleStatus * @return a tutorial rule status for currRule */ @@ -206,14 +212,13 @@ namespace pxt.tutorial { /** * Strict Rule: Checks if the all required number of blocks for a tutorial step is used, returns a TutorialRuleStatus - * @param usersBlockUsed an array of strings - * @param tutorialBlockUsed the next available index + * @param usersBlockUsed a map of user's blocks and count + * @param tutorialBlockUsed a map of tutorial blocks and count * @param currRule the current rule with its TutorialRuleStatus * @return a tutorial rule status for currRule */ function validateMeetRequiredBlocks(usersBlockUsed: pxt.Map, requiredBlocks: pxt.Map, currRule: TutorialRuleStatus): TutorialRuleStatus { currRule.isStrict = true; - const userBlockKeys = Object.keys(usersBlockUsed); let requiredBlockKeys: string[] = [] let blockIds = []; if (requiredBlocks != undefined) { @@ -233,4 +238,30 @@ namespace pxt.tutorial { currRule.blockIds = blockIds; return currRule; } + + /** + * Strict Rule: Checks the overlap block kinds from the tutorial to the user's + * @param usersBlockUsed a map of user's blocks and count + * @param tutorialBlockUsed a map of tutorial blocks and count + * @param tutorialDropdowns a map of tutorial's dropdown kinds + * @param userDropdowns a map of user's dropdown kinds + * @param currRule the current rule with its TutorialRuleStatus + * @return a tutorial rule status for currRule + */ + function validateOverlapBlock(usersBlockUsed: pxt.Map, tutorialBlockUsed: pxt.Map, tutorialDropdowns: pxt.Map, userDropdowns: pxt.Map, currRule: TutorialRuleStatus): TutorialRuleStatus { + if (tutorialBlockUsed != undefined) { + currRule.isStrict = true; + let isValid = (!tutorialBlockUsed['spritesoverlap']); // if this tutorial step doesn't use an overlap, set valid to true + let message = lf("Double check overlap's block dropdowns. Are you using: "); + if (tutorialBlockUsed['spritesoverlap'] && usersBlockUsed['spritesoverlap']) { + isValid = (tutorialDropdowns['kindOne'] == userDropdowns['kindOne']) && (tutorialDropdowns['kindTwo'] == userDropdowns['kindTwo']); + if (!isValid) { + message = message + "kindOne: " + tutorialDropdowns['kindOne'] + " kindTwo: " + tutorialDropdowns['kindTwo']; + } + } + currRule.ruleMessage = message; + currRule.ruleStatus = isValid; + } + return currRule; + } } \ No newline at end of file diff --git a/webapp/src/tutorialCodeValidation.tsx b/webapp/src/tutorialCodeValidation.tsx index 8f4fe8074d9..1aae74b260c 100644 --- a/webapp/src/tutorialCodeValidation.tsx +++ b/webapp/src/tutorialCodeValidation.tsx @@ -73,7 +73,7 @@ export class ShowValidationMessage extends data.Component
{rule.ruleTurnOn && !rule.ruleStatus ? rule.ruleMessage : ''}
- {blockUris.map((blockUri, index) =>
block rendered
)} + {blockUris.map((blockUri, index) =>
block rendered
)}