/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // NumberBubble: Represents a draggable number on the board var NumberBubble = Container.expand(function () { var self = Container.call(this); // Attach a circular shape as the bubble var bubble = self.attachAsset('bubble', { anchorX: 0.5, anchorY: 0.5 }); // Attach the number as text self.text = new Text2('', { size: 120, fill: 0xFFFFFF }); self.text.anchor.set(0.5, 0.5); self.addChild(self.text); // Value of the bubble self.value = 0; // Is this bubble currently selected? self.selected = false; // Set value and update text self.setValue = function (v) { self.value = v; self.text.setText(v + ''); }; // Visual feedback for selection self.setSelected = function (isSelected) { self.selected = isSelected; if (isSelected) { tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 150, easing: tween.easeOut }); } else { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 150, easing: tween.easeOut }); } }; // Destroy bubble self.destroyBubble = function () { // Animate out tween(self, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 200, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); // OperationButton: Represents an operation (+, -, ×, ÷) button var OperationButton = Container.expand(function () { var self = Container.call(this); // Attach a box shape as the button var btn = self.attachAsset('opbtn', { anchorX: 0.5, anchorY: 0.5 }); // Attach the operation symbol as text self.text = new Text2('', { size: 100, fill: 0x222222 }); self.text.anchor.set(0.5, 0.5); self.addChild(self.text); // Operation type: '+', '-', '×', '÷' self.op = '+'; // Set operation and update text self.setOp = function (op) { self.op = op; self.text.setText(op); }; // Visual feedback for selection self.setSelected = function (isSelected) { if (isSelected) { tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 120, easing: tween.easeOut }); } else { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeOut }); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1a1a2e }); /**** * Game Code ****/ // --- Game parameters --- var NUM_BUBBLES = 5; var MIN_NUM = 1; var MAX_NUM = 20; var OPERATIONS = ['+', '-', '×', '÷']; var MOVE_LIMIT = 8; // Number of allowed moves var BUBBLE_RADIUS = 160; var BUBBLE_SPACING = 340; var BOARD_MARGIN = 200; // --- State --- var bubbles = []; var opButtons = []; var selectedBubbles = []; var selectedOp = null; var targetNumber = 0; var moveCount = 0; var moveLimit = MOVE_LIMIT; var gameActive = true; // --- UI Elements --- var targetText, movesText, infoText; // --- Asset Initialization --- // --- Helper Functions --- // Generate a random integer between min and max (inclusive) function randInt(min, max) { return min + Math.floor(Math.random() * (max - min + 1)); } // Shuffle an array in-place function shuffle(arr) { for (var i = arr.length - 1; i > 0; i--) { var j = randInt(0, i); var t = arr[i]; arr[i] = arr[j]; arr[j] = t; } } // Generate a solvable puzzle: returns {numbers:[], target: number} function generatePuzzle() { // Start with a random number as the "target" var numbers = []; var steps = NUM_BUBBLES - 1; var current = randInt(10, 50); var history = [current]; for (var i = 0; i < steps; i++) { // Pick a random operation var op = OPERATIONS[randInt(0, OPERATIONS.length - 1)]; // Pick a random operand var operand; if (op === '+') { operand = randInt(MIN_NUM, MAX_NUM); current = current - operand; } else if (op === '-') { operand = randInt(MIN_NUM, Math.min(current, MAX_NUM)); current = current + operand; } else if (op === '×') { operand = randInt(2, 5); if (current % operand !== 0) { current = current * operand; } current = current / operand; } else if (op === '÷') { operand = randInt(2, 5); current = current * operand; } numbers.push(operand); history.push(current); } numbers.push(Math.round(current)); shuffle(numbers); return { numbers: numbers.map(function (n) { return Math.round(n); }), target: Math.round(history[0]) }; } // Reset game state function resetGame() { // Remove old bubbles for (var i = 0; i < bubbles.length; i++) { bubbles[i].destroy(); } bubbles = []; selectedBubbles = []; selectedOp = null; moveCount = 0; gameActive = true; // Generate puzzle var puzzle = generatePuzzle(); var nums = puzzle.numbers; targetNumber = puzzle.target; moveLimit = MOVE_LIMIT; // Place bubbles in a circle var centerX = 2048 / 2; var centerY = 1200; var angleStep = Math.PI * 2 / nums.length; for (var i = 0; i < nums.length; i++) { var nb = new NumberBubble(); nb.setValue(nums[i]); nb.x = centerX + Math.cos(i * angleStep - Math.PI / 2) * BUBBLE_SPACING; nb.y = centerY + Math.sin(i * angleStep - Math.PI / 2) * BUBBLE_SPACING; nb.scaleX = nb.scaleY = 1; nb.alpha = 1; nb.index = i; nb.setSelected(false); game.addChild(nb); bubbles.push(nb); } // Update UI targetText.setText('Target: ' + targetNumber); movesText.setText('Moves: 0/' + moveLimit); infoText.setText('Select two numbers, pick an operation!'); } // --- UI Setup --- // Target number display targetText = new Text2('Target: 0', { size: 120, fill: 0xFFBE0B }); targetText.anchor.set(0.5, 0); // Move the target text further down by setting its y position targetText.y = 180; LK.gui.top.addChild(targetText); // Moves left display movesText = new Text2('Moves: 0/' + MOVE_LIMIT, { size: 90, fill: 0xFFFFFF }); movesText.anchor.set(0.5, 0); LK.gui.top.addChild(movesText); // Info text infoText = new Text2('', { size: 80, fill: 0xFFFFFF }); infoText.anchor.set(0.5, 0); LK.gui.bottom.addChild(infoText); // --- Operation Buttons --- var opAreaY = 2100; var opAreaX = 2048 / 2 - 2 * 220; for (var i = 0; i < OPERATIONS.length; i++) { var opBtn = new OperationButton(); opBtn.setOp(OPERATIONS[i]); opBtn.x = opAreaX + i * 220; opBtn.y = opAreaY; opBtn.scaleX = opBtn.scaleY = 1; opBtn.setSelected(false); game.addChild(opBtn); opButtons.push(opBtn); } // --- Input Handling --- // Helper: get bubble at (x, y) function getBubbleAt(x, y) { for (var i = 0; i < bubbles.length; i++) { var b = bubbles[i]; var dx = x - b.x; var dy = y - b.y; var r = BUBBLE_RADIUS; if (dx * dx + dy * dy <= r * r) { return b; } } return null; } // Helper: get op button at (x, y) function getOpButtonAt(x, y) { for (var i = 0; i < opButtons.length; i++) { var btn = opButtons[i]; var bx = btn.x, by = btn.y; var w = 180, h = 180; if (x >= bx - w / 2 && x <= bx + w / 2 && y >= by - h / 2 && y <= by + h / 2) { return btn; } } return null; } // Main input handler game.down = function (x, y, obj) { if (!gameActive) return; // Try to select a bubble var b = getBubbleAt(x, y); if (b && selectedBubbles.indexOf(b) === -1) { if (selectedBubbles.length < 2) { selectedBubbles.push(b); b.setSelected(true); } // Deselect if already selected else if (selectedBubbles.length === 2) { for (var i = 0; i < selectedBubbles.length; i++) { selectedBubbles[i].setSelected(false); } selectedBubbles = [b]; b.setSelected(true); } infoText.setText(selectedBubbles.length === 2 ? 'Pick an operation!' : 'Select one more number!'); return; } // Try to select an operation var opBtn = getOpButtonAt(x, y); if (opBtn && selectedBubbles.length === 2) { // Deselect previous op for (var i = 0; i < opButtons.length; i++) { opButtons[i].setSelected(false); } opBtn.setSelected(true); selectedOp = opBtn.op; // Apply operation applyOperation(); return; } }; // --- Operation Logic --- function applyOperation() { if (!gameActive) return; if (selectedBubbles.length !== 2 || !selectedOp) return; var a = selectedBubbles[0]; var b = selectedBubbles[1]; var v1 = a.value, v2 = b.value; var result = 0; var valid = true; if (selectedOp === '+') { result = v1 + v2; } else if (selectedOp === '-') { result = v1 - v2; if (result < 0) valid = false; } else if (selectedOp === '×') { result = v1 * v2; } else if (selectedOp === '÷') { if (v2 === 0 || v1 % v2 !== 0) valid = false;else result = v1 / v2; } if (!valid) { infoText.setText('Invalid operation!'); // Deselect for (var i = 0; i < selectedBubbles.length; i++) { selectedBubbles[i].setSelected(false); } selectedBubbles = []; for (var i = 0; i < opButtons.length; i++) { opButtons[i].setSelected(false); } selectedOp = null; return; } // Remove used bubbles a.destroyBubble(); b.destroyBubble(); var idxA = bubbles.indexOf(a); var idxB = bubbles.indexOf(b); if (idxA > -1) bubbles.splice(idxA, 1); if (idxB > -1 && idxB !== idxA) bubbles.splice(bubbles.indexOf(b), 1); // Add new bubble at average position var newBubble = new NumberBubble(); newBubble.setValue(result); newBubble.x = (a.x + b.x) / 2; newBubble.y = (a.y + b.y) / 2; newBubble.scaleX = newBubble.scaleY = 0.5; newBubble.alpha = 0; newBubble.setSelected(false); game.addChild(newBubble); bubbles.push(newBubble); tween(newBubble, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 200, easing: tween.easeOut }); // Deselect all for (var i = 0; i < opButtons.length; i++) { opButtons[i].setSelected(false); } selectedBubbles = []; selectedOp = null; // Update move count moveCount++; movesText.setText('Moves: ' + moveCount + '/' + moveLimit); // Check win/lose checkGameState(); } // --- Game State Check --- function checkGameState() { // Win: Only one bubble left and its value == target if (bubbles.length === 1 && bubbles[0].value === targetNumber) { infoText.setText('Congratulations! You reached the target!'); LK.setScore(1); LK.showYouWin(); gameActive = false; return; } // Lose: Out of moves or no more valid moves if (moveCount >= moveLimit) { infoText.setText('No moves left!'); LK.setScore(0); LK.showGameOver(); gameActive = false; return; } // If only one bubble left but not target if (bubbles.length === 1 && bubbles[0].value !== targetNumber) { infoText.setText('You did not reach the target!'); LK.setScore(0); LK.showGameOver(); gameActive = false; return; } // Otherwise, keep playing infoText.setText('Select two numbers, pick an operation!'); } // --- Game Update Loop --- game.update = function () { // No per-frame logic needed for now }; // --- Start Game --- resetGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// NumberBubble: Represents a draggable number on the board
var NumberBubble = Container.expand(function () {
var self = Container.call(this);
// Attach a circular shape as the bubble
var bubble = self.attachAsset('bubble', {
anchorX: 0.5,
anchorY: 0.5
});
// Attach the number as text
self.text = new Text2('', {
size: 120,
fill: 0xFFFFFF
});
self.text.anchor.set(0.5, 0.5);
self.addChild(self.text);
// Value of the bubble
self.value = 0;
// Is this bubble currently selected?
self.selected = false;
// Set value and update text
self.setValue = function (v) {
self.value = v;
self.text.setText(v + '');
};
// Visual feedback for selection
self.setSelected = function (isSelected) {
self.selected = isSelected;
if (isSelected) {
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.easeOut
});
} else {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeOut
});
}
};
// Destroy bubble
self.destroyBubble = function () {
// Animate out
tween(self, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
// OperationButton: Represents an operation (+, -, ×, ÷) button
var OperationButton = Container.expand(function () {
var self = Container.call(this);
// Attach a box shape as the button
var btn = self.attachAsset('opbtn', {
anchorX: 0.5,
anchorY: 0.5
});
// Attach the operation symbol as text
self.text = new Text2('', {
size: 100,
fill: 0x222222
});
self.text.anchor.set(0.5, 0.5);
self.addChild(self.text);
// Operation type: '+', '-', '×', '÷'
self.op = '+';
// Set operation and update text
self.setOp = function (op) {
self.op = op;
self.text.setText(op);
};
// Visual feedback for selection
self.setSelected = function (isSelected) {
if (isSelected) {
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 120,
easing: tween.easeOut
});
} else {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeOut
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// --- Game parameters ---
var NUM_BUBBLES = 5;
var MIN_NUM = 1;
var MAX_NUM = 20;
var OPERATIONS = ['+', '-', '×', '÷'];
var MOVE_LIMIT = 8; // Number of allowed moves
var BUBBLE_RADIUS = 160;
var BUBBLE_SPACING = 340;
var BOARD_MARGIN = 200;
// --- State ---
var bubbles = [];
var opButtons = [];
var selectedBubbles = [];
var selectedOp = null;
var targetNumber = 0;
var moveCount = 0;
var moveLimit = MOVE_LIMIT;
var gameActive = true;
// --- UI Elements ---
var targetText, movesText, infoText;
// --- Asset Initialization ---
// --- Helper Functions ---
// Generate a random integer between min and max (inclusive)
function randInt(min, max) {
return min + Math.floor(Math.random() * (max - min + 1));
}
// Shuffle an array in-place
function shuffle(arr) {
for (var i = arr.length - 1; i > 0; i--) {
var j = randInt(0, i);
var t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
// Generate a solvable puzzle: returns {numbers:[], target: number}
function generatePuzzle() {
// Start with a random number as the "target"
var numbers = [];
var steps = NUM_BUBBLES - 1;
var current = randInt(10, 50);
var history = [current];
for (var i = 0; i < steps; i++) {
// Pick a random operation
var op = OPERATIONS[randInt(0, OPERATIONS.length - 1)];
// Pick a random operand
var operand;
if (op === '+') {
operand = randInt(MIN_NUM, MAX_NUM);
current = current - operand;
} else if (op === '-') {
operand = randInt(MIN_NUM, Math.min(current, MAX_NUM));
current = current + operand;
} else if (op === '×') {
operand = randInt(2, 5);
if (current % operand !== 0) {
current = current * operand;
}
current = current / operand;
} else if (op === '÷') {
operand = randInt(2, 5);
current = current * operand;
}
numbers.push(operand);
history.push(current);
}
numbers.push(Math.round(current));
shuffle(numbers);
return {
numbers: numbers.map(function (n) {
return Math.round(n);
}),
target: Math.round(history[0])
};
}
// Reset game state
function resetGame() {
// Remove old bubbles
for (var i = 0; i < bubbles.length; i++) {
bubbles[i].destroy();
}
bubbles = [];
selectedBubbles = [];
selectedOp = null;
moveCount = 0;
gameActive = true;
// Generate puzzle
var puzzle = generatePuzzle();
var nums = puzzle.numbers;
targetNumber = puzzle.target;
moveLimit = MOVE_LIMIT;
// Place bubbles in a circle
var centerX = 2048 / 2;
var centerY = 1200;
var angleStep = Math.PI * 2 / nums.length;
for (var i = 0; i < nums.length; i++) {
var nb = new NumberBubble();
nb.setValue(nums[i]);
nb.x = centerX + Math.cos(i * angleStep - Math.PI / 2) * BUBBLE_SPACING;
nb.y = centerY + Math.sin(i * angleStep - Math.PI / 2) * BUBBLE_SPACING;
nb.scaleX = nb.scaleY = 1;
nb.alpha = 1;
nb.index = i;
nb.setSelected(false);
game.addChild(nb);
bubbles.push(nb);
}
// Update UI
targetText.setText('Target: ' + targetNumber);
movesText.setText('Moves: 0/' + moveLimit);
infoText.setText('Select two numbers, pick an operation!');
}
// --- UI Setup ---
// Target number display
targetText = new Text2('Target: 0', {
size: 120,
fill: 0xFFBE0B
});
targetText.anchor.set(0.5, 0);
// Move the target text further down by setting its y position
targetText.y = 180;
LK.gui.top.addChild(targetText);
// Moves left display
movesText = new Text2('Moves: 0/' + MOVE_LIMIT, {
size: 90,
fill: 0xFFFFFF
});
movesText.anchor.set(0.5, 0);
LK.gui.top.addChild(movesText);
// Info text
infoText = new Text2('', {
size: 80,
fill: 0xFFFFFF
});
infoText.anchor.set(0.5, 0);
LK.gui.bottom.addChild(infoText);
// --- Operation Buttons ---
var opAreaY = 2100;
var opAreaX = 2048 / 2 - 2 * 220;
for (var i = 0; i < OPERATIONS.length; i++) {
var opBtn = new OperationButton();
opBtn.setOp(OPERATIONS[i]);
opBtn.x = opAreaX + i * 220;
opBtn.y = opAreaY;
opBtn.scaleX = opBtn.scaleY = 1;
opBtn.setSelected(false);
game.addChild(opBtn);
opButtons.push(opBtn);
}
// --- Input Handling ---
// Helper: get bubble at (x, y)
function getBubbleAt(x, y) {
for (var i = 0; i < bubbles.length; i++) {
var b = bubbles[i];
var dx = x - b.x;
var dy = y - b.y;
var r = BUBBLE_RADIUS;
if (dx * dx + dy * dy <= r * r) {
return b;
}
}
return null;
}
// Helper: get op button at (x, y)
function getOpButtonAt(x, y) {
for (var i = 0; i < opButtons.length; i++) {
var btn = opButtons[i];
var bx = btn.x,
by = btn.y;
var w = 180,
h = 180;
if (x >= bx - w / 2 && x <= bx + w / 2 && y >= by - h / 2 && y <= by + h / 2) {
return btn;
}
}
return null;
}
// Main input handler
game.down = function (x, y, obj) {
if (!gameActive) return;
// Try to select a bubble
var b = getBubbleAt(x, y);
if (b && selectedBubbles.indexOf(b) === -1) {
if (selectedBubbles.length < 2) {
selectedBubbles.push(b);
b.setSelected(true);
}
// Deselect if already selected
else if (selectedBubbles.length === 2) {
for (var i = 0; i < selectedBubbles.length; i++) {
selectedBubbles[i].setSelected(false);
}
selectedBubbles = [b];
b.setSelected(true);
}
infoText.setText(selectedBubbles.length === 2 ? 'Pick an operation!' : 'Select one more number!');
return;
}
// Try to select an operation
var opBtn = getOpButtonAt(x, y);
if (opBtn && selectedBubbles.length === 2) {
// Deselect previous op
for (var i = 0; i < opButtons.length; i++) {
opButtons[i].setSelected(false);
}
opBtn.setSelected(true);
selectedOp = opBtn.op;
// Apply operation
applyOperation();
return;
}
};
// --- Operation Logic ---
function applyOperation() {
if (!gameActive) return;
if (selectedBubbles.length !== 2 || !selectedOp) return;
var a = selectedBubbles[0];
var b = selectedBubbles[1];
var v1 = a.value,
v2 = b.value;
var result = 0;
var valid = true;
if (selectedOp === '+') {
result = v1 + v2;
} else if (selectedOp === '-') {
result = v1 - v2;
if (result < 0) valid = false;
} else if (selectedOp === '×') {
result = v1 * v2;
} else if (selectedOp === '÷') {
if (v2 === 0 || v1 % v2 !== 0) valid = false;else result = v1 / v2;
}
if (!valid) {
infoText.setText('Invalid operation!');
// Deselect
for (var i = 0; i < selectedBubbles.length; i++) {
selectedBubbles[i].setSelected(false);
}
selectedBubbles = [];
for (var i = 0; i < opButtons.length; i++) {
opButtons[i].setSelected(false);
}
selectedOp = null;
return;
}
// Remove used bubbles
a.destroyBubble();
b.destroyBubble();
var idxA = bubbles.indexOf(a);
var idxB = bubbles.indexOf(b);
if (idxA > -1) bubbles.splice(idxA, 1);
if (idxB > -1 && idxB !== idxA) bubbles.splice(bubbles.indexOf(b), 1);
// Add new bubble at average position
var newBubble = new NumberBubble();
newBubble.setValue(result);
newBubble.x = (a.x + b.x) / 2;
newBubble.y = (a.y + b.y) / 2;
newBubble.scaleX = newBubble.scaleY = 0.5;
newBubble.alpha = 0;
newBubble.setSelected(false);
game.addChild(newBubble);
bubbles.push(newBubble);
tween(newBubble, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 200,
easing: tween.easeOut
});
// Deselect all
for (var i = 0; i < opButtons.length; i++) {
opButtons[i].setSelected(false);
}
selectedBubbles = [];
selectedOp = null;
// Update move count
moveCount++;
movesText.setText('Moves: ' + moveCount + '/' + moveLimit);
// Check win/lose
checkGameState();
}
// --- Game State Check ---
function checkGameState() {
// Win: Only one bubble left and its value == target
if (bubbles.length === 1 && bubbles[0].value === targetNumber) {
infoText.setText('Congratulations! You reached the target!');
LK.setScore(1);
LK.showYouWin();
gameActive = false;
return;
}
// Lose: Out of moves or no more valid moves
if (moveCount >= moveLimit) {
infoText.setText('No moves left!');
LK.setScore(0);
LK.showGameOver();
gameActive = false;
return;
}
// If only one bubble left but not target
if (bubbles.length === 1 && bubbles[0].value !== targetNumber) {
infoText.setText('You did not reach the target!');
LK.setScore(0);
LK.showGameOver();
gameActive = false;
return;
}
// Otherwise, keep playing
infoText.setText('Select two numbers, pick an operation!');
}
// --- Game Update Loop ---
game.update = function () {
// No per-frame logic needed for now
};
// --- Start Game ---
resetGame();