/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var BlockPalette = Container.expand(function (width, height) {
var self = Container.call(this);
self.paletteWidth = width || 400;
self.paletteHeight = height || 2732;
// Create background
self.background = self.addChild(LK.getAsset('codeBlock', {
anchorX: 0,
anchorY: 0,
width: self.paletteWidth,
height: self.paletteHeight,
alpha: 0.3,
tint: 0x333333
}));
// Add title
self.title = new Text2("Block Palette", {
size: 40,
fill: 0xFFFFFF
});
self.title.anchor.set(0.5, 0);
self.title.x = self.paletteWidth / 2;
self.title.y = 30;
self.addChild(self.title);
// Add blocks to palette
self.addBlockToPalette = function (type, text, x, y) {
var block = new CodeBlock(type, text);
block.x = x;
block.y = y;
// This is a template block - when dragged, it creates a new instance
block.down = function (x, y, obj) {
// Create a new block when this template is clicked
var newBlock = new CodeBlock(type, text);
newBlock.x = block.x;
newBlock.y = block.y;
// Convert to global position
var globalPos = self.parent.toGlobal({
x: block.x,
y: block.y
});
newBlock.x = globalPos.x;
newBlock.y = globalPos.y;
// Add to game's blocks array
blocks.push(newBlock);
// Add to game
game.addChild(newBlock);
// Start dragging the new block
newBlock.startDragX = newBlock.x;
newBlock.startDragY = newBlock.y;
newBlock.isDragging = true;
draggedBlock = newBlock;
};
self.addChild(block);
return block;
};
return self;
});
var Button = Container.expand(function (text, width, height, color) {
var self = Container.call(this);
self.buttonWidth = width || 200;
self.buttonHeight = height || 80;
self.buttonColor = color || 0x4287f5;
// Create button background
self.background = self.addChild(LK.getAsset('codeBlock', {
anchorX: 0.5,
anchorY: 0.5,
width: self.buttonWidth,
height: self.buttonHeight,
tint: self.buttonColor
}));
// Add text
self.label = new Text2(text, {
size: 30,
fill: 0xFFFFFF
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
// Handle down event
self.down = function (x, y, obj) {
// Visual feedback
tween(self.background, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
};
// Handle up event
self.up = function (x, y, obj) {
// Reset scale
tween(self.background, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
// Call the button's action
if (self.action) {
self.action();
}
}
});
};
return self;
});
var CodeArea = Container.expand(function (width, height) {
var self = Container.call(this);
self.areaWidth = width || 1200;
self.areaHeight = height || 1500;
// Create background
self.background = self.addChild(LK.getAsset('codeBlock', {
anchorX: 0,
anchorY: 0,
width: self.areaWidth,
height: self.areaHeight,
alpha: 0.2,
tint: 0x222222
}));
// Method to check if a point is inside the code area
self.isPointInside = function (x, y) {
var globalPos = game.toLocal({
x: x,
y: y
}, self.parent);
return globalPos.x >= self.x && globalPos.x <= self.x + self.areaWidth && globalPos.y >= self.y && globalPos.y <= self.y + self.areaHeight;
};
return self;
});
var CodeBlock = Container.expand(function (type, text) {
var self = Container.call(this);
self.type = type || 'default';
self.text = text || '';
self.connections = {
top: null,
bottom: null,
left: null,
right: null
};
self.canConnect = true;
self.isExecuting = false;
// Select the correct asset based on type
var assetId = 'codeBlock';
if (self.type === 'variable') {
assetId = 'codeBlockVar';
} else if (self.type === 'loop') {
assetId = 'codeBlockLoop';
} else if (self.type === 'condition') {
assetId = 'codeBlockCondition';
} else if (self.type === 'function') {
assetId = 'codeBlockFunction';
}
// Create main block
self.blockGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Add connection points
self.connectionPoints = {};
// Top connection
self.connectionPoints.top = self.addChild(LK.getAsset('connectionPoint', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -self.blockGraphics.height / 2
}));
// Bottom connection
self.connectionPoints.bottom = self.addChild(LK.getAsset('connectionPoint', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: self.blockGraphics.height / 2
}));
// Left connection (only for condition and loop types)
if (self.type === 'condition' || self.type === 'loop') {
self.connectionPoints.left = self.addChild(LK.getAsset('connectionPoint', {
anchorX: 0.5,
anchorY: 0.5,
x: -self.blockGraphics.width / 2,
y: 0
}));
}
// Right connection (only for condition type)
if (self.type === 'condition') {
self.connectionPoints.right = self.addChild(LK.getAsset('connectionPoint', {
anchorX: 0.5,
anchorY: 0.5,
x: self.blockGraphics.width / 2,
y: 0
}));
}
// Add text label
self.label = new Text2(self.text, {
size: 24,
fill: 0xFFFFFF
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
// Execution highlight (initially invisible)
self.highlight = self.addChild(LK.getAsset('executionHighlight', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
}));
// Handle interaction events
self.down = function (x, y, obj) {
if (!programIsRunning) {
self.startDragX = self.x;
self.startDragY = self.y;
self.isDragging = true;
draggedBlock = self;
// Bring to front
if (self.parent) {
var parent = self.parent;
parent.removeChild(self);
parent.addChild(self);
}
}
};
self.move = function (x, y, obj) {
// Movement handled by game's move handler
};
self.up = function (x, y, obj) {
if (self.isDragging) {
self.isDragging = false;
draggedBlock = null;
// Check if we're close to another block's connection point
var connected = false;
for (var i = 0; i < blocks.length; i++) {
var otherBlock = blocks[i];
if (otherBlock !== self && otherBlock.canConnect) {
connected = self.tryConnectTo(otherBlock);
if (connected) {
break;
}
}
}
if (!connected && codeArea.isPointInside(self.x, self.y)) {
// Snap to grid if in code area
self.x = Math.round(self.x / grid.cellSize) * grid.cellSize;
self.y = Math.round(self.y / grid.cellSize) * grid.cellSize;
} else if (!connected && !codeArea.isPointInside(self.x, self.y)) {
// Return to palette area if not connected and outside code area
self.x = self.startDragX;
self.y = self.startDragY;
}
}
};
self.tryConnectTo = function (otherBlock) {
if (!otherBlock || otherBlock === self) {
return false;
}
// Check proximity to connection points
var connectionThreshold = 50;
var myPoints = self.connectionPoints;
var otherPoints = otherBlock.connectionPoints;
// Check all possible connections
for (var myPos in myPoints) {
if (!myPoints[myPos]) {
continue;
}
var myGlobalPos = self.parent.toGlobal(myPoints[myPos].position);
for (var otherPos in otherPoints) {
if (!otherPoints[otherPos]) {
continue;
}
// Skip if this connection is already occupied
if (otherBlock.connections[otherPos]) {
continue;
}
var otherGlobalPos = otherBlock.parent.toGlobal(otherPoints[otherPos].position);
var distance = Math.sqrt(Math.pow(myGlobalPos.x - otherGlobalPos.x, 2) + Math.pow(myGlobalPos.y - otherGlobalPos.y, 2));
// If close enough, connect
if (distance < connectionThreshold) {
// Connect based on valid combinations
var validConnection = false;
// Top to bottom connections
if (myPos === 'bottom' && otherPos === 'top' || myPos === 'top' && otherPos === 'bottom') {
validConnection = true;
}
// Left/right connections for condition/loop blocks
if ((self.type === 'condition' || self.type === 'loop') && otherBlock.type !== 'condition' && otherBlock.type !== 'loop') {
if (myPos === 'left' && otherPos === 'top' || myPos === 'right' && otherPos === 'top') {
validConnection = true;
}
}
if (validConnection) {
self.connectTo(otherBlock, myPos, otherPos);
return true;
}
}
}
}
return false;
};
self.connectTo = function (otherBlock, myPos, otherPos) {
// Position calculation
if (myPos === 'bottom' && otherPos === 'top') {
// Align other block below this one
otherBlock.x = self.x;
otherBlock.y = self.y + self.blockGraphics.height;
// Update connections
self.connections.bottom = otherBlock;
otherBlock.connections.top = self;
} else if (myPos === 'top' && otherPos === 'bottom') {
// Align this block below other one
self.x = otherBlock.x;
self.y = otherBlock.y + otherBlock.blockGraphics.height;
// Update connections
self.connections.top = otherBlock;
otherBlock.connections.bottom = self;
} else if (myPos === 'left' && otherPos === 'top') {
// Align other block to the left of this one
otherBlock.x = self.x - self.blockGraphics.width / 2;
otherBlock.y = self.y;
// Update connections
self.connections.left = otherBlock;
otherBlock.connections.top = self;
} else if (myPos === 'right' && otherPos === 'top') {
// Align other block to the right of this one
otherBlock.x = self.x + self.blockGraphics.width / 2;
otherBlock.y = self.y;
// Update connections
self.connections.right = otherBlock;
otherBlock.connections.top = self;
}
// Play connection sound
LK.getSound('connect').play();
};
self.execute = function (callback) {
self.isExecuting = true;
// Show execution highlight
self.highlight.alpha = 0.5;
// Play execution sound
LK.getSound('execute').play();
// Execute block action based on type
var executionTime = 800; // Default execution time
switch (self.type) {
case 'variable':
// Variable assignment
executionLog.push("➡️ Set variable: " + self.text);
// Visual effect for variable assignment
tween(self.blockGraphics, {
alpha: 0.5
}, {
duration: 300,
onFinish: function onFinish() {
tween(self.blockGraphics, {
alpha: 1
}, {
duration: 300
});
}
});
break;
case 'loop':
// Loop block execution
executionLog.push("🔄 Loop: " + self.text);
// Visual effect for loops - make it pulse
tween(self.blockGraphics, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 400,
onFinish: function onFinish() {
tween(self.blockGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 400
});
}
});
// For loops, we need to execute differently
executionTime = 1000;
break;
case 'condition':
// Condition evaluation
executionLog.push("⚖️ If condition: " + self.text);
// Visual effect for conditions - flash slightly
tween(self.blockGraphics, {
alpha: 0.7
}, {
duration: 200,
onFinish: function onFinish() {
tween(self.blockGraphics, {
alpha: 1
}, {
duration: 200
});
}
});
break;
case 'function':
// Function call
executionLog.push("🧩 Call function: " + self.text);
// Visual effect for functions - rotate slightly
tween(self.blockGraphics, {
rotation: 0.1
}, {
duration: 300,
onFinish: function onFinish() {
tween(self.blockGraphics, {
rotation: 0
}, {
duration: 300
});
}
});
break;
default:
// Default block
executionLog.push("▶️ Execute: " + self.text);
// Visual effect for standard blocks
tween(self.blockGraphics, {
alpha: 0.8
}, {
duration: 200,
onFinish: function onFinish() {
tween(self.blockGraphics, {
alpha: 1
}, {
duration: 200
});
}
});
break;
}
// Update execution log display
updateExecutionLog();
// After execution, continue to next block
LK.setTimeout(function () {
self.highlight.alpha = 0;
self.isExecuting = false;
if (callback) {
callback();
}
}, executionTime);
};
return self;
});
var PreviewDisplay = Container.expand(function () {
var self = Container.call(this);
// Create the white cube display area
self.display = self.addChild(LK.getAsset('codeBlock', {
anchorX: 0,
anchorY: 0,
width: 500,
height: 500,
tint: 0xffffff
}));
// Title for the preview area
self.title = new Text2("Program Output", {
size: 30,
fill: 0x000000
});
self.title.anchor.set(0.5, 0);
self.title.x = self.display.width / 2;
self.title.y = 20;
self.addChild(self.title);
// Program output text
self.outputText = new Text2("Your program will run here", {
size: 24,
fill: 0x000000,
wordWrap: true,
wordWrapWidth: 460
});
self.outputText.anchor.set(0.5, 0);
self.outputText.x = self.display.width / 2;
self.outputText.y = 80;
self.addChild(self.outputText);
// Update the output display
self.updateOutput = function (results) {
if (!results || results.length === 0) {
self.outputText.setText("Your program will run here");
return;
}
var displayText = "";
// Take the last 5 operations or fewer
var startIndex = Math.max(0, results.length - 5);
for (var i = startIndex; i < results.length; i++) {
displayText += results[i] + "\n\n";
}
self.outputText.setText(displayText);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a24
});
/****
* Game Code
****/
// Grid settings
var grid = {
cellSize: 100
};
// Game state
var programIsRunning = false;
var draggedBlock = null;
var blocks = [];
var executionLog = [];
var creationScore = 0; // Score based on program complexity
// Function to update the execution log display
function updateExecutionLog() {
var logText = "Execution Log:\n";
for (var i = 0; i < executionLog.length; i++) {
logText += "- " + executionLog[i] + "\n";
}
executionLogText.setText(logText);
}
// Create the code area
var codeArea = new CodeArea(1200, 1500);
codeArea.x = 424; // Offset from left edge
codeArea.y = 150; // Offset from top
game.addChild(codeArea);
// Create block palette
var blockPalette = new BlockPalette(400, 2732);
blockPalette.x = 20; // Left edge
blockPalette.y = 0; // Top edge
game.addChild(blockPalette);
// Add blocks to palette
var yOffset = 100;
var blockSpacing = 120;
// Basic blocks
blockPalette.addBlockToPalette('default', "Print 'Hello World'", blockPalette.paletteWidth / 2, yOffset + blockSpacing);
blockPalette.addBlockToPalette('default', "Display Message", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 2);
blockPalette.addBlockToPalette('default', "Play Sound", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 3);
// Variable blocks
blockPalette.addBlockToPalette('variable', "x = 5", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 4);
blockPalette.addBlockToPalette('variable', "y = 10", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 5);
blockPalette.addBlockToPalette('variable', "counter = 0", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 6);
blockPalette.addBlockToPalette('variable', "name = 'Player'", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 7);
// Loop blocks
blockPalette.addBlockToPalette('loop', "Repeat 3 times", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 8);
blockPalette.addBlockToPalette('loop', "While true", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 9);
blockPalette.addBlockToPalette('loop', "For each item", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 10);
// Condition blocks
blockPalette.addBlockToPalette('condition', "If x > 0", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 11);
blockPalette.addBlockToPalette('condition', "If x == y", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 12);
blockPalette.addBlockToPalette('condition', "If counter > 10", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 13);
// Function blocks
blockPalette.addBlockToPalette('function', "Calculate Sum", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 14);
blockPalette.addBlockToPalette('function', "Get Random Number", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 15);
blockPalette.addBlockToPalette('function', "Convert to String", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 16);
// Action blocks
blockPalette.addBlockToPalette('default', "Draw Shape", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 17);
blockPalette.addBlockToPalette('default', "Move Forward", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 18);
blockPalette.addBlockToPalette('default', "Turn Right", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 19);
blockPalette.addBlockToPalette('default', "Turn Left", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 20);
// Create program builder title
var titleText = new Text2("BlockCode Builder", {
size: 50,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0);
titleText.x = codeArea.x + codeArea.areaWidth / 2;
titleText.y = 50;
game.addChild(titleText);
// Create instructions text
var instructionsText = new Text2("Create and run your own programs by connecting blocks from the palette", {
size: 30,
fill: 0xFFFFFF,
wordWrap: true,
wordWrapWidth: 1100
});
instructionsText.anchor.set(0.5, 0);
instructionsText.x = codeArea.x + codeArea.areaWidth / 2;
instructionsText.y = 110;
game.addChild(instructionsText);
// Execution log
var executionLogText = new Text2("Execution Log:\n", {
size: 24,
fill: 0xFFFFFF,
wordWrap: true,
wordWrapWidth: 1100
});
executionLogText.anchor.set(0, 0);
executionLogText.x = codeArea.x + 50;
executionLogText.y = codeArea.y + codeArea.areaHeight + 20;
game.addChild(executionLogText);
// Create run button
var runButton = new Button("Execute Program", 220, 80, 0x4CAF50);
runButton.x = codeArea.x + codeArea.areaWidth - 150;
runButton.y = codeArea.y + codeArea.areaHeight + 50;
runButton.action = function () {
if (!programIsRunning) {
runProgram();
}
};
game.addChild(runButton);
// Create reset button
var resetButton = new Button("Clear Canvas", 180, 80, 0xF44336);
resetButton.x = codeArea.x + 100;
resetButton.y = codeArea.y + codeArea.areaHeight + 50;
resetButton.action = function () {
resetProgram();
};
game.addChild(resetButton);
// Create preview display area
var previewDisplay = new PreviewDisplay();
previewDisplay.x = codeArea.x + codeArea.areaWidth / 2 - 250; // Center horizontally under code area
previewDisplay.y = 2732 - 550; // Position at bottom of screen
game.addChild(previewDisplay);
// Create score display
var scoreText = new Text2("Score: 0", {
size: 40,
fill: 0xFFFFFF
});
scoreText.anchor.set(1, 0);
scoreText.x = 2048 - 50;
scoreText.y = 50;
game.addChild(scoreText);
// Function to find all start blocks (blocks without a top connection in code area)
function findStartBlocks() {
var startBlocks = [];
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
if (codeArea.isPointInside(block.x, block.y) && !block.connections.top) {
startBlocks.push(block);
}
}
return startBlocks;
}
// Function to run the program
function runProgram() {
// Clear execution log
executionLog = [];
updateExecutionLog();
// Find all starting blocks
var startingBlocks = [];
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
if (codeArea.isPointInside(block.x, block.y) && !block.connections.top) {
startingBlocks.push(block);
}
}
if (startingBlocks.length === 0) {
executionLog.push("No starting blocks found!");
updateExecutionLog();
// Update preview display
previewDisplay.updateOutput(["No starting blocks found!"]);
return;
}
programIsRunning = true;
// Calculate program complexity before execution
calculateProgramComplexity();
// Execute all starting blocks in sequence
var currentBlockIndex = 0;
function executeNextBlock() {
if (currentBlockIndex < startingBlocks.length) {
executionLog.push("--- Running Program " + (currentBlockIndex + 1) + " ---");
updateExecutionLog();
executeBlock(startingBlocks[currentBlockIndex], function () {
currentBlockIndex++;
executeNextBlock();
});
} else {
// All programs executed
programIsRunning = false;
// Reward the player for successfully running programs
LK.setScore(creationScore);
scoreText.setText("Score: " + LK.getScore());
// Update the preview display with the execution results
previewDisplay.updateOutput(executionLog);
// Play completion sound for visual feedback
if (executionLog.length > 0) {
LK.getSound('complete').play();
}
}
}
// Start executing blocks
executeNextBlock();
}
// Function to execute a block and its connected blocks
function executeBlock(block, callback) {
if (!block) {
if (callback) {
callback();
}
return;
}
block.execute(function () {
// After execution, continue to next block based on type
if (block.type === 'condition') {
// For condition, randomly go left or right branch
var randomBranch = Math.random() > 0.5 ? 'left' : 'right';
if (block.connections[randomBranch]) {
executeBlock(block.connections[randomBranch], function () {
// After branch execution, continue with bottom block if any
executeBlock(block.connections.bottom, callback);
});
} else {
// No branch, go to bottom
executeBlock(block.connections.bottom, callback);
}
} else if (block.type === 'loop') {
// For loop, execute left branch twice, then continue
if (block.connections.left) {
executeBlock(block.connections.left, function () {
// Execute loop content again
if (block.connections.left) {
executeBlock(block.connections.left, function () {
// Then continue with bottom block
executeBlock(block.connections.bottom, callback);
});
} else {
executeBlock(block.connections.bottom, callback);
}
});
} else {
// No loop content, go to bottom
executeBlock(block.connections.bottom, callback);
}
} else {
// For other blocks, just go to bottom
executeBlock(block.connections.bottom, callback);
}
});
}
// Function to reset the program
function resetProgram() {
// Clear blocks in the code area
var blocksRemoved = 0;
for (var i = blocks.length - 1; i >= 0; i--) {
var block = blocks[i];
if (codeArea.isPointInside(block.x, block.y)) {
block.destroy();
blocks.splice(i, 1);
blocksRemoved++;
}
}
// Clear execution log
executionLog = [];
executionLog.push("Program reset. " + blocksRemoved + " blocks removed.");
updateExecutionLog();
// Reset preview display
previewDisplay.updateOutput(["Program has been reset. Add blocks to create a new program."]);
// Reset score
creationScore = 0;
}
// Function to calculate program complexity and score
function calculateProgramComplexity() {
// Reset score for this program
creationScore = 0;
// Count total blocks in the program
var programBlocks = 0;
var variableCount = 0;
var loopCount = 0;
var conditionCount = 0;
var functionCount = 0;
var defaultBlockCount = 0;
// Count different types of blocks in the code area
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
if (codeArea.isPointInside(block.x, block.y)) {
programBlocks++;
switch (block.type) {
case 'variable':
variableCount++;
break;
case 'loop':
loopCount++;
break;
case 'condition':
conditionCount++;
break;
case 'function':
functionCount++;
break;
default:
defaultBlockCount++;
break;
}
}
}
// Base score for having a program
if (programBlocks > 0) {
creationScore += 10;
}
// Award points for program complexity
creationScore += variableCount * 15;
creationScore += loopCount * 25;
creationScore += conditionCount * 20;
creationScore += functionCount * 20;
creationScore += defaultBlockCount * 10;
// Bonus for more complex programs
if (programBlocks >= 5) {
creationScore += 50;
}
if (variableCount > 0 && loopCount > 0 && conditionCount > 0) {
creationScore += 100; // Bonus for using all major programming concepts
}
// Play success sound if we have a reasonably complex program
if (programBlocks >= 3 && (loopCount > 0 || conditionCount > 0)) {
LK.getSound('complete').play();
}
}
// Handle game events
game.move = function (x, y, obj) {
if (draggedBlock && draggedBlock.isDragging) {
draggedBlock.x = x;
draggedBlock.y = y;
}
};
game.down = function (x, y, obj) {
// Default handler, individual objects have their own handlers
};
game.up = function (x, y, obj) {
// Default handler, individual objects have their own handlers
};
// Start background music
LK.playMusic('bgmusic'); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var BlockPalette = Container.expand(function (width, height) {
var self = Container.call(this);
self.paletteWidth = width || 400;
self.paletteHeight = height || 2732;
// Create background
self.background = self.addChild(LK.getAsset('codeBlock', {
anchorX: 0,
anchorY: 0,
width: self.paletteWidth,
height: self.paletteHeight,
alpha: 0.3,
tint: 0x333333
}));
// Add title
self.title = new Text2("Block Palette", {
size: 40,
fill: 0xFFFFFF
});
self.title.anchor.set(0.5, 0);
self.title.x = self.paletteWidth / 2;
self.title.y = 30;
self.addChild(self.title);
// Add blocks to palette
self.addBlockToPalette = function (type, text, x, y) {
var block = new CodeBlock(type, text);
block.x = x;
block.y = y;
// This is a template block - when dragged, it creates a new instance
block.down = function (x, y, obj) {
// Create a new block when this template is clicked
var newBlock = new CodeBlock(type, text);
newBlock.x = block.x;
newBlock.y = block.y;
// Convert to global position
var globalPos = self.parent.toGlobal({
x: block.x,
y: block.y
});
newBlock.x = globalPos.x;
newBlock.y = globalPos.y;
// Add to game's blocks array
blocks.push(newBlock);
// Add to game
game.addChild(newBlock);
// Start dragging the new block
newBlock.startDragX = newBlock.x;
newBlock.startDragY = newBlock.y;
newBlock.isDragging = true;
draggedBlock = newBlock;
};
self.addChild(block);
return block;
};
return self;
});
var Button = Container.expand(function (text, width, height, color) {
var self = Container.call(this);
self.buttonWidth = width || 200;
self.buttonHeight = height || 80;
self.buttonColor = color || 0x4287f5;
// Create button background
self.background = self.addChild(LK.getAsset('codeBlock', {
anchorX: 0.5,
anchorY: 0.5,
width: self.buttonWidth,
height: self.buttonHeight,
tint: self.buttonColor
}));
// Add text
self.label = new Text2(text, {
size: 30,
fill: 0xFFFFFF
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
// Handle down event
self.down = function (x, y, obj) {
// Visual feedback
tween(self.background, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
};
// Handle up event
self.up = function (x, y, obj) {
// Reset scale
tween(self.background, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
// Call the button's action
if (self.action) {
self.action();
}
}
});
};
return self;
});
var CodeArea = Container.expand(function (width, height) {
var self = Container.call(this);
self.areaWidth = width || 1200;
self.areaHeight = height || 1500;
// Create background
self.background = self.addChild(LK.getAsset('codeBlock', {
anchorX: 0,
anchorY: 0,
width: self.areaWidth,
height: self.areaHeight,
alpha: 0.2,
tint: 0x222222
}));
// Method to check if a point is inside the code area
self.isPointInside = function (x, y) {
var globalPos = game.toLocal({
x: x,
y: y
}, self.parent);
return globalPos.x >= self.x && globalPos.x <= self.x + self.areaWidth && globalPos.y >= self.y && globalPos.y <= self.y + self.areaHeight;
};
return self;
});
var CodeBlock = Container.expand(function (type, text) {
var self = Container.call(this);
self.type = type || 'default';
self.text = text || '';
self.connections = {
top: null,
bottom: null,
left: null,
right: null
};
self.canConnect = true;
self.isExecuting = false;
// Select the correct asset based on type
var assetId = 'codeBlock';
if (self.type === 'variable') {
assetId = 'codeBlockVar';
} else if (self.type === 'loop') {
assetId = 'codeBlockLoop';
} else if (self.type === 'condition') {
assetId = 'codeBlockCondition';
} else if (self.type === 'function') {
assetId = 'codeBlockFunction';
}
// Create main block
self.blockGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Add connection points
self.connectionPoints = {};
// Top connection
self.connectionPoints.top = self.addChild(LK.getAsset('connectionPoint', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -self.blockGraphics.height / 2
}));
// Bottom connection
self.connectionPoints.bottom = self.addChild(LK.getAsset('connectionPoint', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: self.blockGraphics.height / 2
}));
// Left connection (only for condition and loop types)
if (self.type === 'condition' || self.type === 'loop') {
self.connectionPoints.left = self.addChild(LK.getAsset('connectionPoint', {
anchorX: 0.5,
anchorY: 0.5,
x: -self.blockGraphics.width / 2,
y: 0
}));
}
// Right connection (only for condition type)
if (self.type === 'condition') {
self.connectionPoints.right = self.addChild(LK.getAsset('connectionPoint', {
anchorX: 0.5,
anchorY: 0.5,
x: self.blockGraphics.width / 2,
y: 0
}));
}
// Add text label
self.label = new Text2(self.text, {
size: 24,
fill: 0xFFFFFF
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
// Execution highlight (initially invisible)
self.highlight = self.addChild(LK.getAsset('executionHighlight', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
}));
// Handle interaction events
self.down = function (x, y, obj) {
if (!programIsRunning) {
self.startDragX = self.x;
self.startDragY = self.y;
self.isDragging = true;
draggedBlock = self;
// Bring to front
if (self.parent) {
var parent = self.parent;
parent.removeChild(self);
parent.addChild(self);
}
}
};
self.move = function (x, y, obj) {
// Movement handled by game's move handler
};
self.up = function (x, y, obj) {
if (self.isDragging) {
self.isDragging = false;
draggedBlock = null;
// Check if we're close to another block's connection point
var connected = false;
for (var i = 0; i < blocks.length; i++) {
var otherBlock = blocks[i];
if (otherBlock !== self && otherBlock.canConnect) {
connected = self.tryConnectTo(otherBlock);
if (connected) {
break;
}
}
}
if (!connected && codeArea.isPointInside(self.x, self.y)) {
// Snap to grid if in code area
self.x = Math.round(self.x / grid.cellSize) * grid.cellSize;
self.y = Math.round(self.y / grid.cellSize) * grid.cellSize;
} else if (!connected && !codeArea.isPointInside(self.x, self.y)) {
// Return to palette area if not connected and outside code area
self.x = self.startDragX;
self.y = self.startDragY;
}
}
};
self.tryConnectTo = function (otherBlock) {
if (!otherBlock || otherBlock === self) {
return false;
}
// Check proximity to connection points
var connectionThreshold = 50;
var myPoints = self.connectionPoints;
var otherPoints = otherBlock.connectionPoints;
// Check all possible connections
for (var myPos in myPoints) {
if (!myPoints[myPos]) {
continue;
}
var myGlobalPos = self.parent.toGlobal(myPoints[myPos].position);
for (var otherPos in otherPoints) {
if (!otherPoints[otherPos]) {
continue;
}
// Skip if this connection is already occupied
if (otherBlock.connections[otherPos]) {
continue;
}
var otherGlobalPos = otherBlock.parent.toGlobal(otherPoints[otherPos].position);
var distance = Math.sqrt(Math.pow(myGlobalPos.x - otherGlobalPos.x, 2) + Math.pow(myGlobalPos.y - otherGlobalPos.y, 2));
// If close enough, connect
if (distance < connectionThreshold) {
// Connect based on valid combinations
var validConnection = false;
// Top to bottom connections
if (myPos === 'bottom' && otherPos === 'top' || myPos === 'top' && otherPos === 'bottom') {
validConnection = true;
}
// Left/right connections for condition/loop blocks
if ((self.type === 'condition' || self.type === 'loop') && otherBlock.type !== 'condition' && otherBlock.type !== 'loop') {
if (myPos === 'left' && otherPos === 'top' || myPos === 'right' && otherPos === 'top') {
validConnection = true;
}
}
if (validConnection) {
self.connectTo(otherBlock, myPos, otherPos);
return true;
}
}
}
}
return false;
};
self.connectTo = function (otherBlock, myPos, otherPos) {
// Position calculation
if (myPos === 'bottom' && otherPos === 'top') {
// Align other block below this one
otherBlock.x = self.x;
otherBlock.y = self.y + self.blockGraphics.height;
// Update connections
self.connections.bottom = otherBlock;
otherBlock.connections.top = self;
} else if (myPos === 'top' && otherPos === 'bottom') {
// Align this block below other one
self.x = otherBlock.x;
self.y = otherBlock.y + otherBlock.blockGraphics.height;
// Update connections
self.connections.top = otherBlock;
otherBlock.connections.bottom = self;
} else if (myPos === 'left' && otherPos === 'top') {
// Align other block to the left of this one
otherBlock.x = self.x - self.blockGraphics.width / 2;
otherBlock.y = self.y;
// Update connections
self.connections.left = otherBlock;
otherBlock.connections.top = self;
} else if (myPos === 'right' && otherPos === 'top') {
// Align other block to the right of this one
otherBlock.x = self.x + self.blockGraphics.width / 2;
otherBlock.y = self.y;
// Update connections
self.connections.right = otherBlock;
otherBlock.connections.top = self;
}
// Play connection sound
LK.getSound('connect').play();
};
self.execute = function (callback) {
self.isExecuting = true;
// Show execution highlight
self.highlight.alpha = 0.5;
// Play execution sound
LK.getSound('execute').play();
// Execute block action based on type
var executionTime = 800; // Default execution time
switch (self.type) {
case 'variable':
// Variable assignment
executionLog.push("➡️ Set variable: " + self.text);
// Visual effect for variable assignment
tween(self.blockGraphics, {
alpha: 0.5
}, {
duration: 300,
onFinish: function onFinish() {
tween(self.blockGraphics, {
alpha: 1
}, {
duration: 300
});
}
});
break;
case 'loop':
// Loop block execution
executionLog.push("🔄 Loop: " + self.text);
// Visual effect for loops - make it pulse
tween(self.blockGraphics, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 400,
onFinish: function onFinish() {
tween(self.blockGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 400
});
}
});
// For loops, we need to execute differently
executionTime = 1000;
break;
case 'condition':
// Condition evaluation
executionLog.push("⚖️ If condition: " + self.text);
// Visual effect for conditions - flash slightly
tween(self.blockGraphics, {
alpha: 0.7
}, {
duration: 200,
onFinish: function onFinish() {
tween(self.blockGraphics, {
alpha: 1
}, {
duration: 200
});
}
});
break;
case 'function':
// Function call
executionLog.push("🧩 Call function: " + self.text);
// Visual effect for functions - rotate slightly
tween(self.blockGraphics, {
rotation: 0.1
}, {
duration: 300,
onFinish: function onFinish() {
tween(self.blockGraphics, {
rotation: 0
}, {
duration: 300
});
}
});
break;
default:
// Default block
executionLog.push("▶️ Execute: " + self.text);
// Visual effect for standard blocks
tween(self.blockGraphics, {
alpha: 0.8
}, {
duration: 200,
onFinish: function onFinish() {
tween(self.blockGraphics, {
alpha: 1
}, {
duration: 200
});
}
});
break;
}
// Update execution log display
updateExecutionLog();
// After execution, continue to next block
LK.setTimeout(function () {
self.highlight.alpha = 0;
self.isExecuting = false;
if (callback) {
callback();
}
}, executionTime);
};
return self;
});
var PreviewDisplay = Container.expand(function () {
var self = Container.call(this);
// Create the white cube display area
self.display = self.addChild(LK.getAsset('codeBlock', {
anchorX: 0,
anchorY: 0,
width: 500,
height: 500,
tint: 0xffffff
}));
// Title for the preview area
self.title = new Text2("Program Output", {
size: 30,
fill: 0x000000
});
self.title.anchor.set(0.5, 0);
self.title.x = self.display.width / 2;
self.title.y = 20;
self.addChild(self.title);
// Program output text
self.outputText = new Text2("Your program will run here", {
size: 24,
fill: 0x000000,
wordWrap: true,
wordWrapWidth: 460
});
self.outputText.anchor.set(0.5, 0);
self.outputText.x = self.display.width / 2;
self.outputText.y = 80;
self.addChild(self.outputText);
// Update the output display
self.updateOutput = function (results) {
if (!results || results.length === 0) {
self.outputText.setText("Your program will run here");
return;
}
var displayText = "";
// Take the last 5 operations or fewer
var startIndex = Math.max(0, results.length - 5);
for (var i = startIndex; i < results.length; i++) {
displayText += results[i] + "\n\n";
}
self.outputText.setText(displayText);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a24
});
/****
* Game Code
****/
// Grid settings
var grid = {
cellSize: 100
};
// Game state
var programIsRunning = false;
var draggedBlock = null;
var blocks = [];
var executionLog = [];
var creationScore = 0; // Score based on program complexity
// Function to update the execution log display
function updateExecutionLog() {
var logText = "Execution Log:\n";
for (var i = 0; i < executionLog.length; i++) {
logText += "- " + executionLog[i] + "\n";
}
executionLogText.setText(logText);
}
// Create the code area
var codeArea = new CodeArea(1200, 1500);
codeArea.x = 424; // Offset from left edge
codeArea.y = 150; // Offset from top
game.addChild(codeArea);
// Create block palette
var blockPalette = new BlockPalette(400, 2732);
blockPalette.x = 20; // Left edge
blockPalette.y = 0; // Top edge
game.addChild(blockPalette);
// Add blocks to palette
var yOffset = 100;
var blockSpacing = 120;
// Basic blocks
blockPalette.addBlockToPalette('default', "Print 'Hello World'", blockPalette.paletteWidth / 2, yOffset + blockSpacing);
blockPalette.addBlockToPalette('default', "Display Message", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 2);
blockPalette.addBlockToPalette('default', "Play Sound", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 3);
// Variable blocks
blockPalette.addBlockToPalette('variable', "x = 5", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 4);
blockPalette.addBlockToPalette('variable', "y = 10", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 5);
blockPalette.addBlockToPalette('variable', "counter = 0", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 6);
blockPalette.addBlockToPalette('variable', "name = 'Player'", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 7);
// Loop blocks
blockPalette.addBlockToPalette('loop', "Repeat 3 times", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 8);
blockPalette.addBlockToPalette('loop', "While true", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 9);
blockPalette.addBlockToPalette('loop', "For each item", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 10);
// Condition blocks
blockPalette.addBlockToPalette('condition', "If x > 0", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 11);
blockPalette.addBlockToPalette('condition', "If x == y", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 12);
blockPalette.addBlockToPalette('condition', "If counter > 10", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 13);
// Function blocks
blockPalette.addBlockToPalette('function', "Calculate Sum", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 14);
blockPalette.addBlockToPalette('function', "Get Random Number", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 15);
blockPalette.addBlockToPalette('function', "Convert to String", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 16);
// Action blocks
blockPalette.addBlockToPalette('default', "Draw Shape", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 17);
blockPalette.addBlockToPalette('default', "Move Forward", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 18);
blockPalette.addBlockToPalette('default', "Turn Right", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 19);
blockPalette.addBlockToPalette('default', "Turn Left", blockPalette.paletteWidth / 2, yOffset + blockSpacing * 20);
// Create program builder title
var titleText = new Text2("BlockCode Builder", {
size: 50,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0);
titleText.x = codeArea.x + codeArea.areaWidth / 2;
titleText.y = 50;
game.addChild(titleText);
// Create instructions text
var instructionsText = new Text2("Create and run your own programs by connecting blocks from the palette", {
size: 30,
fill: 0xFFFFFF,
wordWrap: true,
wordWrapWidth: 1100
});
instructionsText.anchor.set(0.5, 0);
instructionsText.x = codeArea.x + codeArea.areaWidth / 2;
instructionsText.y = 110;
game.addChild(instructionsText);
// Execution log
var executionLogText = new Text2("Execution Log:\n", {
size: 24,
fill: 0xFFFFFF,
wordWrap: true,
wordWrapWidth: 1100
});
executionLogText.anchor.set(0, 0);
executionLogText.x = codeArea.x + 50;
executionLogText.y = codeArea.y + codeArea.areaHeight + 20;
game.addChild(executionLogText);
// Create run button
var runButton = new Button("Execute Program", 220, 80, 0x4CAF50);
runButton.x = codeArea.x + codeArea.areaWidth - 150;
runButton.y = codeArea.y + codeArea.areaHeight + 50;
runButton.action = function () {
if (!programIsRunning) {
runProgram();
}
};
game.addChild(runButton);
// Create reset button
var resetButton = new Button("Clear Canvas", 180, 80, 0xF44336);
resetButton.x = codeArea.x + 100;
resetButton.y = codeArea.y + codeArea.areaHeight + 50;
resetButton.action = function () {
resetProgram();
};
game.addChild(resetButton);
// Create preview display area
var previewDisplay = new PreviewDisplay();
previewDisplay.x = codeArea.x + codeArea.areaWidth / 2 - 250; // Center horizontally under code area
previewDisplay.y = 2732 - 550; // Position at bottom of screen
game.addChild(previewDisplay);
// Create score display
var scoreText = new Text2("Score: 0", {
size: 40,
fill: 0xFFFFFF
});
scoreText.anchor.set(1, 0);
scoreText.x = 2048 - 50;
scoreText.y = 50;
game.addChild(scoreText);
// Function to find all start blocks (blocks without a top connection in code area)
function findStartBlocks() {
var startBlocks = [];
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
if (codeArea.isPointInside(block.x, block.y) && !block.connections.top) {
startBlocks.push(block);
}
}
return startBlocks;
}
// Function to run the program
function runProgram() {
// Clear execution log
executionLog = [];
updateExecutionLog();
// Find all starting blocks
var startingBlocks = [];
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
if (codeArea.isPointInside(block.x, block.y) && !block.connections.top) {
startingBlocks.push(block);
}
}
if (startingBlocks.length === 0) {
executionLog.push("No starting blocks found!");
updateExecutionLog();
// Update preview display
previewDisplay.updateOutput(["No starting blocks found!"]);
return;
}
programIsRunning = true;
// Calculate program complexity before execution
calculateProgramComplexity();
// Execute all starting blocks in sequence
var currentBlockIndex = 0;
function executeNextBlock() {
if (currentBlockIndex < startingBlocks.length) {
executionLog.push("--- Running Program " + (currentBlockIndex + 1) + " ---");
updateExecutionLog();
executeBlock(startingBlocks[currentBlockIndex], function () {
currentBlockIndex++;
executeNextBlock();
});
} else {
// All programs executed
programIsRunning = false;
// Reward the player for successfully running programs
LK.setScore(creationScore);
scoreText.setText("Score: " + LK.getScore());
// Update the preview display with the execution results
previewDisplay.updateOutput(executionLog);
// Play completion sound for visual feedback
if (executionLog.length > 0) {
LK.getSound('complete').play();
}
}
}
// Start executing blocks
executeNextBlock();
}
// Function to execute a block and its connected blocks
function executeBlock(block, callback) {
if (!block) {
if (callback) {
callback();
}
return;
}
block.execute(function () {
// After execution, continue to next block based on type
if (block.type === 'condition') {
// For condition, randomly go left or right branch
var randomBranch = Math.random() > 0.5 ? 'left' : 'right';
if (block.connections[randomBranch]) {
executeBlock(block.connections[randomBranch], function () {
// After branch execution, continue with bottom block if any
executeBlock(block.connections.bottom, callback);
});
} else {
// No branch, go to bottom
executeBlock(block.connections.bottom, callback);
}
} else if (block.type === 'loop') {
// For loop, execute left branch twice, then continue
if (block.connections.left) {
executeBlock(block.connections.left, function () {
// Execute loop content again
if (block.connections.left) {
executeBlock(block.connections.left, function () {
// Then continue with bottom block
executeBlock(block.connections.bottom, callback);
});
} else {
executeBlock(block.connections.bottom, callback);
}
});
} else {
// No loop content, go to bottom
executeBlock(block.connections.bottom, callback);
}
} else {
// For other blocks, just go to bottom
executeBlock(block.connections.bottom, callback);
}
});
}
// Function to reset the program
function resetProgram() {
// Clear blocks in the code area
var blocksRemoved = 0;
for (var i = blocks.length - 1; i >= 0; i--) {
var block = blocks[i];
if (codeArea.isPointInside(block.x, block.y)) {
block.destroy();
blocks.splice(i, 1);
blocksRemoved++;
}
}
// Clear execution log
executionLog = [];
executionLog.push("Program reset. " + blocksRemoved + " blocks removed.");
updateExecutionLog();
// Reset preview display
previewDisplay.updateOutput(["Program has been reset. Add blocks to create a new program."]);
// Reset score
creationScore = 0;
}
// Function to calculate program complexity and score
function calculateProgramComplexity() {
// Reset score for this program
creationScore = 0;
// Count total blocks in the program
var programBlocks = 0;
var variableCount = 0;
var loopCount = 0;
var conditionCount = 0;
var functionCount = 0;
var defaultBlockCount = 0;
// Count different types of blocks in the code area
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
if (codeArea.isPointInside(block.x, block.y)) {
programBlocks++;
switch (block.type) {
case 'variable':
variableCount++;
break;
case 'loop':
loopCount++;
break;
case 'condition':
conditionCount++;
break;
case 'function':
functionCount++;
break;
default:
defaultBlockCount++;
break;
}
}
}
// Base score for having a program
if (programBlocks > 0) {
creationScore += 10;
}
// Award points for program complexity
creationScore += variableCount * 15;
creationScore += loopCount * 25;
creationScore += conditionCount * 20;
creationScore += functionCount * 20;
creationScore += defaultBlockCount * 10;
// Bonus for more complex programs
if (programBlocks >= 5) {
creationScore += 50;
}
if (variableCount > 0 && loopCount > 0 && conditionCount > 0) {
creationScore += 100; // Bonus for using all major programming concepts
}
// Play success sound if we have a reasonably complex program
if (programBlocks >= 3 && (loopCount > 0 || conditionCount > 0)) {
LK.getSound('complete').play();
}
}
// Handle game events
game.move = function (x, y, obj) {
if (draggedBlock && draggedBlock.isDragging) {
draggedBlock.x = x;
draggedBlock.y = y;
}
};
game.down = function (x, y, obj) {
// Default handler, individual objects have their own handlers
};
game.up = function (x, y, obj) {
// Default handler, individual objects have their own handlers
};
// Start background music
LK.playMusic('bgmusic');