@coorpacademy/progression-engine
Version:
301 lines (232 loc) • 9.99 kB
JavaScript
'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