namespace pxt . tutorial { export interface TutorialRuleStatus { ruleName : string ; ruleTurnOn : boolean ; ruleStatus ? : boolean ; ruleMessage ? : string ; isStrict ? : boolean ; blockIds ? : string [ ] ; } param tutorial param workspaceBlocks param blockinfo return export async function validate ( tutorial , workspaceBlocks , blockinfo ) { const listOfRules = tutorial . tutorialValidationRules ; let TutorialRuleStatuses = classifyRules ( listOfRules ) ; if ( TutorialRuleStatuses . length > 0 && workspaceBlocks . length > 0 ) { const userBlockTypes = workspaceBlocks . map ( b => b . type ) ; const usersBlockUsed = blockCount ( userBlockTypes ) ; const { tutorialStepInfo , tutorialStep } = tutorial ; const step = tutorialStepInfo [ tutorialStep ] ; const indexdb = await tutorialBlockList ( tutorial , step ) ; const tutorialBlockUsed = extractBlockSnippet ( tutorial , indexdb ) ; for ( let i = 0 ; i < TutorialRuleStatuses . length ; i ++ ) { let currRuleToValidate = TutorialRuleStatuses [ i ] ; const ruleName = TutorialRuleStatuses [ i ] . ruleName ; const isRuleEnabled = TutorialRuleStatuses [ i ] . ruleTurnOn ; if ( isRuleEnabled ) { switch ( ruleName ) { case "exact" : currRuleToValidate = validateExactNumberOfBlocks ( usersBlockUsed , tutorialBlockUsed , currRuleToValidate ) ; break ; case "atleast" : currRuleToValidate = validateAtleastOneBlocks ( usersBlockUsed , tutorialBlockUsed , currRuleToValidate ) ; break ; case "required" : const requiredBlocksList = extractRequiredBlockSnippet ( tutorial , indexdb ) ; currRuleToValidate = validateMeetRequiredBlocks ( usersBlockUsed , requiredBlocksList , currRuleToValidate ) ; break ; } } } } return TutorialRuleStatuses ; } param listOfRules return function classifyRules ( listOfRules ) { let listOfRuleStatuses = [ ] ; if ( listOfRules != undefined ) { const ruleNames = Object . keys ( listOfRules ) ; for ( let i = 0 ; i < ruleNames . length ; i ++ ) { const currRule = ruleNames [ i ] ; const ruleVal = listOfRules [ currRule ] ; const currRuleStatus = { ruleName : currRule , ruleTurnOn : ruleVal } ; listOfRuleStatuses . push ( currRuleStatus ) ; } } return listOfRuleStatuses ; } param arr return function blockCount ( arr ) { let frequencyMap = { } ; for ( let i = 0 ; i < arr . length ; i ++ ) { if ( ! frequencyMap [ arr [ i ] ] ) { frequencyMap [ arr [ i ] ] = 0 ; } frequencyMap [ arr [ i ] ] = frequencyMap [ arr [ i ] ] + 1 ; } return frequencyMap ; } param tutorial param step return function tutorialBlockList ( tutorial , step ) { return pxt . BrowserUtils . tutorialInfoDbAsync ( ) . then ( db => db . getAsync ( tutorial . tutorial , tutorial . tutorialCode ) . then ( entry => { if ( entry ?. snippets ) { return Promise . resolve ( entry . snippets ) ; } else { return Promise . resolve ( undefined ) ; } } ) ) ; } param tutorial param indexdb return function extractBlockSnippet ( tutorial , indexdb ) { const { tutorialStepInfo , tutorialStep } = tutorial ; const body = tutorial . 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 ) { hintCode = `{\n${ m2 }\n}` ; return "" ; } ) ; } const snippetStepKey = pxt . BrowserUtils . getTutorialCodeHash ( [ hintCode ] ) ; let blockMap = { } ; if ( indexdb != undefined ) { blockMap = indexdb [ snippetStepKey ] ; } return blockMap ; } param tutorial param indexdb return function extractRequiredBlockSnippet ( tutorial , indexdb ) { const { tutorialStep } = tutorial ; const body = tutorial . tutorialStepInfo [ tutorialStep ] . requiredBlockMd ; const snippetStepKey = pxt . BrowserUtils . getTutorialCodeHash ( [ body ] ) ; let blockMap = { } ; if ( indexdb != undefined ) { blockMap = indexdb [ snippetStepKey ] ; } return blockMap ; } param usersBlockUsed param tutorialBlockUsed param currRule return function validateExactNumberOfBlocks ( usersBlockUsed , tutorialBlockUsed , currRule ) { currRule . isStrict = true ; const userBlockKeys = Object . keys ( usersBlockUsed ) ; let tutorialBlockKeys = [ ] ; let blockIds = [ ] ; if ( tutorialBlockUsed != undefined ) { tutorialBlockKeys = Object . keys ( tutorialBlockUsed ) ; } let isValid = userBlockKeys . length >= tutorialBlockKeys . length ; const message = lf ( "These are the blocks you seem to be missing:" ) ; for ( let i = 0 ; i < tutorialBlockKeys . length ; i ++ ) { let tutorialBlockKey = tutorialBlockKeys [ i ] ; if ( ! usersBlockUsed [ tutorialBlockKey ] || usersBlockUsed [ tutorialBlockKey ] < tutorialBlockUsed [ tutorialBlockKey ] ) { blockIds . push ( tutorialBlockKey ) ; isValid = false ; } } currRule . ruleMessage = message ; currRule . ruleStatus = isValid ; currRule . blockIds = blockIds ; return currRule ; } param usersBlockUsed param tutorialBlockUsed param currRule return function validateAtleastOneBlocks ( usersBlockUsed , tutorialBlockUsed , currRule ) { const userBlockKeys = Object . keys ( usersBlockUsed ) ; const tutorialBlockKeys = Object . keys ( tutorialBlockUsed ?? { } ) ; let isValid = userBlockKeys . length >= tutorialBlockKeys . length ; for ( let i = 0 ; i < tutorialBlockKeys . length ; i ++ ) { let tutorialBlockKey = tutorialBlockKeys [ i ] ; if ( ! usersBlockUsed [ tutorialBlockKey ] ) { isValid = false ; break ; } } currRule . ruleStatus = isValid ; return currRule ; } param usersBlockUsed param tutorialBlockUsed param currRule return function validateMeetRequiredBlocks ( usersBlockUsed , requiredBlocks , currRule ) { currRule . isStrict = true ; const userBlockKeys = Object . keys ( usersBlockUsed ) ; let requiredBlockKeys = [ ] ; let blockIds = [ ] ; if ( requiredBlocks != undefined ) { requiredBlockKeys = Object . keys ( requiredBlocks ) ; } let isValid = true ; const message = lf ( "You are required to have the following block:" ) ; for ( let i = 0 ; i < requiredBlockKeys . length ; i ++ ) { let requiredBlockKey = requiredBlockKeys [ i ] ; if ( ! usersBlockUsed [ requiredBlockKey ] ) { blockIds . push ( requiredBlockKey ) ; isValid = false ; } } currRule . ruleMessage = message ; currRule . ruleStatus = isValid ; currRule . blockIds = blockIds ; return currRule ; } }