Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Framework for Overlap Block Rule #8253

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions localtypings/pxtarget.d.ts
Expand Up @@ -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<boolean>; //a map of rules used in a tutorial and if the rules are activated
validationOverlapBlocks?: pxt.Map<string>; // a map of dropdown kinds used for a overlap block in a single tutorial
}

interface TutorialMetadata {
Expand Down Expand Up @@ -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<boolean>; //a map of rules used in a tutorial and if the rules are activated
validationOverlapBlocks?: pxt.Map<string>; // a map of dropdown kinds used for a overlap block in a single tutorial
}
interface TutorialCompletionInfo {
// id of the tutorial
Expand Down
20 changes: 15 additions & 5 deletions pxtlib/tutorial.ts
Expand Up @@ -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<boolean>;
let validationOverlapBlocks: pxt.Map<string>;
if (metadata.tutorialCodeValidation) {
tutorialValidationRules = pxt.Util.jsonTryParse(tutorialValidationRulesStr);
categorizingValidationRules(tutorialValidationRules, title);
if (validationOverlapStr) {
validationOverlapBlocks = pxt.Util.jsonTryParse(validationOverlapStr);
}
}

// noDiffs legacy
Expand Down Expand Up @@ -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) {
Expand All @@ -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")
Expand Down Expand Up @@ -115,14 +121,17 @@ namespace pxt.tutorial {
case "tutorialValidationRules":
tutorialValidationRulesStr = m2;
break;
case "validationOverlap":
validationOverlapStr = m2;
break;
}
code.push(m1 == "python" ? `\n${m2}\n` : `{\n${m2}\n}`);
idx++
return "";
});
// 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) {
Expand Down Expand Up @@ -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 };
Expand Down
47 changes: 39 additions & 8 deletions pxtlib/tutorialValidator.ts
Expand Up @@ -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<string> = {};
currRuleToValidate = validateOverlapBlock(usersBlockUsed, tutorialBlockUsed, tutorialDropdowns, userDropdowns, currRuleToValidate);
break;
}
}
}
Expand Down Expand Up @@ -117,7 +123,7 @@ namespace pxt.tutorial {
*/
function extractBlockSnippet(tutorial: TutorialOptions, indexdb: pxt.Map<pxt.Map<number>>) {
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) {
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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
*/
Expand All @@ -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<number>, requiredBlocks: pxt.Map<number>, currRule: TutorialRuleStatus): TutorialRuleStatus {
currRule.isStrict = true;
const userBlockKeys = Object.keys(usersBlockUsed);
let requiredBlockKeys: string[] = []
let blockIds = [];
if (requiredBlocks != undefined) {
Expand All @@ -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<number>, tutorialBlockUsed: pxt.Map<number>, tutorialDropdowns: pxt.Map<string>, userDropdowns: pxt.Map<string>, 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;
}
}
2 changes: 1 addition & 1 deletion webapp/src/tutorialCodeValidation.tsx
Expand Up @@ -73,7 +73,7 @@ export class ShowValidationMessage extends data.Component<TutorialCodeValidation
return <div>
<div className="codeValidationPopUpText">{rule.ruleTurnOn && !rule.ruleStatus ? rule.ruleMessage : ''}</div>
<div className="validationRendering">
{blockUris.map((blockUri, index) => <div> <img key={index + blockUri} src={blockUri} alt="block rendered" /></div>)}
{blockUris.map((blockUri, index) => <div> <img key={index + blockUri + rule.ruleName} src={blockUri} alt="block rendered" /></div>)}
</div>
</div>

Expand Down