/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // (Cloud class removed for space setting) // Dark Wader (Darth Vader inspired) character for space setting var Lumberjack = Container.expand(function () { var self = Container.call(this); // Side: 'left' or 'right' self.side = 'left'; // --- Construction --- // Boots (black) var bootL = self.attachAsset('karate_leg', { anchorX: 0.5, anchorY: 0.1, x: -18, y: 0, scaleX: 1.1, scaleY: 1.1, tint: 0x222222 }); var bootR = self.attachAsset('karate_leg', { anchorX: 0.5, anchorY: 0.1, x: 18, y: 0, scaleX: 1.1, scaleY: 1.1, tint: 0x222222 }); // Legs (dark gray) var pants = self.attachAsset('karate_leg', { anchorX: 0.5, anchorY: 1, x: 0, y: -10, scaleX: 1.3, scaleY: 1.2, tint: 0x444444 }); // Body armor (dark, Vader chest) var armor = self.attachAsset('karate_body', { anchorX: 0.5, anchorY: 1, x: 0, y: -60, scaleX: 1.2, scaleY: 1.15, tint: 0x222222 }); // Chest panel (Vader's control panel, colored) var chestPanel = self.attachAsset('karate_belt', { anchorX: 0.5, anchorY: 0, x: 0, y: -110, scaleX: 0.7, scaleY: 0.5, tint: 0x00ffcc }); // Add a red button var chestButton = self.attachAsset('karate_belt', { anchorX: 0.5, anchorY: 0, x: 10, y: -108, scaleX: 0.18, scaleY: 0.18, tint: 0xff2222 }); // Head (helmet, black) var helmet = self.attachAsset('karate_head', { anchorX: 0.5, anchorY: 1, x: 0, y: -160, scaleX: 1.15, scaleY: 1.15, tint: 0x222222 }); // Helmet dome (shine) var helmetShine = self.attachAsset('karate_hair', { anchorX: 0.5, anchorY: 1, x: -10, y: -180, scaleX: 0.5, scaleY: 0.7, tint: 0x888888 }); helmetShine.alpha = 0.25; // Face mask (gray, covers lower face) var mask = self.attachAsset('karate_belt', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -150, scaleX: 0.7, scaleY: 0.25, tint: 0x888888 }); // Eyes (red, menacing) var eyeL = self.attachAsset('karate_belt', { anchorX: 0.5, anchorY: 0.5, x: -10, y: -165, scaleX: 0.18, scaleY: 0.18, tint: 0xff2222 }); var eyeR = self.attachAsset('karate_belt', { anchorX: 0.5, anchorY: 0.5, x: 10, y: -165, scaleX: 0.18, scaleY: 0.18, tint: 0xff2222 }); // Cape (dark, behind body) var cape = self.attachAsset('karate_body', { anchorX: 0.5, anchorY: 1, x: 0, y: -60, scaleX: 1.5, scaleY: 1.5, tint: 0x111122 }); cape.alpha = 0.5; // Move cape to back self.setChildIndex(cape, 0); // Left Arm (sleeve, hand) var larm = self.attachAsset('karate_arm', { anchorX: 0.5, anchorY: 0.1, x: -38, y: -90, rotation: -0.25, scaleX: 1.1, scaleY: 1.1, tint: 0x222222 }); // Right Arm (sleeve, hand) var rarm = self.attachAsset('karate_arm', { anchorX: 0.5, anchorY: 0.1, x: 38, y: -90, rotation: 0.25, scaleX: 1.1, scaleY: 1.1, tint: 0x222222 }); // Lightsaber (replaces fist/axe, left hand) var saberBlade = self.attachAsset('katanaBlade', { anchorX: 0.1, anchorY: 0.5, x: -55, y: -100, scaleX: 1.1, scaleY: 0.5, tint: 0x00ffcc }); var saberGlow = self.attachAsset('katanaEdge', { anchorX: 0.1, anchorY: 0.5, x: -55, y: -100, scaleX: 1.1, scaleY: 0.7, tint: 0x00ffff }); saberGlow.alpha = 0.5; // Set side ('left' or 'right') self.setSide = function (side) { self.side = side; // Use armor width for offset var charWidth = armor.width; if (side === 'left') { self.x = treeX - trunkWidth / 2 - charWidth / 2 - 40; self.scaleX = 1; } else { self.x = treeX + trunkWidth / 2 + charWidth / 2 + 40; self.scaleX = -1; } }; // Chop animation: swing saber! self.chop = function () { // Animate a quick scale for feedback tween(self, { scaleY: 0.92 }, { duration: 60, easing: tween.cubicOut, onFinish: function onFinish() { tween(self, { scaleY: 1 }, { duration: 80, easing: tween.cubicIn }); } }); // Saber/arm swing animation (right arm if facing right, left arm if facing left) var arm = self.side === 'left' ? larm : rarm; var origRot = arm.rotation; tween(arm, { rotation: origRot + (self.side === 'left' ? -1.0 : 1.0) }, { duration: 60, easing: tween.cubicOut, onFinish: function onFinish() { tween(arm, { rotation: origRot }, { duration: 80, easing: tween.cubicIn }); } }); }; return self; }); // ShootingStar: A single shooting star streaking across the sky var ShootingStar = Container.expand(function () { var self = Container.call(this); // Use a snowflake as the star head var star = self.attachAsset('snowflake', { anchorX: 0.5, anchorY: 0.5 }); // Randomize size and speed var scale = 0.7 + Math.random() * 0.7; star.scaleX = scale * (1.2 + Math.random() * 0.6); star.scaleY = scale * (0.7 + Math.random() * 0.3); star.alpha = 0.95; // Color: white or pale blue star.tint = Math.random() < 0.5 ? 0xffffff : 0x99ccff; // Shooting direction: mostly left-to-right, sometimes right-to-left var leftToRight = Math.random() < 0.7; var angle = Math.PI / 4 + Math.random() * Math.PI / 6 * (leftToRight ? 1 : -1); var speed = 18 + Math.random() * 10; self.speedX = Math.cos(angle) * speed * (leftToRight ? 1 : -1); self.speedY = Math.sin(angle) * speed; // Start position: random at top or upper left/right if (leftToRight) { self.x = -80 - Math.random() * 100; self.y = 100 + Math.random() * 800; } else { self.x = 2048 + 80 + Math.random() * 100; self.y = 100 + Math.random() * 800; } // Add a faint trail (multiple faded ellipses) self.trail = []; for (var i = 0; i < 6; i++) { var t = self.attachAsset('snowflake', { anchorX: 0.5, anchorY: 0.5, x: -self.speedX * 0.08 * i, y: -self.speedY * 0.08 * i, scaleX: star.scaleX * (0.7 - i * 0.09), scaleY: star.scaleY * (0.7 - i * 0.09), tint: star.tint }); t.alpha = 0.18 - i * 0.025; self.trail.push(t); } // Update method for shooting self.update = function () { self.x += self.speedX; self.y += self.speedY; // Fade out as it moves star.alpha *= 0.985; for (var i = 0; i < self.trail.length; i++) { self.trail[i].alpha *= 0.985; } // Remove if out of screen if (self.x < -120 || self.x > 2048 + 120 || self.y > 2732 + 120) { self.toRemove = true; } }; return self; }); // TreeSegment: A single segment of the space pillar, may have a katana (left/right/none) var TreeSegment = Container.expand(function () { var self = Container.call(this); // Default: no branch self.branch = 'none'; // 'left', 'right', 'none' // Space pillar trunk (futuristic, glowing, layered) var trunk = self.attachAsset('spacePillar', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.18 + Math.random() * 0.08, scaleY: 1.08 + Math.random() * 0.06 }); // Add glowing blue highlight for (var hi = 0; hi < 2 + Math.floor(Math.random() * 2); hi++) { var trunkGlow = self.attachAsset('spacePillarGlow', { anchorX: 0.5, anchorY: 0.5, x: -trunk.width * 0.18 + Math.random() * trunk.width * 0.36, y: -trunk.height * 0.18 + Math.random() * trunk.height * 0.36, scaleX: 0.5 + Math.random() * 0.25, scaleY: 0.12 + Math.random() * 0.08, tint: 0x44aaff, alpha: 0.13 + Math.random() * 0.09 }); } // Add white highlight for shine for (var hi2 = 0; hi2 < 1; hi2++) { var trunkHighlight = self.attachAsset('spacePillarHighlight', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -trunk.height * 0.18 + Math.random() * trunk.height * 0.36, scaleX: 0.22 + Math.random() * 0.18, scaleY: 0.10 + Math.random() * 0.08, tint: 0xffffff, alpha: 0.10 + Math.random() * 0.09 }); } // Branch (if any) self.branchNode = null; // Set up the segment (branchSide: 'left', 'right', or 'none') self.setup = function (branchSide) { self.branch = branchSide; if (self.branchNode) { self.removeChild(self.branchNode); self.branchNode = null; } if (branchSide === 'left') { var group = new Container(); var blade = group.attachAsset('katanaBlade', { anchorX: 1, anchorY: 0.5, x: -trunk.width / 2, y: 0, scaleX: 1.12 + Math.random() * 0.13, scaleY: 1.0 + Math.random() * 0.10, tint: 0xc0c0c0 }); var edge = group.attachAsset('katanaEdge', { anchorX: 1, anchorY: 0.5, x: -trunk.width / 2, y: 0, scaleX: 1.12 + Math.random() * 0.13, scaleY: 0.35 + Math.random() * 0.10, tint: 0xffffff }); var guard = group.attachAsset('katanaGuard', { anchorX: 1, anchorY: 0.5, x: -trunk.width / 2 - blade.width + 18, y: 0, scaleX: 0.7 + Math.random() * 0.2, scaleY: 0.7 + Math.random() * 0.2, tint: 0xffd700 }); var handle = group.attachAsset('katanaHandle', { anchorX: 1, anchorY: 0.5, x: -trunk.width / 2 - blade.width - 10, y: 0, scaleX: 1.0, scaleY: 1.0, tint: 0x6b4f1d }); self.branchNode = group; self.addChild(group); } else if (branchSide === 'right') { var group = new Container(); var blade = group.attachAsset('katanaBlade', { anchorX: 0, anchorY: 0.5, x: trunk.width / 2, y: 0, scaleX: 1.12 + Math.random() * 0.13, scaleY: 1.0 + Math.random() * 0.10, tint: 0xc0c0c0, flipX: 1 }); var edge = group.attachAsset('katanaEdge', { anchorX: 0, anchorY: 0.5, x: trunk.width / 2, y: 0, scaleX: 1.12 + Math.random() * 0.13, scaleY: 0.35 + Math.random() * 0.10, tint: 0xffffff, flipX: 1 }); var guard = group.attachAsset('katanaGuard', { anchorX: 0, anchorY: 0.5, x: trunk.width / 2 + blade.width - 18, y: 0, scaleX: 0.7 + Math.random() * 0.2, scaleY: 0.7 + Math.random() * 0.2, tint: 0xffd700, flipX: 1 }); var handle = group.attachAsset('katanaHandle', { anchorX: 0, anchorY: 0.5, x: trunk.width / 2 + blade.width + 10, y: 0, scaleX: 1.0, scaleY: 1.0, tint: 0x6b4f1d, flipX: 1 }); self.branchNode = group; self.addChild(group); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x07081a // Deep space blue-black }); /**** * Game Code ****/ // Star Wars lightsaber battle sounds (multiple for variety) // Fail sound // Chop sound // Lumberjack // Branch // Tree trunk segment // --- Asset Initialization (auto by LK engine) --- // --- Game Variables --- // Space setting: no clouds, just stars and shooting stars! var treeSegments = []; // Array of TreeSegment var treeX = 2048 / 2; var treeBaseY = 2732 - 350; // Y position of the bottom segment var trunkWidth = 210; var trunkHeight = 120; var visibleSegments = 13; // Number of segments visible on screen var branchChance = 0.35; // Probability of a branch per segment var lastBranch = 'none'; // To avoid impossible patterns var player = null; var scoreTxt = null; var timerBar = null; var timeLeft = 2000; // ms, time before game over var maxTime = 2000; // ms, resets to this on chop var timerInterval = null; var isGameOver = false; // --- Shooting Star System --- var snowflakes = []; // Now used for shooting stars var snowSpawnTimer = 0; var snowSpawnInterval = 18 + Math.floor(Math.random() * 12); // randomize interval for more natural effect // --- UI Setup --- scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); // Place the score text at the very top, centered horizontally scoreTxt.x = 0; scoreTxt.y = 0; LK.gui.top.addChild(scoreTxt); // Timer bar with improved visuals, placed just below the score text timerBar = LK.getAsset('timerBar', { width: 800, height: 38, color: 0x00b050, shape: 'box', anchorX: 0.5, anchorY: 0, x: 0, y: scoreTxt.y + scoreTxt.height + 10 // just below score text with a small gap }); timerBar.alpha = 0.93; timerBar.border = LK.getAsset('timerBar', { width: 820, height: 48, color: 0x222222, shape: 'box', anchorX: 0.5, anchorY: 0, x: 0, y: timerBar.y // match timerBar y }); timerBar.border.alpha = 0.25; LK.gui.top.addChild(timerBar.border); LK.gui.top.addChild(timerBar); // Timer bar hit animation function timerBarHitAnim() { tween(timerBar, { scaleY: 1.25 }, { duration: 60, easing: tween.cubicOut, onFinish: function onFinish() { tween(timerBar, { scaleY: 1 }, { duration: 100, easing: tween.cubicIn }); } }); tween(timerBar, { alpha: 1 }, { duration: 60, onFinish: function onFinish() { tween(timerBar, { alpha: 0.93 }, { duration: 100 }); } }); } // Timer bar explosion animation function timerBarExplodeAnim() { tween(timerBar, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 350, easing: tween.cubicOut }); tween(timerBar.border, { alpha: 0 }, { duration: 350 }); } // --- Tree Setup --- function randomBranch(prevBranch) { // Avoid two branches on the same side in a row var r = Math.random(); if (r < branchChance) { if (prevBranch === 'left') return 'right'; if (prevBranch === 'right') return 'left'; return Math.random() < 0.5 ? 'left' : 'right'; } return 'none'; } function createInitialTree() { var prev = 'none'; for (var i = 0; i < visibleSegments + 2; i++) { var seg = new TreeSegment(); var branch = randomBranch(prev); seg.setup(branch); seg.x = treeX; seg.y = treeBaseY - i * trunkHeight; treeSegments.push(seg); game.addChild(seg); prev = branch; } } function shiftTree() { // Remove bottom segment var bottom = treeSegments.shift(); bottom.destroy(); // Move all segments down for (var i = 0; i < treeSegments.length; i++) { tween(treeSegments[i], { y: treeBaseY - i * trunkHeight }, { duration: 80, easing: tween.cubicOut }); } // Add new segment at the top var prevBranch = treeSegments[treeSegments.length - 1].branch; var newSeg = new TreeSegment(); var branch = randomBranch(prevBranch); newSeg.setup(branch); newSeg.x = treeX; newSeg.y = treeSegments[treeSegments.length - 1].y - trunkHeight; treeSegments.push(newSeg); game.addChild(newSeg); } // --- Player Setup --- player = new Lumberjack(); player.setSide('left'); player.y = treeBaseY + trunkHeight / 2 + 10; game.addChild(player); // --- Game State --- var score = 0; // --- Timer --- function resetTimer() { timeLeft = maxTime; updateTimerBar(); } function updateTimerBar() { var pct = Math.max(0, timeLeft / maxTime); timerBar.width = 800 * pct; if (pct > 0.5) { timerBar.tint = 0x00b050; } else if (pct > 0.2) { timerBar.tint = 0xffc300; } else { timerBar.tint = 0xff0000; } } // --- Game Over --- function triggerGameOver() { if (isGameOver) return; isGameOver = true; timerBarExplodeAnim(); LK.effects.flashScreen(0xff0000, 800); // Play a better wood explosion sound and effect LK.getSound('fail').play(); // Play Darth Vader shouting Obi Wan (using the fail sound asset as placeholder for the shout) LK.getSound('fail', { start: 4.0, end: 6.0 }).play(); LK.effects.flashObject(treeSegments[0], 0xffe066, 400); // flash the bottom segment yellow for a wood burst tween(treeSegments[0], { scaleX: 2.2, scaleY: 2.2, alpha: 0 }, { duration: 420, easing: tween.cubicOut }); LK.showGameOver(); } // --- Chop Action --- function chop() { if (isGameOver) return; // Check for collision with branch on current side at the bottom segment var bottomSeg = treeSegments[0]; if (bottomSeg.branch === player.side) { triggerGameOver(); return; } // Chop: remove bottom, shift tree, add new at top shiftTree(); // Animate player player.chop(); timerBarHitAnim(); // Play sound LK.getSound('chop').play(); // Score score += 1; LK.setScore(score); scoreTxt.setText(score); // Reset timer, but make it a bit shorter as score increases maxTime = Math.max(800, 2000 - score * 10); resetTimer(); } // --- Input Handling --- var touchStartX = null; var touchStartY = null; var dragThreshold = 40; // px function handleDown(x, y, obj) { touchStartX = x; touchStartY = y; } function handleUp(x, y, obj) { if (isGameOver) return; if (touchStartX === null || touchStartY === null) return; var dx = x - touchStartX; var dy = y - touchStartY; // Swipe detection if (Math.abs(dx) > dragThreshold && Math.abs(dx) > Math.abs(dy)) { // Horizontal swipe if (dx > 0 && player.side === 'left') { player.setSide('right'); } else if (dx < 0 && player.side === 'right') { player.setSide('left'); } } else if (Math.abs(dx) < dragThreshold && Math.abs(dy) < dragThreshold) { // Tap: chop chop(); } touchStartX = null; touchStartY = null; } game.down = handleDown; game.up = handleUp; // --- Main Update Loop --- // --- Darth Vader Speech System --- var vaderSpeechInterval = 4200; // ms between speeches var vaderSpeechTimer = 0; var vaderSpeechActive = false; var vaderSpeechLines = [{ start: 0.0, end: 2.0 }, // "You don't know the power of the dark side" { start: 2.1, end: 4.0 }, // "I am your father" { start: 4.1, end: 6.0 }, // "Obi-Wan has taught you well" { start: 6.1, end: 8.0 }, // "Impressive. Most impressive." { start: 8.1, end: 10.0 } // "You are unwise to lower your defenses" ]; // Use the 'fail' sound asset as placeholder for Vader speech (replace with real asset id when available) var vaderSpeechAssetId = 'fail'; // --- Darth Vader Random Sound System --- var vaderRandomSoundInterval = 8000; // 8 seconds in ms var vaderRandomSoundTimer = 0; var vaderRandomSoundList = ['darthvadersound1', 'darthvadersound2', 'darthvadersound3', 'darthvadersound5']; game.update = function () { // --- Shooting star update --- snowSpawnTimer++; if (snowSpawnTimer >= snowSpawnInterval) { snowSpawnTimer = 0; // Only a few shooting stars at a time if (snowflakes.length < 7) { var star = new ShootingStar(); snowflakes.push(star); game.addChild(star); } // Randomize next interval for more natural effect snowSpawnInterval = 18 + Math.floor(Math.random() * 12); } // Update and remove shooting stars for (var i = snowflakes.length - 1; i >= 0; i--) { var star = snowflakes[i]; if (star.update) star.update(); if (star.toRemove) { star.destroy(); snowflakes.splice(i, 1); } } // Darth Vader speech logic if (!isGameOver) { vaderSpeechTimer += 1000 / 60; if (vaderSpeechTimer >= vaderSpeechInterval && !vaderSpeechActive) { vaderSpeechTimer = 0; vaderSpeechActive = true; // Pick a random speech line var idx = Math.floor(Math.random() * vaderSpeechLines.length); var line = vaderSpeechLines[idx]; // Play the speech line (using fail asset as placeholder) LK.getSound(vaderSpeechAssetId, { start: line.start, end: line.end }).play(); // Prevent overlap: allow new speech after a short delay LK.setTimeout(function () { vaderSpeechActive = false; }, (line.end - line.start) * 1000 + 400); } // --- Darth Vader random sound logic --- vaderRandomSoundTimer += 1000 / 60; if (vaderRandomSoundTimer >= vaderRandomSoundInterval) { vaderRandomSoundTimer = 0; // 88% chance to play Obi Wan (fail sound), 12% chance to play a random Vader sound if (Math.random() < 0.88) { // Play Obi Wan (fail sound) from 4.0s to 6.0s LK.getSound('fail', { start: 4.0, end: 6.0 }).play(); } else { // Pick a random sound from the list var soundIdx = Math.floor(Math.random() * vaderRandomSoundList.length); var soundId = vaderRandomSoundList[soundIdx]; LK.getSound(soundId).play(); } } } if (isGameOver) return; // Timer timeLeft -= 1000 / 60; updateTimerBar(); if (timeLeft <= 0) { triggerGameOver(); } }; // --- Game Initialization --- function resetGame() { // Remove old tree for (var i = 0; i < treeSegments.length; i++) { treeSegments[i].destroy(); } treeSegments = []; // Remove old snowflakes for (var i = 0; i < snowflakes.length; i++) { snowflakes[i].destroy(); } snowflakes = []; // Reset variables score = 0; LK.setScore(0); scoreTxt.setText('0'); isGameOver = false; maxTime = 2000; timerBar.scaleX = 1; timerBar.scaleY = 1; timerBar.alpha = 0.93; if (timerBar.border) timerBar.border.alpha = 0.25; resetTimer(); // Recreate tree createInitialTree(); // Reset player player.setSide('left'); player.y = treeBaseY + trunkHeight / 2 + 10; } // Reset Vader speech system vaderSpeechTimer = 0; vaderSpeechActive = false; // Reset random Vader sound timer vaderRandomSoundTimer = 0; // No league system or leaderboard/login UI. Game starts immediately. resetGame(); scoreTxt.visible = true; timerBar.visible = true; if (timerBar.border) timerBar.border.visible = true;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// (Cloud class removed for space setting)
// Dark Wader (Darth Vader inspired) character for space setting
var Lumberjack = Container.expand(function () {
var self = Container.call(this);
// Side: 'left' or 'right'
self.side = 'left';
// --- Construction ---
// Boots (black)
var bootL = self.attachAsset('karate_leg', {
anchorX: 0.5,
anchorY: 0.1,
x: -18,
y: 0,
scaleX: 1.1,
scaleY: 1.1,
tint: 0x222222
});
var bootR = self.attachAsset('karate_leg', {
anchorX: 0.5,
anchorY: 0.1,
x: 18,
y: 0,
scaleX: 1.1,
scaleY: 1.1,
tint: 0x222222
});
// Legs (dark gray)
var pants = self.attachAsset('karate_leg', {
anchorX: 0.5,
anchorY: 1,
x: 0,
y: -10,
scaleX: 1.3,
scaleY: 1.2,
tint: 0x444444
});
// Body armor (dark, Vader chest)
var armor = self.attachAsset('karate_body', {
anchorX: 0.5,
anchorY: 1,
x: 0,
y: -60,
scaleX: 1.2,
scaleY: 1.15,
tint: 0x222222
});
// Chest panel (Vader's control panel, colored)
var chestPanel = self.attachAsset('karate_belt', {
anchorX: 0.5,
anchorY: 0,
x: 0,
y: -110,
scaleX: 0.7,
scaleY: 0.5,
tint: 0x00ffcc
});
// Add a red button
var chestButton = self.attachAsset('karate_belt', {
anchorX: 0.5,
anchorY: 0,
x: 10,
y: -108,
scaleX: 0.18,
scaleY: 0.18,
tint: 0xff2222
});
// Head (helmet, black)
var helmet = self.attachAsset('karate_head', {
anchorX: 0.5,
anchorY: 1,
x: 0,
y: -160,
scaleX: 1.15,
scaleY: 1.15,
tint: 0x222222
});
// Helmet dome (shine)
var helmetShine = self.attachAsset('karate_hair', {
anchorX: 0.5,
anchorY: 1,
x: -10,
y: -180,
scaleX: 0.5,
scaleY: 0.7,
tint: 0x888888
});
helmetShine.alpha = 0.25;
// Face mask (gray, covers lower face)
var mask = self.attachAsset('karate_belt', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -150,
scaleX: 0.7,
scaleY: 0.25,
tint: 0x888888
});
// Eyes (red, menacing)
var eyeL = self.attachAsset('karate_belt', {
anchorX: 0.5,
anchorY: 0.5,
x: -10,
y: -165,
scaleX: 0.18,
scaleY: 0.18,
tint: 0xff2222
});
var eyeR = self.attachAsset('karate_belt', {
anchorX: 0.5,
anchorY: 0.5,
x: 10,
y: -165,
scaleX: 0.18,
scaleY: 0.18,
tint: 0xff2222
});
// Cape (dark, behind body)
var cape = self.attachAsset('karate_body', {
anchorX: 0.5,
anchorY: 1,
x: 0,
y: -60,
scaleX: 1.5,
scaleY: 1.5,
tint: 0x111122
});
cape.alpha = 0.5;
// Move cape to back
self.setChildIndex(cape, 0);
// Left Arm (sleeve, hand)
var larm = self.attachAsset('karate_arm', {
anchorX: 0.5,
anchorY: 0.1,
x: -38,
y: -90,
rotation: -0.25,
scaleX: 1.1,
scaleY: 1.1,
tint: 0x222222
});
// Right Arm (sleeve, hand)
var rarm = self.attachAsset('karate_arm', {
anchorX: 0.5,
anchorY: 0.1,
x: 38,
y: -90,
rotation: 0.25,
scaleX: 1.1,
scaleY: 1.1,
tint: 0x222222
});
// Lightsaber (replaces fist/axe, left hand)
var saberBlade = self.attachAsset('katanaBlade', {
anchorX: 0.1,
anchorY: 0.5,
x: -55,
y: -100,
scaleX: 1.1,
scaleY: 0.5,
tint: 0x00ffcc
});
var saberGlow = self.attachAsset('katanaEdge', {
anchorX: 0.1,
anchorY: 0.5,
x: -55,
y: -100,
scaleX: 1.1,
scaleY: 0.7,
tint: 0x00ffff
});
saberGlow.alpha = 0.5;
// Set side ('left' or 'right')
self.setSide = function (side) {
self.side = side;
// Use armor width for offset
var charWidth = armor.width;
if (side === 'left') {
self.x = treeX - trunkWidth / 2 - charWidth / 2 - 40;
self.scaleX = 1;
} else {
self.x = treeX + trunkWidth / 2 + charWidth / 2 + 40;
self.scaleX = -1;
}
};
// Chop animation: swing saber!
self.chop = function () {
// Animate a quick scale for feedback
tween(self, {
scaleY: 0.92
}, {
duration: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self, {
scaleY: 1
}, {
duration: 80,
easing: tween.cubicIn
});
}
});
// Saber/arm swing animation (right arm if facing right, left arm if facing left)
var arm = self.side === 'left' ? larm : rarm;
var origRot = arm.rotation;
tween(arm, {
rotation: origRot + (self.side === 'left' ? -1.0 : 1.0)
}, {
duration: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(arm, {
rotation: origRot
}, {
duration: 80,
easing: tween.cubicIn
});
}
});
};
return self;
});
// ShootingStar: A single shooting star streaking across the sky
var ShootingStar = Container.expand(function () {
var self = Container.call(this);
// Use a snowflake as the star head
var star = self.attachAsset('snowflake', {
anchorX: 0.5,
anchorY: 0.5
});
// Randomize size and speed
var scale = 0.7 + Math.random() * 0.7;
star.scaleX = scale * (1.2 + Math.random() * 0.6);
star.scaleY = scale * (0.7 + Math.random() * 0.3);
star.alpha = 0.95;
// Color: white or pale blue
star.tint = Math.random() < 0.5 ? 0xffffff : 0x99ccff;
// Shooting direction: mostly left-to-right, sometimes right-to-left
var leftToRight = Math.random() < 0.7;
var angle = Math.PI / 4 + Math.random() * Math.PI / 6 * (leftToRight ? 1 : -1);
var speed = 18 + Math.random() * 10;
self.speedX = Math.cos(angle) * speed * (leftToRight ? 1 : -1);
self.speedY = Math.sin(angle) * speed;
// Start position: random at top or upper left/right
if (leftToRight) {
self.x = -80 - Math.random() * 100;
self.y = 100 + Math.random() * 800;
} else {
self.x = 2048 + 80 + Math.random() * 100;
self.y = 100 + Math.random() * 800;
}
// Add a faint trail (multiple faded ellipses)
self.trail = [];
for (var i = 0; i < 6; i++) {
var t = self.attachAsset('snowflake', {
anchorX: 0.5,
anchorY: 0.5,
x: -self.speedX * 0.08 * i,
y: -self.speedY * 0.08 * i,
scaleX: star.scaleX * (0.7 - i * 0.09),
scaleY: star.scaleY * (0.7 - i * 0.09),
tint: star.tint
});
t.alpha = 0.18 - i * 0.025;
self.trail.push(t);
}
// Update method for shooting
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
// Fade out as it moves
star.alpha *= 0.985;
for (var i = 0; i < self.trail.length; i++) {
self.trail[i].alpha *= 0.985;
}
// Remove if out of screen
if (self.x < -120 || self.x > 2048 + 120 || self.y > 2732 + 120) {
self.toRemove = true;
}
};
return self;
});
// TreeSegment: A single segment of the space pillar, may have a katana (left/right/none)
var TreeSegment = Container.expand(function () {
var self = Container.call(this);
// Default: no branch
self.branch = 'none'; // 'left', 'right', 'none'
// Space pillar trunk (futuristic, glowing, layered)
var trunk = self.attachAsset('spacePillar', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.18 + Math.random() * 0.08,
scaleY: 1.08 + Math.random() * 0.06
});
// Add glowing blue highlight
for (var hi = 0; hi < 2 + Math.floor(Math.random() * 2); hi++) {
var trunkGlow = self.attachAsset('spacePillarGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: -trunk.width * 0.18 + Math.random() * trunk.width * 0.36,
y: -trunk.height * 0.18 + Math.random() * trunk.height * 0.36,
scaleX: 0.5 + Math.random() * 0.25,
scaleY: 0.12 + Math.random() * 0.08,
tint: 0x44aaff,
alpha: 0.13 + Math.random() * 0.09
});
}
// Add white highlight for shine
for (var hi2 = 0; hi2 < 1; hi2++) {
var trunkHighlight = self.attachAsset('spacePillarHighlight', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -trunk.height * 0.18 + Math.random() * trunk.height * 0.36,
scaleX: 0.22 + Math.random() * 0.18,
scaleY: 0.10 + Math.random() * 0.08,
tint: 0xffffff,
alpha: 0.10 + Math.random() * 0.09
});
}
// Branch (if any)
self.branchNode = null;
// Set up the segment (branchSide: 'left', 'right', or 'none')
self.setup = function (branchSide) {
self.branch = branchSide;
if (self.branchNode) {
self.removeChild(self.branchNode);
self.branchNode = null;
}
if (branchSide === 'left') {
var group = new Container();
var blade = group.attachAsset('katanaBlade', {
anchorX: 1,
anchorY: 0.5,
x: -trunk.width / 2,
y: 0,
scaleX: 1.12 + Math.random() * 0.13,
scaleY: 1.0 + Math.random() * 0.10,
tint: 0xc0c0c0
});
var edge = group.attachAsset('katanaEdge', {
anchorX: 1,
anchorY: 0.5,
x: -trunk.width / 2,
y: 0,
scaleX: 1.12 + Math.random() * 0.13,
scaleY: 0.35 + Math.random() * 0.10,
tint: 0xffffff
});
var guard = group.attachAsset('katanaGuard', {
anchorX: 1,
anchorY: 0.5,
x: -trunk.width / 2 - blade.width + 18,
y: 0,
scaleX: 0.7 + Math.random() * 0.2,
scaleY: 0.7 + Math.random() * 0.2,
tint: 0xffd700
});
var handle = group.attachAsset('katanaHandle', {
anchorX: 1,
anchorY: 0.5,
x: -trunk.width / 2 - blade.width - 10,
y: 0,
scaleX: 1.0,
scaleY: 1.0,
tint: 0x6b4f1d
});
self.branchNode = group;
self.addChild(group);
} else if (branchSide === 'right') {
var group = new Container();
var blade = group.attachAsset('katanaBlade', {
anchorX: 0,
anchorY: 0.5,
x: trunk.width / 2,
y: 0,
scaleX: 1.12 + Math.random() * 0.13,
scaleY: 1.0 + Math.random() * 0.10,
tint: 0xc0c0c0,
flipX: 1
});
var edge = group.attachAsset('katanaEdge', {
anchorX: 0,
anchorY: 0.5,
x: trunk.width / 2,
y: 0,
scaleX: 1.12 + Math.random() * 0.13,
scaleY: 0.35 + Math.random() * 0.10,
tint: 0xffffff,
flipX: 1
});
var guard = group.attachAsset('katanaGuard', {
anchorX: 0,
anchorY: 0.5,
x: trunk.width / 2 + blade.width - 18,
y: 0,
scaleX: 0.7 + Math.random() * 0.2,
scaleY: 0.7 + Math.random() * 0.2,
tint: 0xffd700,
flipX: 1
});
var handle = group.attachAsset('katanaHandle', {
anchorX: 0,
anchorY: 0.5,
x: trunk.width / 2 + blade.width + 10,
y: 0,
scaleX: 1.0,
scaleY: 1.0,
tint: 0x6b4f1d,
flipX: 1
});
self.branchNode = group;
self.addChild(group);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x07081a // Deep space blue-black
});
/****
* Game Code
****/
// Star Wars lightsaber battle sounds (multiple for variety)
// Fail sound
// Chop sound
// Lumberjack
// Branch
// Tree trunk segment
// --- Asset Initialization (auto by LK engine) ---
// --- Game Variables ---
// Space setting: no clouds, just stars and shooting stars!
var treeSegments = []; // Array of TreeSegment
var treeX = 2048 / 2;
var treeBaseY = 2732 - 350; // Y position of the bottom segment
var trunkWidth = 210;
var trunkHeight = 120;
var visibleSegments = 13; // Number of segments visible on screen
var branchChance = 0.35; // Probability of a branch per segment
var lastBranch = 'none'; // To avoid impossible patterns
var player = null;
var scoreTxt = null;
var timerBar = null;
var timeLeft = 2000; // ms, time before game over
var maxTime = 2000; // ms, resets to this on chop
var timerInterval = null;
var isGameOver = false;
// --- Shooting Star System ---
var snowflakes = []; // Now used for shooting stars
var snowSpawnTimer = 0;
var snowSpawnInterval = 18 + Math.floor(Math.random() * 12); // randomize interval for more natural effect
// --- UI Setup ---
scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
// Place the score text at the very top, centered horizontally
scoreTxt.x = 0;
scoreTxt.y = 0;
LK.gui.top.addChild(scoreTxt);
// Timer bar with improved visuals, placed just below the score text
timerBar = LK.getAsset('timerBar', {
width: 800,
height: 38,
color: 0x00b050,
shape: 'box',
anchorX: 0.5,
anchorY: 0,
x: 0,
y: scoreTxt.y + scoreTxt.height + 10 // just below score text with a small gap
});
timerBar.alpha = 0.93;
timerBar.border = LK.getAsset('timerBar', {
width: 820,
height: 48,
color: 0x222222,
shape: 'box',
anchorX: 0.5,
anchorY: 0,
x: 0,
y: timerBar.y // match timerBar y
});
timerBar.border.alpha = 0.25;
LK.gui.top.addChild(timerBar.border);
LK.gui.top.addChild(timerBar);
// Timer bar hit animation
function timerBarHitAnim() {
tween(timerBar, {
scaleY: 1.25
}, {
duration: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(timerBar, {
scaleY: 1
}, {
duration: 100,
easing: tween.cubicIn
});
}
});
tween(timerBar, {
alpha: 1
}, {
duration: 60,
onFinish: function onFinish() {
tween(timerBar, {
alpha: 0.93
}, {
duration: 100
});
}
});
}
// Timer bar explosion animation
function timerBarExplodeAnim() {
tween(timerBar, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 350,
easing: tween.cubicOut
});
tween(timerBar.border, {
alpha: 0
}, {
duration: 350
});
}
// --- Tree Setup ---
function randomBranch(prevBranch) {
// Avoid two branches on the same side in a row
var r = Math.random();
if (r < branchChance) {
if (prevBranch === 'left') return 'right';
if (prevBranch === 'right') return 'left';
return Math.random() < 0.5 ? 'left' : 'right';
}
return 'none';
}
function createInitialTree() {
var prev = 'none';
for (var i = 0; i < visibleSegments + 2; i++) {
var seg = new TreeSegment();
var branch = randomBranch(prev);
seg.setup(branch);
seg.x = treeX;
seg.y = treeBaseY - i * trunkHeight;
treeSegments.push(seg);
game.addChild(seg);
prev = branch;
}
}
function shiftTree() {
// Remove bottom segment
var bottom = treeSegments.shift();
bottom.destroy();
// Move all segments down
for (var i = 0; i < treeSegments.length; i++) {
tween(treeSegments[i], {
y: treeBaseY - i * trunkHeight
}, {
duration: 80,
easing: tween.cubicOut
});
}
// Add new segment at the top
var prevBranch = treeSegments[treeSegments.length - 1].branch;
var newSeg = new TreeSegment();
var branch = randomBranch(prevBranch);
newSeg.setup(branch);
newSeg.x = treeX;
newSeg.y = treeSegments[treeSegments.length - 1].y - trunkHeight;
treeSegments.push(newSeg);
game.addChild(newSeg);
}
// --- Player Setup ---
player = new Lumberjack();
player.setSide('left');
player.y = treeBaseY + trunkHeight / 2 + 10;
game.addChild(player);
// --- Game State ---
var score = 0;
// --- Timer ---
function resetTimer() {
timeLeft = maxTime;
updateTimerBar();
}
function updateTimerBar() {
var pct = Math.max(0, timeLeft / maxTime);
timerBar.width = 800 * pct;
if (pct > 0.5) {
timerBar.tint = 0x00b050;
} else if (pct > 0.2) {
timerBar.tint = 0xffc300;
} else {
timerBar.tint = 0xff0000;
}
}
// --- Game Over ---
function triggerGameOver() {
if (isGameOver) return;
isGameOver = true;
timerBarExplodeAnim();
LK.effects.flashScreen(0xff0000, 800);
// Play a better wood explosion sound and effect
LK.getSound('fail').play();
// Play Darth Vader shouting Obi Wan (using the fail sound asset as placeholder for the shout)
LK.getSound('fail', {
start: 4.0,
end: 6.0
}).play();
LK.effects.flashObject(treeSegments[0], 0xffe066, 400); // flash the bottom segment yellow for a wood burst
tween(treeSegments[0], {
scaleX: 2.2,
scaleY: 2.2,
alpha: 0
}, {
duration: 420,
easing: tween.cubicOut
});
LK.showGameOver();
}
// --- Chop Action ---
function chop() {
if (isGameOver) return;
// Check for collision with branch on current side at the bottom segment
var bottomSeg = treeSegments[0];
if (bottomSeg.branch === player.side) {
triggerGameOver();
return;
}
// Chop: remove bottom, shift tree, add new at top
shiftTree();
// Animate player
player.chop();
timerBarHitAnim();
// Play sound
LK.getSound('chop').play();
// Score
score += 1;
LK.setScore(score);
scoreTxt.setText(score);
// Reset timer, but make it a bit shorter as score increases
maxTime = Math.max(800, 2000 - score * 10);
resetTimer();
}
// --- Input Handling ---
var touchStartX = null;
var touchStartY = null;
var dragThreshold = 40; // px
function handleDown(x, y, obj) {
touchStartX = x;
touchStartY = y;
}
function handleUp(x, y, obj) {
if (isGameOver) return;
if (touchStartX === null || touchStartY === null) return;
var dx = x - touchStartX;
var dy = y - touchStartY;
// Swipe detection
if (Math.abs(dx) > dragThreshold && Math.abs(dx) > Math.abs(dy)) {
// Horizontal swipe
if (dx > 0 && player.side === 'left') {
player.setSide('right');
} else if (dx < 0 && player.side === 'right') {
player.setSide('left');
}
} else if (Math.abs(dx) < dragThreshold && Math.abs(dy) < dragThreshold) {
// Tap: chop
chop();
}
touchStartX = null;
touchStartY = null;
}
game.down = handleDown;
game.up = handleUp;
// --- Main Update Loop ---
// --- Darth Vader Speech System ---
var vaderSpeechInterval = 4200; // ms between speeches
var vaderSpeechTimer = 0;
var vaderSpeechActive = false;
var vaderSpeechLines = [{
start: 0.0,
end: 2.0
},
// "You don't know the power of the dark side"
{
start: 2.1,
end: 4.0
},
// "I am your father"
{
start: 4.1,
end: 6.0
},
// "Obi-Wan has taught you well"
{
start: 6.1,
end: 8.0
},
// "Impressive. Most impressive."
{
start: 8.1,
end: 10.0
} // "You are unwise to lower your defenses"
];
// Use the 'fail' sound asset as placeholder for Vader speech (replace with real asset id when available)
var vaderSpeechAssetId = 'fail';
// --- Darth Vader Random Sound System ---
var vaderRandomSoundInterval = 8000; // 8 seconds in ms
var vaderRandomSoundTimer = 0;
var vaderRandomSoundList = ['darthvadersound1', 'darthvadersound2', 'darthvadersound3', 'darthvadersound5'];
game.update = function () {
// --- Shooting star update ---
snowSpawnTimer++;
if (snowSpawnTimer >= snowSpawnInterval) {
snowSpawnTimer = 0;
// Only a few shooting stars at a time
if (snowflakes.length < 7) {
var star = new ShootingStar();
snowflakes.push(star);
game.addChild(star);
}
// Randomize next interval for more natural effect
snowSpawnInterval = 18 + Math.floor(Math.random() * 12);
}
// Update and remove shooting stars
for (var i = snowflakes.length - 1; i >= 0; i--) {
var star = snowflakes[i];
if (star.update) star.update();
if (star.toRemove) {
star.destroy();
snowflakes.splice(i, 1);
}
}
// Darth Vader speech logic
if (!isGameOver) {
vaderSpeechTimer += 1000 / 60;
if (vaderSpeechTimer >= vaderSpeechInterval && !vaderSpeechActive) {
vaderSpeechTimer = 0;
vaderSpeechActive = true;
// Pick a random speech line
var idx = Math.floor(Math.random() * vaderSpeechLines.length);
var line = vaderSpeechLines[idx];
// Play the speech line (using fail asset as placeholder)
LK.getSound(vaderSpeechAssetId, {
start: line.start,
end: line.end
}).play();
// Prevent overlap: allow new speech after a short delay
LK.setTimeout(function () {
vaderSpeechActive = false;
}, (line.end - line.start) * 1000 + 400);
}
// --- Darth Vader random sound logic ---
vaderRandomSoundTimer += 1000 / 60;
if (vaderRandomSoundTimer >= vaderRandomSoundInterval) {
vaderRandomSoundTimer = 0;
// 88% chance to play Obi Wan (fail sound), 12% chance to play a random Vader sound
if (Math.random() < 0.88) {
// Play Obi Wan (fail sound) from 4.0s to 6.0s
LK.getSound('fail', {
start: 4.0,
end: 6.0
}).play();
} else {
// Pick a random sound from the list
var soundIdx = Math.floor(Math.random() * vaderRandomSoundList.length);
var soundId = vaderRandomSoundList[soundIdx];
LK.getSound(soundId).play();
}
}
}
if (isGameOver) return;
// Timer
timeLeft -= 1000 / 60;
updateTimerBar();
if (timeLeft <= 0) {
triggerGameOver();
}
};
// --- Game Initialization ---
function resetGame() {
// Remove old tree
for (var i = 0; i < treeSegments.length; i++) {
treeSegments[i].destroy();
}
treeSegments = [];
// Remove old snowflakes
for (var i = 0; i < snowflakes.length; i++) {
snowflakes[i].destroy();
}
snowflakes = [];
// Reset variables
score = 0;
LK.setScore(0);
scoreTxt.setText('0');
isGameOver = false;
maxTime = 2000;
timerBar.scaleX = 1;
timerBar.scaleY = 1;
timerBar.alpha = 0.93;
if (timerBar.border) timerBar.border.alpha = 0.25;
resetTimer();
// Recreate tree
createInitialTree();
// Reset player
player.setSide('left');
player.y = treeBaseY + trunkHeight / 2 + 10;
}
// Reset Vader speech system
vaderSpeechTimer = 0;
vaderSpeechActive = false;
// Reset random Vader sound timer
vaderRandomSoundTimer = 0;
// No league system or leaderboard/login UI. Game starts immediately.
resetGame();
scoreTxt.visible = true;
timerBar.visible = true;
if (timerBar.border) timerBar.border.visible = true;