Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Google Blockly] Support for Play Lab variables #63057

Open
wants to merge 5 commits into
base: staging
Choose a base branch
from

Conversation

mikeharv
Copy link
Contributor

@mikeharv mikeharv commented Dec 19, 2024

This branch aims to migrate several CDO Blockly features that are required for maintaining Play Lab levels and projects once they're migrated to mainline Google Blockly.

This branch makes all levels in s/course4/lessons/7 fully playable.

Wrapper Properties

  1. disableVariableEditing: This flag indicates that students should not be able to edit variable fields on variable setters and getters. When CDO Blockly creates these blocks, it read this value to determine whether a variable field (dropdown) or label field should be used. For Google Blockly, I propose we continue to use variable fields, but no-op the dropdown editor and hide the dropdown "arrow" to mimic an uneditable block. To do this, we can read from this flag in two CdoFieldVariable methods. Some of this logic was already present in CdoFieldDropdown.createTextArrow_. I took this opportunity to add it where it was missing for our variable and parameter fields, reading from Blockly.disableVariableEditing in the former.

CDO Blockly:
image

Google Blockly before:
image
Google Blockly after:
image

  1. varsInGlobals: This is a flag used to specify that the generated code for a variable (e.g. points) should be added to the Globals namespace (e.g. Globals.points) rather than treated as an actual global variable (e.g. var points;) like Blockly would normally do. This requires updating the custom generator method translateVarName and supplying custom block generator functions, described below.

Generator functions

As of v10, Google Blockly has moved all generator functions to generator.forBlock. To account for this, we need to make sure that all generators are being defined in the right place.
We also add overrides for the standard generators for variable getters and setters. These come from CDO Blockly and the difference is that they read from Blockly.varsInGlobals (by way of translateVarName) to determine whether we should add the Globals name space. The code snippets below are the result of generating for the given block using Blockly.JavaScript.blockToCode:

CDO Blockly:
image
'Globals.points = Globals.points + 1;\n'

Google Blockly before::
image
'points = points + 1;\n'

Google Blockly after:
image
'pointsGlobals. = pointsGlobals. + 1;\n'

Invisible Blocks

Many of these levels feature "user invisible" blocks on the main (CDO Blockly) workspace. Google Blockly does not support invisible blocks, so we need to move them to our hidden workspace instead. To facilitate this, we have created a set of custom serialization hooks that can be added to top blocks that potentially need to be hidden. This is done via Blockly.customBlocks.addSerializationHooksToBlock:

mutationToDom(this: Record<string, any>) {
const container = Blockly.utils.xml.createElement('mutation');
mutatorProperties.forEach(prop => {
if (this[prop]) {
container.setAttribute(prop, this[prop]);
}
});
return container;
},
domToMutation(this: Record<string, any>, mutationElement: Element) {
Array.from(mutationElement.attributes).forEach(attr => {
const attrName = attr.name;
const attrValue = attr.value;
const parsedInt = parseInt(attrValue);
if (!isNaN(parsedInt)) {
this[attrName] = parsedInt;
} else if (
attrValue.toLowerCase() === 'false' ||
attrValue.toLowerCase() === 'true'
) {
this[attrName] = readBooleanAttribute(mutationElement, attrName);
} else {
this[attrName] = attrValue;
}
mutatorProperties.indexOf(attrName) === -1 &&
mutatorProperties.push(attrName);
});
},
saveExtraState(this: Record<string, any>) {
const state: Record<string, any> = {};
mutatorProperties.forEach(prop => {
if (this[prop]) {
state[prop] = this[prop];
}
});
return state;
},
loadExtraState(this: Record<string, any>, state: Record<string, any>) {
for (const prop in state) {
this[prop] = state[prop];
mutatorProperties.indexOf(prop) === -1 && mutatorProperties.push(prop);
}
},
// Global function to handle serialization hooks
addSerializationHooksToBlock(block: GoogleBlockly.Block) {
if (!block.mutationToDom) {
block.mutationToDom = this.mutationToDom;
}
if (!block.domToMutation) {
block.domToMutation = this.domToMutation;
}
if (!block.saveExtraState) {
block.saveExtraState = this.saveExtraState;
}
if (!block.loadExtraState) {
block.loadExtraState = this.loadExtraState;
}
},

Example of invisible blocks as shown in Start Mode:
image

This branch adds the hooks to the following Studio block definitions, all of which are used as invisible top blocks in levels to add hidden game mechanics: studio_whenArrow, studio_whenSpriteCollided, and studio_repeatForever,

CS in Algebra cleanup

While updating generator functions, I came across a set of block definitions that are no longer needed. These are all of the blocks that include the word "functional" in their name. These blocks were only used in the deprecated "Algebra game" version of Play Lab. When reviewing, you'll want to manually expand apps/src/studio/blocks.js so you can see all of the changes.

Links

https://codedotorg.atlassian.net/browse/CT-907

Follow-up work

There will likely be similar work coming to get functions and parameters working.

PR Checklist:

  • Tests provide adequate coverage
  • Privacy and Security impacts have been assessed
  • Code is well-commented
  • New features are translatable or updates will not break translations
  • Relevant documentation has been added or updated
  • User impact is well-understood and desirable
  • Pull Request is labeled appropriately
  • Follow-up work items (including potential tech debt) are tracked and linked

@@ -79,8 +79,6 @@ var spriteCount = 6;
var projectileCollisions = false;
var startAvatars = [];

var customGameLogic = null;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only used with some "functional" blocks.

@@ -3405,250 +3424,6 @@ exports.install = function (blockly, blockInstallOptions) {
);
};

//
// Install functional start blocks
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deleted blocks below this point were only used in CS in Algebra, which has been deprecated.

generator.studio_ask = function () {
// Overrides the standard generator from Core Blockly.
// Variable labels in Playlab include the Globals namespace.
blockGeneratorFunctionDictionary.variables_get = function () {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These new generator functions come CDO Blockly: https://github.com/code-dot-org/blockly/blob/v4.1.0/generators/javascript/variables.js#L30-L46

Google Blockly's are here: https://github.com/google/blockly/blob/505492526c15817a4ecc09d6f36820fff3be5136/generators/javascript/variables.ts

We only need to main this set for Play Lab, which is the only lab that relies upon translateVarName (to add the Globals namespace).

@@ -416,7 +416,6 @@ Studio.loadLevel = function () {
staticPlayer: true,
});
}
blocks.registerCustomGameLogic(Studio.customLogic);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This export was deleted because it's only used in CS in Algebra.

@mikeharv mikeharv marked this pull request as ready for review December 20, 2024 17:57
@mikeharv mikeharv requested a review from a team December 20, 2024 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant