Skip to content

Commit

Permalink
Merge pull request #1149 from Program-AR/develop
Browse files Browse the repository at this point in the history
1.12.0
  • Loading branch information
tfloxolodeiro authored Oct 18, 2022
2 parents 1e35906 + 421dd30 commit 89fb521
Show file tree
Hide file tree
Showing 42 changed files with 376 additions and 182 deletions.
2 changes: 1 addition & 1 deletion .jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"unzipit",
"ProcedsBlockly"
],
"esversion": 9,
"esversion": 11,
"asi": true,
"browser": true,
"boss": true,
Expand Down
11 changes: 11 additions & 0 deletions README_en.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
![JavaScript](https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge&logo=javascript&logoColor=%23F7DF1E)
![Ember](https://img.shields.io/badge/ember-1C1E24?style=for-the-badge&logo=ember.js&logoColor=#D04A37)

[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/Program-AR/pilas-bloques/issues)
[![open issues](https://badgen.net/github/open-issues/Program-AR/pilas-bloques)](https://github.com/Program-AR/pilas-bloques/issues)
![downloads](https://img.shields.io/github/downloads/Program-AR/pilas-bloques/total.svg)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)

[:ar: Leer en español](https://github.com/Program-AR/pilas-bloques/blob/develop/README.md)
_____________

# Pilas Bloques - A tool to learn computer programming

<p align="center">
Expand Down
2 changes: 1 addition & 1 deletion app/components/import-custom-challenge.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default Component.extend({
const challengeJson = await this._getChallengeJson(entries)
const sceneImages = await this._getSceneImages(entries)
const challengeCover = await this._imageContentToURL(entries[`${assetsPath}/splashChallenge.png`]);
challengeJson.challengeCover = challengeCover
challengeJson.customCover = challengeCover
challengeJson.imagesToPreload = sceneImages.map(image => image.url)
//Currently it is not possible to define scenes in the json itself, like in the desafios.js file, but it can be made possible by replacing this line with "challengeJson.escena = challengeJson.sceneConstructor || `new CustomScene(...)"
challengeJson.escena = `new CustomScene({grid:{spec:${JSON.stringify(challengeJson.grid)}}, images:${JSON.stringify(sceneImages)}})`
Expand Down
4 changes: 4 additions & 0 deletions app/components/non-scored-expectations.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ export default Component.extend({
}))
}),

solutionWorksDescription: computed('intl', function () {
return this.intl.t("components.spects.control_group.solution_works").toString()
})

});
49 changes: 31 additions & 18 deletions app/components/pilas-blockly.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export default Component.extend({
expects: [],
codigoActualEnFormatoXML: '', // se actualiza automáticamente al modificar el workspace.

staticAnalysisError: '',

anterior_ancho: -1,
anterior_alto: -1,

Expand Down Expand Up @@ -85,7 +87,7 @@ export default Component.extend({

// Este es un hook para luego agregar a la interfaz
// el informe deseado al ocurrir un error.
this.pilasService.on("error", ({error}) => {
this.pilasService.on("error", ({ error }) => {
this.set('engineError', error);
});

Expand Down Expand Up @@ -378,20 +380,22 @@ export default Component.extend({
score: {
expectResults: this.scoredExpectsResults(),
percentage: this.expectsScoring.totalScore(this.get('expects'), this.challenge)
}
},
error: this.get('staticAnalysisError')
}
},

persistableExpectsResults(expects) {
const deletableProperties = ["description", "isForControlGroup", "isSuggestion", "isScoreable", "limit", "isRelatedToUsage", "isCritical"]
return expects.map(e => {
const expect = { ...e }
delete expect.description
deletableProperties.forEach(p => delete expect[p])
return expect
})
},

scoredExpectsResults() {
return this.persistableExpectsResults(this.expectsScoring.expectsResults(this.get('expects')))
return this.expectsScoring.resultsIncludingUnusedExpects(this.get('expects'), this.challenge).reduce((expectsObj, expect) => ({ ...expectsObj, [expect.id]: expect.result }), {})
},

runProgramEvent() {
Expand All @@ -400,7 +404,7 @@ export default Component.extend({

executionFinishedEvent(solutionId, executionResult) {
run(this, function () {
this.pilasBloquesApi.executionFinished(solutionId, {
this.pilasBloquesApi.executionFinished(solutionId, this.staticAnalysis(), {
isTheProblemSolved: this.pilasService.estaResueltoElProblema(),
...executionResult
})
Expand All @@ -412,15 +416,15 @@ export default Component.extend({
notCritical,
addWarning
)

this.showExpectationFeedbackFor(
warningInControlStructureBlock,
addWarning,
getNestedControlStructureBlocks
)
},

showBlocksErrorExpectationFeedback(){
showBlocksErrorExpectationFeedback() {
this.showExpectationFeedbackFor(
isCritical,
addError
Expand All @@ -431,19 +435,26 @@ export default Component.extend({
this.get('failedExpects')
.filter(condition)
.forEach(({ declaration, description }, i) => {
getBlocks(declaration)
.forEach(block => addFeedback(block, description.asSuggestion, -i))
})
getBlocks(declaration)
.forEach(block => addFeedback(block, description.asSuggestion, -i))
})
},


async runValidations() {
clearValidations()
this.set('expects', await this.pilasMulang.analyze(Blockly.mainWorkspace, this.challenge))
// Order is important. Warnings should be added first. This way, if errors appear, warning bubbles will be painted red.
if(this.experiments.shouldShowBlocksWarningExpectationFeedback()) this.showBlocksWarningExpectationFeedback()
this.showBlocksErrorExpectationFeedback()
Blockly.Events.fireRunCode()
this.set('staticAnalysisError', '')

try {
clearValidations()
this.set('expects', await this.pilasMulang.analyze(Blockly.mainWorkspace, this.challenge))
// Order is important. Warnings should be added first. This way, if errors appear, warning bubbles will be painted red.
if (this.experiments.shouldShowBlocksWarningExpectationFeedback()) this.showBlocksWarningExpectationFeedback()
this.showBlocksErrorExpectationFeedback()
Blockly.Events.fireRunCode()
} catch (e) {
console.log(e)
this.set('staticAnalysisError', e.toString())
}
},

javascriptCode() {
Expand All @@ -459,10 +470,12 @@ export default Component.extend({
actions: {

async ejecutar(pasoAPaso = false) {
const analyticsSolutionId = this.runProgramEvent()
await this.pilasService.restartScene()

await this.runValidations()

const analyticsSolutionId = this.runProgramEvent()

if (!this.shouldExecuteProgram()) return;

let factory = this.interpreterFactory;
Expand Down
5 changes: 0 additions & 5 deletions app/components/scene-details.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import Component from '@ember/component';
import { computed } from '@ember/object';

export default Component.extend({

initialized: false,
exerciseCover: computed('model', function () {
return this.model.challengeCover || `imagenes/desafios/${this.model.nombreImagen}`
}),

actions: {

setTab(tabId) {
Expand Down
4 changes: 0 additions & 4 deletions app/components/scored-expectations.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,4 @@ export default Component.extend({
expectsResults: computed('expects', function () {
return this.expectsScoring.expectsResults(this.expects)
}),

passedExpectsValue: computed('expects', function () {
return this.expectsScoring.totalScore(this.expects, this.challenge)
}),
});
5 changes: 3 additions & 2 deletions app/models/desafio.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default Model.extend({

nombre: attr('string'),
imagen: attr('string'),
customCover: attr('string'),
deshabilitado: attr('boolean'),
escena: attr('string'),
hasAutomaticGrading: attr('boolean', { defaultValue: true }),
Expand All @@ -35,8 +36,8 @@ export default Model.extend({
expectations: attr(),
shouldShowMultipleScenarioHelp: attr('boolean', {defaultValue: false}),

nombreImagen: computed('imagen', 'nombre', function () {
return `${this.imagen || this.nombre || 'proximamente'}.png`;
coverSrc: computed('imagen', 'nombre', 'customCover', function () {
return this.customCover || `imagenes/desafios/${ this.imagen || this.nombre || 'proximamente'}.png`;
}),

initialWorkspace: computed("solucionInicial", function () {
Expand Down
48 changes: 23 additions & 25 deletions app/services/challenge-expectations.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Service from '@ember/service'
import { isEmpty, sum } from 'ramda'
import { allProceduresShould, doesNotUseRecursion, doSomething, isUsed, isUsedFromMain, multiExpect, notTooLong, mainNotTooLong, noExpectation, nameWasChanged, doesNotNestControlStructures, decompositionExpectsIdsForControlGroup } from '../utils/expectations'
import { allProceduresShould, doesNotUseRecursion, doSomething, isUsed, isUsedFromMain, multiExpect, notTooLong, mainNotTooLong, noExpectation, nameWasChanged, doesNotNestControlStructures, doSomethingId, tooLongId, doesNotNestControlStructuresId, nameWasChangedId, mainTooLongId, decompositionExpectsIdsForControlGroup } from '../utils/expectations'
import { inject as service } from '@ember/service';

// Be careful when adding new expects. idsToScore should be potentially updated too.
Expand Down Expand Up @@ -46,23 +46,11 @@ const idsToExpectations = (intl) => ({
})


/**
*
* This should be _erased from existence_ calculated from expectations.
* Related to: https://github.com/Program-AR/pilas-bloques/issues/1040
*/
const idsToScore = {
decomposition: 5,
decomposition9: 5,

/**
* See comment above
conditionalAlternative: 1,
conditionalRepetition: 1,
simpleRepetition: 1
*/
//Will (not) be obliterated Soon™ (never)
const harcodedAllConfigurationsToExpectIds = {
decomposition: [doSomethingId, tooLongId, mainTooLongId, nameWasChangedId, doesNotNestControlStructuresId],
decomposition9: [doSomethingId, tooLongId, mainTooLongId, nameWasChangedId, doesNotNestControlStructuresId],
//simpleRepetition: [simpleRepetitionId]
}

// TODO: DELETE. I cant even...
Expand Down Expand Up @@ -139,29 +127,39 @@ export default Service.extend({
allExpectConfigurationsMerged(challenge) {
return this.mergeConfigurations(this.allExpectConfigurations(challenge))
},
allExpectIdsIn(challenge) {
const challengeConfigurations = this.allExpectConfigurationsMerged(challenge)
const validConfigurationsIds = Object.keys(challengeConfigurations).filter(key => challengeConfigurations[key]) //Should only use configurations set to true.
return validConfigurationsIds.flatMap(this.expectIdsInConfiguration)
},

expectIdsInConfiguration(configId) {
return harcodedAllConfigurationsToExpectIds[configId] || []
},

hasDecomposition(challenge) {
return !!this.allExpectConfigurationsMerged(challenge).decomposition
const allExpectConfigurationsMerged = this.allExpectConfigurationsMerged(challenge)
return !!(allExpectConfigurationsMerged.decomposition || allExpectConfigurationsMerged.decomposition9)
},

totalScoreOf(challenge) {
howManyScoreableExpectationsFor(challenge) {
return this.configToTotalScore(this.allExpectConfigurationsMerged(challenge))
},

configToTotalScore(expectationsConfig) {
return isEmpty(expectationsConfig) ? 0
: sum(
this.appliableConfigs(expectationsConfig).map(([id,]) => this.idToScore(id))
this.appliableConfigs(expectationsConfig).map(([id,]) => this.configIdToMaxScore(id))
)
},

idToScore(id) {
return idsToScore[id] || 0
configIdToMaxScore(id) {
return harcodedAllConfigurationsToExpectIds[id]?.length || 0
},

expectationsIdsForControlGroup(challenge) {
return Object.entries(this.allExpectConfigurationsMerged(challenge))
.filter(([, shouldBeApplied]) => shouldBeApplied)
.flatMap(([id,]) => idsToExpectationsIdsForControl[id] || [])
.filter(([, shouldBeApplied]) => shouldBeApplied)
.flatMap(([id,]) => idsToExpectationsIdsForControl[id] || [])
}
})
34 changes: 30 additions & 4 deletions app/services/expects-scoring.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import Service from '@ember/service'
import { inject as service } from '@ember/service'
import { find, groupBy } from 'ramda'
import { expectationDescription } from '../utils/expectations'
import { expectationDescription, doesNotNestControlStructuresId } from '../utils/expectations'

export const solutionWorks = 'solution_works'

export default Service.extend({
intl: service(),
challengeExpectations: service(),
pilasService: service('pilas'),

expectsResults(expects) {
return [this.solutionWorksExpectResult()].concat(this.combineMultipleExpectations(expects)).filter(this.isScoreable)
Expand Down Expand Up @@ -44,17 +45,42 @@ export default Service.extend({

solutionWorksExpectResult() {
const params = { isScoreable: true }
const result = this.pilasService.estaResueltoElProblema()

return {
id: solutionWorks,
description: expectationDescription(this.intl, solutionWorks, true, params),
result: true,
description: expectationDescription(this.intl, solutionWorks, result, params),
result,
...params
}
},

unusedExpects(expects, challenge) {
const unusedExpectIds = this.challengeExpectations.allExpectIdsIn(challenge).filter(expectId => !this.expectIdIsUsed(expectId, expects))
return unusedExpectIds.map(id => this.unusedExpectationIdToPassingExpectation(id, expects))
},

expectIdIsUsed(expectationId, expects) {
return this.expectsResults(expects).some(expectResult => expectResult.id === expectationId)
},

unusedExpectationIdToPassingExpectation(expectationId) {
return {
id: expectationId,
isScoreable: true,
result: expectationId === doesNotNestControlStructuresId, // the only one with default true,
description: {}
}
},

resultsIncludingUnusedExpects(expects, challenge) {
return this.expectsResults(expects).concat(this.unusedExpects(expects, challenge))
},

totalScore(expects, challenge) {
return 100 * this.allPassedExpects(expects).length / (this.challengeExpectations.totalScoreOf(challenge) + 1) // Solution works adds one to the final score
const resultsIncludingUnused = this.resultsIncludingUnusedExpects(expects, challenge)
const passingResults = resultsIncludingUnused.filter(e => e.result)
return 100 * passingResults.length / (this.challengeExpectations.howManyScoreableExpectationsFor(challenge) + 1) // Solution works adds one to the final score
}

})
2 changes: 1 addition & 1 deletion app/services/experiments.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default Service.extend({
},

getExperimentGroupAssigned() {
return this.pilasBloquesApi.getUser()?.experimentGroup || this.storage.getExperimentGroup() || this.randomizeAndSaveExperimentGroup() // jshint ignore:line
return this.pilasBloquesApi.getUser()?.experimentGroup || this.storage.getExperimentGroup() || this.randomizeAndSaveExperimentGroup()
},

randomizeAndSaveExperimentGroup() {
Expand Down
12 changes: 10 additions & 2 deletions app/services/intl.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* app/services/intl.js */
import { assign } from '@ember/polyfills';
import IntlService from 'pilas-bloques-ember-intl/services/intl';
import Ember from 'ember';
import ENV from 'pilasbloques/config/environment'
Expand All @@ -9,7 +8,7 @@ export default IntlService.extend({
storage: service(),

t(key, options) {
return this.addTranslationCheck(this._super(key, assign({ htmlSafe: true }, options)));
return this.addEmojiFontTag(this.addTranslationCheck(this._super(key, { ...options, htmlSafe: true })));
},

addTranslationCheck(safeHTML) {
Expand All @@ -18,6 +17,15 @@ export default IntlService.extend({
safeHTML
},

addEmojiFontTag(safeHTML){
return Ember.String.htmlSafe(safeHTML.string.replace(
/:[^ ]+:/g,
(emoji) => {
return `<span class="emoji">${emoji}</span>`
}
))
},

setSelectedLocale(selectedLocaleCode) {
this.storage.saveSelectedLocale(selectedLocaleCode);
window.location.reload(true)
Expand Down
Loading

0 comments on commit 89fb521

Please sign in to comment.