/**** * 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');