Skip to content

Commit

Permalink
Merge pull request #22267 from code-dot-org/gamelab-validation
Browse files Browse the repository at this point in the history
 Run validation code after every draw loop
  • Loading branch information
balderdash committed May 10, 2018
2 parents 21cc675 + a1e12ef commit 9bcce43
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 4 deletions.
50 changes: 46 additions & 4 deletions apps/src/gamelab/GameLab.js
Expand Up @@ -56,6 +56,7 @@ import {showHideWorkspaceCallouts} from '../code-studio/callouts';
import GameLabJrLib from './GameLabJr.interpreted';
import defaultSprites from './defaultSprites.json';
import {GamelabAutorunOptions} from '@cdo/apps/util/sharedConstants';
import ValidationSetupCode from './ValidationSetup.interpreted.js';

const LIBRARIES = {
'GameLabJr': GameLabJrLib,
Expand Down Expand Up @@ -314,7 +315,7 @@ GameLab.prototype.init = function (config) {
}
};

var showFinishButton = !this.level.isProjectLevel;
var showFinishButton = !this.level.isProjectLevel && !this.level.validationCode;
var finishButtonFirstLine = _.isEmpty(this.level.softButtons);

var showDebugButtons = config.level.editCode &&
Expand Down Expand Up @@ -478,7 +479,14 @@ GameLab.prototype.afterInject_ = function (config) {
if (this.studioApp_.isUsingBlockly()) {
// Add to reserved word list: API, local variables in execution evironment
// (execute) and the infinite loop detection function.
Blockly.JavaScript.addReservedWords('GameLab,code');
Blockly.JavaScript.addReservedWords([
'GameLab',
'code',
'validationState',
'validationResult',
'levelSuccess',
'levelFailure',
].join(','));
}

// Update gameLabP5's scale and keep it updated with future resizes:
Expand Down Expand Up @@ -609,7 +617,7 @@ GameLab.prototype.rerunSetupCode = function () {
this.gameLabP5.p5.redraw();
};

GameLab.prototype.onPuzzleComplete = function (submit) {
GameLab.prototype.onPuzzleComplete = function (submit, testResult) {
if (this.executionError) {
this.result = ResultType.ERROR;
} else {
Expand All @@ -624,6 +632,8 @@ GameLab.prototype.onPuzzleComplete = function (submit) {
this.testResults = this.studioApp_.getTestResults(levelComplete, {
executionError: this.executionError
});
} else if (testResult) {
this.testResults = testResult;
} else {
this.testResults = TestResults.FREE_PLAY;
}
Expand Down Expand Up @@ -716,7 +726,7 @@ GameLab.prototype.runButtonClick = function () {

// Enable the Finish button if is present:
var shareCell = document.getElementById('share-cell');
if (shareCell) {
if (shareCell && !this.level.validationCode) {
shareCell.className = 'share-cell-enabled';

// Adding completion button changes layout. Force a resize.
Expand Down Expand Up @@ -950,6 +960,9 @@ GameLab.prototype.initInterpreter = function (attachDebugger=true) {
.join("\n");
code = libs + code;
}
if (this.level.validationCode) {
code = ValidationSetupCode + code;
}
this.JSInterpreter.parse({
code,
blocks: dropletConfig.blocks,
Expand Down Expand Up @@ -1206,6 +1219,35 @@ GameLab.prototype.onP5Draw = function () {
this.drawInProgress = true;
if (getStore().getState().runState.isRunning) {
this.eventHandlers.draw.apply(null);
if (this.level.validationCode) {
try {
const validationResult =
this.JSInterpreter.interpreter.marshalInterpreterToNative(
this.JSInterpreter.evalInCurrentScope(`
(function () {
validationState = null;
validationResult = null;
${this.level.validationCode}
return {
state: validationState,
result: validationResult
};
})();
`)
);
if (validationResult.state === 'succeeded') {
console.log('success!');
const testResult = validationResult.result ||
TestResults.ALL_PASS;
this.onPuzzleComplete(false, testResult);
} else if (validationResult === 'failed') {
// TODO(ram): Show failure feedback
}
} catch (e) {
// If validation code errors, assume it was neither a success nor failure
console.error(e);
}
}
} else if (this.shouldAutoRunSetup) {
this.gameLabP5.p5.background('white');
switch (this.level.autoRunSetup) {
Expand Down
10 changes: 10 additions & 0 deletions apps/src/gamelab/ValidationSetup.interpreted.js
@@ -0,0 +1,10 @@
/* eslint-disable */
function levelSuccess(testResult) {
validationState = 'succeeded';
validationResult = testResult;
}

function levelFailure(msg) {
validationState = 'failed';
validationResult = msg;
}
3 changes: 3 additions & 0 deletions apps/src/sites/studio/pages/levelbuilder_gamelab.js
Expand Up @@ -58,6 +58,9 @@ $(document).ready(function () {
if (document.getElementById('level_custom_helper_library')) {
initializeCodeMirror('level_custom_helper_library', 'javascript');
}
if (document.getElementById('level_validation_code')) {
initializeCodeMirror('level_validation_code', 'javascript');
}
const autoRunSetup = document.getElementById('level_auto_run_setup');
const customSetupCode = document.getElementById('level_custom_setup_code');
if (autoRunSetup && customSetupCode) {
Expand Down
1 change: 1 addition & 0 deletions dashboard/app/models/levels/gamelab.rb
Expand Up @@ -48,6 +48,7 @@ class Gamelab < Blockly
teacher_markdown
auto_run_setup
custom_setup_code
validation_code
)

# List of possible skins, the first is used as a default.
Expand Down
6 changes: 6 additions & 0 deletions dashboard/app/views/levels/editors/_gamelab.html.haml
Expand Up @@ -93,6 +93,12 @@
= f.label 'Custom setup code'
= f.text_field :custom_setup_code

.field
= f.label 'Validation code'
%p
This is a snippet of javascript that is run after every draw loop. Call levelSuccess() in here to stop the level as a success. You can also pass a number into levelSuccess() to give the student a non-perfect test result (see <a href="https://github.com/code-dot-org/code-dot-org/blob/101fbfd2dcdd635d7359e991559ba782f9fd00be/apps/src/constants.js#L33-L106">TestResults</a> for the list of valid test results).
= f.text_area :validation_code

.field
=f.label 'Animations are paused by default'
%p
Expand Down

0 comments on commit 9bcce43

Please sign in to comment.