UNPKG

@coorpacademy/progression-engine

Version:

301 lines (232 loc) 9.99 kB
'use strict'; exports.__esModule = true; exports.computeNextStepForNewChapter = exports.prepareStateToSwitchChapters = exports.nextSlidePool = undefined; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _map = require('lodash/fp/map'); var _map2 = _interopRequireDefault(_map); var _get = require('lodash/fp/get'); var _get2 = _interopRequireDefault(_get); var _find = require('lodash/fp/find'); var _find2 = _interopRequireDefault(_find); var _pipe = require('lodash/fp/pipe'); var _pipe2 = _interopRequireDefault(_pipe); var _head = require('lodash/fp/head'); var _head2 = _interopRequireDefault(_head); var _last = require('lodash/fp/last'); var _last2 = _interopRequireDefault(_last); var _filter = require('lodash/fp/filter'); var _filter2 = _interopRequireDefault(_filter); var _sortBy = require('lodash/fp/sortBy'); var _sortBy2 = _interopRequireDefault(_sortBy); var _isEqual = require('lodash/fp/isEqual'); var _isEqual2 = _interopRequireDefault(_isEqual); var _isEmpty = require('lodash/fp/isEmpty'); var _isEmpty2 = _interopRequireDefault(_isEmpty); var _shuffle = require('lodash/fp/shuffle'); var _shuffle2 = _interopRequireDefault(_shuffle); var _includes = require('lodash/fp/includes'); var _includes2 = _interopRequireDefault(_includes); var _findIndex = require('lodash/fp/findIndex'); var _findIndex2 = _interopRequireDefault(_findIndex); var _intersection = require('lodash/fp/intersection'); var _intersection2 = _interopRequireDefault(_intersection); var _selectRule = require('../rule-engine/select-rule'); var _selectRule2 = _interopRequireDefault(_selectRule); var _applyInstructions = require('../rule-engine/apply-instructions'); var _applyInstructions2 = _interopRequireDefault(_applyInstructions); var _updateState = require('../update-state'); var _updateState2 = _interopRequireDefault(_updateState); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const hasNoMoreLives = (config, state) => !config.livesDisabled && state.lives <= 0; const getContentRef = (0, _get2.default)('content.ref'); const hasRemainingLifeRequests = state => state.remainingLifeRequests > 0; const stepIsAlreadyExtraLife = state => getContentRef(state) === 'extraLife'; const hasRulesToApply = chapterContent => { return !!(chapterContent && Array.isArray(chapterContent.rules) && !(0, _isEmpty2.default)(chapterContent.rules)); }; const nextSlidePool = exports.nextSlidePool = (config, availableContent, state) => { if (state.nextContent.type === 'chapter') { const content = (0, _find2.default)({ ref: state.nextContent.ref }, availableContent) || null; return { currentChapterContent: content, nextChapterContent: content, temporaryNextContent: { type: 'slide', ref: '' } }; } const lastSlideRef = (0, _pipe2.default)((0, _get2.default)('slides'), _last2.default)(state); const _currentIndex = (0, _findIndex2.default)(({ slides }) => !!(0, _find2.default)({ _id: lastSlideRef }, slides), availableContent); const currentIndex = _currentIndex !== -1 ? _currentIndex : 0; const currentChapterPool = availableContent[currentIndex] || null; const slidesAnsweredForThisChapter = (0, _intersection2.default)(state.slides, currentChapterPool && (0, _map2.default)('_id', currentChapterPool.slides) || []); const isChapterCompleted = slidesAnsweredForThisChapter.length >= config.slidesToComplete; const hasRules = hasRulesToApply(currentChapterPool); const shouldChangeChapter = !hasRules && isChapterCompleted; if (shouldChangeChapter) { return { currentChapterContent: currentChapterPool, nextChapterContent: availableContent[currentIndex + 1] || null, temporaryNextContent: { type: 'slide', ref: '' } }; } return { currentChapterContent: currentChapterPool, nextChapterContent: currentChapterPool, temporaryNextContent: state.nextContent }; }; const _getChapterContent = (config, availableContent, state) => { const firstContent = (0, _head2.default)(availableContent); if (!state) { return { currentChapterContent: firstContent, nextChapterContent: firstContent, temporaryNextContent: { type: 'slide', ref: '' } }; } return nextSlidePool(config, availableContent, state); }; const getChapterContent = (config, availableContent, state) => { const res = _getChapterContent(config, availableContent, state); if (!res.currentChapterContent) { return null; } return res; }; const sortByPosition = (0, _sortBy2.default)(slide => typeof slide.position === 'number' ? -slide.position : 0); const pickNextSlide = (0, _pipe2.default)(_shuffle2.default, sortByPosition, _head2.default); const isTargetingIsCorrect = condition => condition.target.scope === 'slide' && condition.target.field === 'isCorrect'; const getIsCorrect = (isCorrect, chapterRule) => { if (chapterRule.conditions.some(isTargetingIsCorrect)) return isCorrect; return null; }; const computeNextSlide = (config, chapterContent, state) => { const remainingSlides = (0, _filter2.default)((0, _pipe2.default)((0, _get2.default)('_id'), slideId => !state || !(0, _includes2.default)(slideId, state.slides)), chapterContent.slides); return { type: 'slide', ref: pickNextSlide(remainingSlides)._id }; }; const prepareStateToSwitchChapters = exports.prepareStateToSwitchChapters = (chapterRule, state) => { if (!state) { return state; } return (0, _applyInstructions2.default)(chapterRule.instructions)(_extends({}, state, { nextContent: chapterRule.destination })); }; const computeNextStepForNewChapter = exports.computeNextStepForNewChapter = (config, state, chapterRule, isCorrect, availableContent) => { // eslint-disable-next-line no-use-before-define const nextStep = computeNextStep(config, prepareStateToSwitchChapters(chapterRule, state), availableContent, null); if (!nextStep) { return null; } return { nextContent: nextStep.nextContent, instructions: chapterRule.instructions.concat(nextStep.instructions || []), isCorrect: getIsCorrect(isCorrect, chapterRule) }; }; const extendPartialAction = (action, state) => { if (!action) { return null; } switch (action.type) { case 'answer': { const nextContent = action.payload.content || (state ? state.nextContent : { ref: '', type: 'node' }); return { type: 'answer', payload: { answer: action.payload.answer, godMode: action.payload.godMode, isCorrect: action.payload.isCorrect, content: nextContent, nextContent, instructions: null } }; } case 'extraLifeAccepted': { const nextContent = state ? state.nextContent : { ref: '', type: 'node' }; return { type: 'extraLifeAccepted', payload: { content: nextContent, nextContent, instructions: null } }; } default: return null; } }; const computeNextStep = (config, _state, availableContent, partialAction) => { const action = extendPartialAction(partialAction, _state); const isCorrect = !!action && action.type === 'answer' && !!action.payload.isCorrect; const answer = !!action && action.type === 'answer' && action.payload.answer || []; const state = !_state || !action ? _state : (0, _updateState2.default)(config, _state, [action]); const chapterContent = getChapterContent(config, availableContent, state); if (!chapterContent) { return null; } const { currentChapterContent, nextChapterContent, temporaryNextContent } = chapterContent; const hasRules = hasRulesToApply(nextChapterContent); if (!hasRules) { if (state && hasNoMoreLives(config, state)) { return { nextContent: !stepIsAlreadyExtraLife(state) && hasRemainingLifeRequests(state) ? { type: 'node', ref: 'extraLife' } : { type: 'failure', ref: 'failExitNode' }, instructions: null, isCorrect }; } else if (!nextChapterContent) { // If user has answered all questions, return success endpoint return { nextContent: { type: 'success', ref: 'successExitNode' }, instructions: null, isCorrect }; } } if (hasRules) { const allAnswers = !!state && state.allAnswers || []; // $FlowFixMe nextChapterContent.rules = array not emtpy -> checked by hasRulesToApply const chapterRule = (0, _selectRule2.default)(nextChapterContent.rules, _extends({}, state, { nextContent: temporaryNextContent, allAnswers: [...allAnswers, { slideRef: temporaryNextContent.ref, answer, isCorrect }] })); if (!chapterRule) { return null; } if (chapterRule.destination.type === 'chapter') { return computeNextStepForNewChapter(config, state, chapterRule, isCorrect, availableContent); } return { nextContent: chapterRule.destination, instructions: chapterRule.instructions, isCorrect: (0, _isEqual2.default)(currentChapterContent, nextChapterContent) ? getIsCorrect(isCorrect, chapterRule) : isCorrect }; } if (nextChapterContent && Array.isArray(nextChapterContent.slides) && nextChapterContent.slides.length > 0) { const stateWithDecrementedLives = state ? _extends({}, state, { nextContent: temporaryNextContent }) : state; const nextContent = computeNextSlide(config, nextChapterContent, stateWithDecrementedLives); return { nextContent, instructions: null, isCorrect }; } return null; }; exports.default = computeNextStep; //# sourceMappingURL=compute-next-step.js.map