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