/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Building = Container.expand(function () {
var self = Container.call(this);
var buildingGraphics = self.attachAsset('building', {
anchorX: 0.5,
anchorY: 0
});
// Randomize building properties
var shade = Math.floor(Math.random() * 40) + 40;
buildingGraphics.tint = shade << 16 | shade << 8 | shade;
buildingGraphics.height = Math.random() * 600 + 400;
buildingGraphics.width = Math.random() * 100 + 100;
// Add windows to buildings for a city look
var windowCount = Math.floor(buildingGraphics.height / 80);
var windowsPerRow = Math.floor(buildingGraphics.width / 40);
for (var i = 0; i < windowCount; i++) {
for (var j = 0; j < windowsPerRow; j++) {
var windowLight = self.addChild(new Container());
var windowAsset = windowLight.attachAsset('building', {
anchorX: 0.5,
anchorY: 0.5,
width: 20,
height: 30,
tint: 0xFFFF99
});
windowLight.x = j * 40 - buildingGraphics.width / 2 + 25;
windowLight.y = i * 80 + 50;
// Randomly light windows
windowLight.alpha = Math.random() > 0.3 ? 0.8 : 0;
}
}
self.speed = 10; // Default speed
self.update = function () {
self.y += self.speed;
// Reset building when it goes off screen
if (self.y > 2732 + buildingGraphics.height) {
self.y = -buildingGraphics.height;
// Determine road boundaries to keep buildings off the road
var road = null;
for (var i = 0; i < game.children.length; i++) {
if (game.children[i].asset && game.children[i].asset.id === 'road') {
road = game.children[i];
break;
}
}
// Place building only on sidewalks
var roadLeftEdge = road ? (2048 - road.width) / 2 : 424;
var roadRightEdge = road ? 2048 - (2048 - road.width) / 2 : 1624;
// Determine which side to place the building on based on its current side
if (self.x < roadLeftEdge) {
// Keep on left side
self.x = Math.random() * (roadLeftEdge - buildingGraphics.width / 2);
} else {
// Keep on right side
self.x = roadRightEdge + Math.random() * (roadLeftEdge - buildingGraphics.width / 2);
}
var shade = Math.floor(Math.random() * 40) + 40;
buildingGraphics.tint = shade << 16 | shade << 8 | shade;
buildingGraphics.height = Math.random() * 600 + 400;
buildingGraphics.width = Math.random() * 100 + 100;
// Recreate windows when building resets
self.removeChildren(1); // Keep only the main building graphic
var windowCount = Math.floor(buildingGraphics.height / 80);
var windowsPerRow = Math.floor(buildingGraphics.width / 40);
for (var i = 0; i < windowCount; i++) {
for (var j = 0; j < windowsPerRow; j++) {
var windowLight = self.addChild(new Container());
var windowAsset = windowLight.attachAsset('building', {
anchorX: 0.5,
anchorY: 0.5,
width: 20,
height: 30,
tint: 0xFFFF99
});
windowLight.x = j * 40 - buildingGraphics.width / 2 + 25;
windowLight.y = i * 80 + 50;
// Randomly light windows
windowLight.alpha = Math.random() > 0.3 ? 0.8 : 0;
}
}
}
};
return self;
});
var Car = Container.expand(function () {
var self = Container.call(this);
// Create car body - using just the car asset without additional square
var carGraphics = self.attachAsset('car', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x3366CC // Blue car color
});
// Add headlights
var leftHeadlight = self.addChild(new Container());
var leftHeadlightAsset = leftHeadlight.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 40,
tint: 0xFFFFAA
});
leftHeadlight.x = -60;
leftHeadlight.y = -140;
var rightHeadlight = self.addChild(new Container());
var rightHeadlightAsset = rightHeadlight.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 40,
tint: 0xFFFFAA
});
rightHeadlight.x = 60;
rightHeadlight.y = -140;
self.jumping = false;
self.jumpHeight = 100;
self.originalY = 0;
self.lastY = 0; // Track last Y position for intersection detection
self.jump = function () {
if (self.jumping) {
return;
}
self.jumping = true;
self.originalY = self.y;
// Play jump sound
LK.getSound('jump').play();
// Jump animation using tween
tween(self, {
y: self.y - self.jumpHeight
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Fall back down
tween(self, {
y: self.originalY
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
self.jumping = false;
}
});
}
});
};
self.down = function () {
self.jump();
};
self.update = function () {
self.lastY = self.y;
// Create headlight glow effect
if (LK.ticks % 5 === 0) {
leftHeadlightAsset.alpha = 0.7 + Math.random() * 0.3;
rightHeadlightAsset.alpha = 0.7 + Math.random() * 0.3;
}
};
return self;
});
var Line = Container.expand(function () {
var self = Container.call(this);
// Create road line marking
var lineGraphics = self.attachAsset('line', {
anchorX: 0.5,
anchorY: 0.5
});
// Make line vertical
lineGraphics.rotation = Math.PI / 2; // Rotate 90 degrees
// Road lines have fixed width to better match car size
lineGraphics.width = 400; // Line length (now vertical)
lineGraphics.height = 60; // Line thickness (more visible)
self.lastY = 0; // Track last Y position for intersection detection
self.speed = 15; // Default speed
self.active = true; // Whether the line is active for scoring
self.scored = false; // Whether this line has been scored already
// Add visual effect to make lines more prominent
var glowEffect = self.addChild(new Container());
var glowAsset = glowEffect.attachAsset('line', {
anchorX: 0.5,
anchorY: 0.5,
width: lineGraphics.width + 20,
height: lineGraphics.height + 20,
tint: 0xFFFF00,
alpha: 0.3
});
glowAsset.rotation = Math.PI / 2;
self.update = function () {
self.lastY = self.y; // Store last position for collision detection
self.y += self.speed;
// Pulse glow effect
if (LK.ticks % 10 === 0) {
glowAsset.alpha = 0.2 + Math.random() * 0.2;
}
// Remove when off screen
if (self.y > 2732 + lineGraphics.width / 2) {
self.active = false;
}
};
return self;
});
var ScoreEffect = Container.expand(function () {
var self = Container.call(this);
var effectGraphics = self.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
self.show = function (x, y, isPositive) {
self.x = x;
self.y = y;
if (!isPositive) {
effectGraphics.tint = 0xFF0000;
// Play penalty sound
LK.getSound('penalty').play();
} else {
effectGraphics.tint = 0x00FF00;
// Play point sound
LK.getSound('point').play();
}
// Animation for the effect
tween(self, {
alpha: 0,
y: y - 100
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
self.alpha = 1;
self.visible = false;
}
});
self.visible = true;
};
return self;
});
var Store = Container.expand(function () {
var self = Container.call(this);
// Create store building
var storeGraphics = self.attachAsset('building', {
anchorX: 0.5,
anchorY: 0,
width: 200,
height: 250,
tint: 0xFAE5D3 // Light beige for store
});
// Add store sign
var storeSign = self.addChild(new Container());
var storeSignAsset = storeSign.attachAsset('building', {
anchorX: 0.5,
anchorY: 0.5,
width: 180,
height: 60,
tint: 0x3498DB // Blue for store sign
});
storeSign.y = 40;
// Add store window
var storeWindow = self.addChild(new Container());
var storeWindowAsset = storeWindow.attachAsset('building', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 100,
tint: 0xD6EAF8 // Light blue for window
});
storeWindow.y = 120;
// Add store door
var storeDoor = self.addChild(new Container());
var storeDoorAsset = storeDoor.attachAsset('building', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 100,
tint: 0x784212 // Brown for door
});
storeDoor.y = 180;
self.speed = 10; // Default speed
self.update = function () {
self.y += self.speed;
// Reset store when it goes off screen
if (self.y > 2732 + storeGraphics.height) {
self.y = -storeGraphics.height;
// Randomize store appearance
var storeTypes = [{
building: 0xFAE5D3,
sign: 0x3498DB,
window: 0xD6EAF8
},
// Blue store
{
building: 0xFCF3CF,
sign: 0xE74C3C,
window: 0xFADBD8
},
// Red store
{
building: 0xD5F5E3,
sign: 0x8E44AD,
window: 0xEBDEF0
},
// Purple store
{
building: 0xEAEDED,
sign: 0xF39C12,
window: 0xFAE5D3
} // Orange store
];
var type = storeTypes[Math.floor(Math.random() * storeTypes.length)];
storeGraphics.tint = type.building;
storeSignAsset.tint = type.sign;
storeWindowAsset.tint = type.window;
// Vary store size slightly
var sizeVar = 0.8 + Math.random() * 0.4;
storeGraphics.width = 200 * sizeVar;
storeGraphics.height = 250 * sizeVar;
storeSign.scale.set(sizeVar);
storeWindow.scale.set(sizeVar);
storeDoor.scale.set(sizeVar);
// Adjust positions based on new size
storeSign.y = 40 * sizeVar;
storeWindow.y = 120 * sizeVar;
storeDoor.y = 180 * sizeVar;
}
};
return self;
});
var StreetLight = Container.expand(function () {
var self = Container.call(this);
// Create pole
var pole = self.addChild(new Container());
var poleAsset = pole.attachAsset('building', {
anchorX: 0.5,
anchorY: 1.0,
width: 20,
height: 200,
tint: 0x555555 // Dark gray for pole
});
// Create light
var light = self.addChild(new Container());
var lightAsset = light.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 40,
tint: 0xFFFF99 // Yellow light
});
light.y = -200;
// Light arm
var arm = self.addChild(new Container());
var armAsset = arm.attachAsset('building', {
anchorX: 0,
anchorY: 0.5,
width: 40,
height: 10,
tint: 0x555555 // Dark gray for arm
});
arm.y = -190;
self.speed = 10; // Default speed
self.isLit = Math.random() > 0.2; // Most lights are on
self.update = function () {
self.y += self.speed;
// Make light flicker occasionally
if (self.isLit && LK.ticks % 20 === 0 && Math.random() > 0.7) {
lightAsset.alpha = 0.6 + Math.random() * 0.4;
}
// Reset light when it goes off screen
if (self.y > 2732 + 200) {
self.y = -200;
self.isLit = Math.random() > 0.2;
lightAsset.alpha = self.isLit ? 1 : 0.2;
}
};
return self;
});
var TapEffect = Container.expand(function () {
var self = Container.call(this);
var effectGraphics = self.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 300,
alpha: 0.6
});
self.show = function (x, y, isPositive) {
self.x = x;
self.y = y;
// Set color based on whether it's a positive or negative tap
if (isPositive) {
effectGraphics.tint = 0x00FF00; // Green for good taps
// Play point sound with lower volume to avoid overwhelming
LK.getSound('point').play({
volume: 0.3
});
} else {
effectGraphics.tint = 0xFF0000; // Red for bad taps
// Play penalty sound with lower volume to avoid overwhelming
LK.getSound('penalty').play({
volume: 0.3
});
}
// Reset properties before animation
self.alpha = 1;
effectGraphics.alpha = 0.6;
effectGraphics.scale.x = 0.5;
effectGraphics.scale.y = 0.5;
self.visible = true;
// Animate the effect - expand and fade out for a nice ripple effect
tween(effectGraphics, {
alpha: 0,
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
self.visible = false;
effectGraphics.scale.x = 0.5;
effectGraphics.scale.y = 0.5;
}
});
};
return self;
});
var Tree = Container.expand(function () {
var self = Container.call(this);
// Create tree trunk
var trunk = self.addChild(new Container());
var trunkAsset = trunk.attachAsset('building', {
anchorX: 0.5,
anchorY: 1.0,
width: 40,
height: 120,
tint: 0x8B4513 // Brown color for trunk
});
// Create tree foliage (multiple layers for depth)
var foliage1 = self.addChild(new Container());
var foliageAsset1 = foliage1.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.8,
width: 150,
height: 150,
tint: 0x228B22 // Forest green
});
foliage1.y = -80;
var foliage2 = self.addChild(new Container());
var foliageAsset2 = foliage2.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.7,
width: 120,
height: 120,
tint: 0x32CD32 // Lime green for highlight
});
foliage2.y = -100;
self.speed = 10; // Default speed
self.update = function () {
self.y += self.speed;
// Reset tree when it goes off screen
if (self.y > 2732 + 150) {
self.y = -150;
// Randomize tree appearance when resetting
trunkAsset.height = 80 + Math.random() * 80;
trunkAsset.width = 30 + Math.random() * 20;
var scale = 0.8 + Math.random() * 0.5;
foliage1.scale.set(scale);
foliage2.scale.set(scale * 0.9);
// Slightly vary green shades
var greenVariation = Math.floor(Math.random() * 40);
foliageAsset1.tint = 34 + greenVariation << 16 | 139 + greenVariation << 8 | 34;
foliageAsset2.tint = 50 + greenVariation << 16 | 205 + greenVariation << 8 | 50;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game variables
var car;
var lines = [];
var buildings = [];
var trees = [];
var stores = [];
var streetLights = [];
var score = 0;
var scoreText;
var speedMultiplier = 1;
var lineSpawnRate = 60; // Frames between line spawns
var difficulty = 1;
var isGameActive = true;
var scoreEffects = [];
var tapEffects = [];
var effectIndex = 0;
var tapEffectIndex = 0;
var lastJumpScore = 0;
// Create a sky background
game.setBackgroundColor(0x87CEEB); // Light blue sky
// Initialize the road
var road = game.addChild(LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
width: 1200,
// Make the road narrower than the full width
tint: 0x444444 // Darker gray for asphalt
}));
// Add road edges/sidewalks
var leftSidewalk = game.addChild(LK.getAsset('building', {
anchorX: 0,
anchorY: 0.5,
width: (2048 - road.width) / 2,
height: 2732,
x: 0,
y: 2732 / 2,
tint: 0x999999 // Light gray for sidewalk
}));
var rightSidewalk = game.addChild(LK.getAsset('building', {
anchorX: 1,
anchorY: 0.5,
width: (2048 - road.width) / 2,
height: 2732,
x: 2048,
y: 2732 / 2,
tint: 0x999999 // Light gray for sidewalk
}));
// Initialize score display
scoreText = new Text2('SCORE: 0', {
size: 80,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 5
});
scoreText.anchor.set(0.5, 0);
scoreText.y = 50; // Move down slightly from top
LK.gui.top.addChild(scoreText);
// Add game instructions
var instructionsText = new Text2('Tap when no lines\nAvoid tapping on lines\nScore 1000 to win', {
size: 80,
fill: 0xFFFFFF,
stroke: 0x800080,
strokeThickness: 10,
fontWeight: 'bold',
align: 'center'
});
instructionsText.anchor.set(0.5, 0);
instructionsText.y = 150;
LK.gui.top.addChild(instructionsText);
// Create buildings for city skyline on both sides of the road (not on the road)
for (var i = 0; i < 20; i++) {
var building = new Building();
// Position buildings only on the sides, ensuring they stay off the road
var roadLeftEdge = (2048 - road.width) / 2;
var roadRightEdge = 2048 - roadLeftEdge;
if (i % 2 === 0) {
// Left side only - ensure buildings stay completely on sidewalk
var buildingWidth = building.children[0].width;
building.x = Math.random() * (roadLeftEdge - buildingWidth / 2); // Left side only, accounting for building width
} else {
// Right side only - ensure buildings stay completely on sidewalk
var buildingWidth = building.children[0].width;
building.x = roadRightEdge + Math.random() * (roadLeftEdge - buildingWidth / 2); // Right side only
}
building.y = Math.random() * 2732;
buildings.push(building);
game.addChild(building);
}
// Add trees along the road
var trees = [];
for (var i = 0; i < 15; i++) {
var tree = new Tree();
// Position trees on the sidewalks
if (i % 2 === 0) {
// Left side trees - closer to road
tree.x = (2048 - road.width) / 2 - 80 - Math.random() * 60;
} else {
// Right side trees - closer to road
tree.x = 2048 - (2048 - road.width) / 2 + 80 + Math.random() * 60;
}
// Distribute trees along the road
tree.y = -150 + i * 350 + Math.random() * 200;
trees.push(tree);
game.addChild(tree);
}
// Add stores on the sides
var stores = [];
for (var i = 0; i < 8; i++) {
var store = new Store();
// Position stores on the sidewalks, further from the road than trees
if (i % 2 === 0) {
// Left side stores
store.x = (2048 - road.width) / 4;
} else {
// Right side stores
store.x = 2048 - (2048 - road.width) / 4;
}
// Distribute stores along the road with some spacing
store.y = -300 + i * 650 + Math.random() * 300;
stores.push(store);
game.addChild(store);
}
// Add street lights
var streetLights = [];
for (var i = 0; i < 12; i++) {
var light = new StreetLight();
// Position lights on the edge of the road
if (i % 2 === 0) {
// Left side lights
light.x = (2048 - road.width) / 2 - 20;
} else {
// Right side lights
light.x = 2048 - (2048 - road.width) / 2 + 20;
}
// Distribute lights evenly
light.y = -200 + i * 400 + i % 2 * 200;
streetLights.push(light);
game.addChild(light);
}
// Create car
car = new Car();
car.x = 2048 / 2;
car.y = 2732 - 400;
game.addChild(car);
// Bring car to front to make sure it appears on top of lines
game.setChildIndex(car, game.children.length - 1);
// Create score effects pool
for (var i = 0; i < 5; i++) {
var effect = new ScoreEffect();
effect.visible = false;
scoreEffects.push(effect);
game.addChild(effect);
}
// Create tap effects pool - increased size to handle multiple effects
for (var i = 0; i < 10; i++) {
var tapEffect = new TapEffect();
tapEffect.visible = false;
tapEffects.push(tapEffect);
game.addChild(tapEffect);
}
// Game click handler
game.down = function (x, y, obj) {
if (!isGameActive) {
return;
}
// Check if car is already jumping
if (!car.jumping) {
car.jump();
// Check if car is over a road line
var overLine = false;
for (var i = 0; i < lines.length; i++) {
// Check if the car intersects with any vertical road line
if (lines[i].active && car.intersects(lines[i])) {
overLine = true;
break;
}
}
// Score or penalty based on jump
if (overLine) {
// Penalty for jumping over a line
score = Math.max(0, score - 10);
// Show penalty effect
var effect = scoreEffects[effectIndex];
effectIndex = (effectIndex + 1) % scoreEffects.length;
effect.show(car.x, car.y - 50, false);
// Show tap feedback effect (red for penalty)
var tapEffect = tapEffects[tapEffectIndex];
tapEffectIndex = (tapEffectIndex + 1) % tapEffects.length;
tapEffect.show(x, y, false);
// Add multiple red effects for stronger visual feedback
for (var j = 0; j < 2; j++) {
var extraEffect = tapEffects[tapEffectIndex];
tapEffectIndex = (tapEffectIndex + 1) % tapEffects.length;
extraEffect.show(x + (Math.random() * 100 - 50), y + (Math.random() * 100 - 50), false);
}
// Flash screen red for bad tap
LK.effects.flashScreen(0xff0000, 300);
} else {
// Points for jumping on empty road
var pointsGained = 20 * difficulty;
score += pointsGained;
lastJumpScore = pointsGained;
// Show score effect
var effect = scoreEffects[effectIndex];
effectIndex = (effectIndex + 1) % scoreEffects.length;
effect.show(car.x, car.y - 50, true);
// Show tap feedback effect (green for points)
var tapEffect = tapEffects[tapEffectIndex];
tapEffectIndex = (tapEffectIndex + 1) % tapEffects.length;
tapEffect.show(x, y, true);
// Add multiple green effects for stronger visual feedback
for (var j = 0; j < 2; j++) {
var extraEffect = tapEffects[tapEffectIndex];
tapEffectIndex = (tapEffectIndex + 1) % tapEffects.length;
extraEffect.show(x + (Math.random() * 100 - 50), y + (Math.random() * 100 - 50), true);
}
}
// Update score display
scoreText.setText(Math.round(score));
LK.setScore(Math.round(score));
}
};
// Start background music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 0.4,
duration: 1000
}
});
// Game update function
game.update = function () {
if (!isGameActive) {
return;
}
// Update car
car.update();
// Update all lines
for (var i = lines.length - 1; i >= 0; i--) {
lines[i].update();
// We don't penalize for just passing over a line anymore
// Only interactions happen when player taps
if (!lines[i].scored && lines[i].lastY < car.y && lines[i].y >= car.y) {
lines[i].scored = true; // Mark as scored
}
// Remove inactive lines
if (!lines[i].active) {
lines[i].destroy();
lines.splice(i, 1);
}
}
// Update all buildings
for (var i = 0; i < buildings.length; i++) {
buildings[i].speed = 5 * speedMultiplier;
buildings[i].update();
}
// Update all trees
for (var i = 0; i < trees.length; i++) {
trees[i].speed = 7 * speedMultiplier;
trees[i].update();
}
// Update all stores
for (var i = 0; i < stores.length; i++) {
stores[i].speed = 5 * speedMultiplier;
stores[i].update();
}
// Update all street lights
for (var i = 0; i < streetLights.length; i++) {
streetLights[i].speed = 7 * speedMultiplier;
streetLights[i].update();
}
// Create new lines at intervals - always come from center of the screen
if (LK.ticks % Math.floor(lineSpawnRate / speedMultiplier) === 0) {
var line = new Line();
// Position line at the center of the screen
line.x = 2048 / 2; // Center X coordinate
line.y = -50;
line.speed = 15 * speedMultiplier;
lines.push(line);
game.addChild(line);
// Ensure car is always rendered on top of lines
game.setChildIndex(car, game.children.length - 1);
}
// Increase difficulty over time
if (LK.ticks % 600 === 0) {
// Every 10 seconds
difficulty += 0.1;
// Speed up game after 500 points, but not too much, and slow down a bit after 800
if (score > 800) {
speedMultiplier = 1 + (difficulty - 1) * 1.0; // Reduced speed multiplier after 800 points
lineSpawnRate = Math.max(15, 40 - difficulty * 3); // Reduced line spawn rate
} else if (score > 500) {
speedMultiplier = 1 + (difficulty - 1) * 1.1; // Moderate speed increase after 500 points
lineSpawnRate = Math.max(12, 40 - difficulty * 4); // Moderate increase in line spawn rate
} else {
speedMultiplier = 1 + (difficulty - 1) * 0.5;
lineSpawnRate = Math.max(15, 60 - difficulty * 5);
}
}
// Check for game over condition
if (score < 0) {
isGameActive = false;
LK.showGameOver();
}
// Check for win condition (1000 points to win)
if (score >= 1000) {
isGameActive = false;
LK.showYouWin();
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Building = Container.expand(function () {
var self = Container.call(this);
var buildingGraphics = self.attachAsset('building', {
anchorX: 0.5,
anchorY: 0
});
// Randomize building properties
var shade = Math.floor(Math.random() * 40) + 40;
buildingGraphics.tint = shade << 16 | shade << 8 | shade;
buildingGraphics.height = Math.random() * 600 + 400;
buildingGraphics.width = Math.random() * 100 + 100;
// Add windows to buildings for a city look
var windowCount = Math.floor(buildingGraphics.height / 80);
var windowsPerRow = Math.floor(buildingGraphics.width / 40);
for (var i = 0; i < windowCount; i++) {
for (var j = 0; j < windowsPerRow; j++) {
var windowLight = self.addChild(new Container());
var windowAsset = windowLight.attachAsset('building', {
anchorX: 0.5,
anchorY: 0.5,
width: 20,
height: 30,
tint: 0xFFFF99
});
windowLight.x = j * 40 - buildingGraphics.width / 2 + 25;
windowLight.y = i * 80 + 50;
// Randomly light windows
windowLight.alpha = Math.random() > 0.3 ? 0.8 : 0;
}
}
self.speed = 10; // Default speed
self.update = function () {
self.y += self.speed;
// Reset building when it goes off screen
if (self.y > 2732 + buildingGraphics.height) {
self.y = -buildingGraphics.height;
// Determine road boundaries to keep buildings off the road
var road = null;
for (var i = 0; i < game.children.length; i++) {
if (game.children[i].asset && game.children[i].asset.id === 'road') {
road = game.children[i];
break;
}
}
// Place building only on sidewalks
var roadLeftEdge = road ? (2048 - road.width) / 2 : 424;
var roadRightEdge = road ? 2048 - (2048 - road.width) / 2 : 1624;
// Determine which side to place the building on based on its current side
if (self.x < roadLeftEdge) {
// Keep on left side
self.x = Math.random() * (roadLeftEdge - buildingGraphics.width / 2);
} else {
// Keep on right side
self.x = roadRightEdge + Math.random() * (roadLeftEdge - buildingGraphics.width / 2);
}
var shade = Math.floor(Math.random() * 40) + 40;
buildingGraphics.tint = shade << 16 | shade << 8 | shade;
buildingGraphics.height = Math.random() * 600 + 400;
buildingGraphics.width = Math.random() * 100 + 100;
// Recreate windows when building resets
self.removeChildren(1); // Keep only the main building graphic
var windowCount = Math.floor(buildingGraphics.height / 80);
var windowsPerRow = Math.floor(buildingGraphics.width / 40);
for (var i = 0; i < windowCount; i++) {
for (var j = 0; j < windowsPerRow; j++) {
var windowLight = self.addChild(new Container());
var windowAsset = windowLight.attachAsset('building', {
anchorX: 0.5,
anchorY: 0.5,
width: 20,
height: 30,
tint: 0xFFFF99
});
windowLight.x = j * 40 - buildingGraphics.width / 2 + 25;
windowLight.y = i * 80 + 50;
// Randomly light windows
windowLight.alpha = Math.random() > 0.3 ? 0.8 : 0;
}
}
}
};
return self;
});
var Car = Container.expand(function () {
var self = Container.call(this);
// Create car body - using just the car asset without additional square
var carGraphics = self.attachAsset('car', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x3366CC // Blue car color
});
// Add headlights
var leftHeadlight = self.addChild(new Container());
var leftHeadlightAsset = leftHeadlight.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 40,
tint: 0xFFFFAA
});
leftHeadlight.x = -60;
leftHeadlight.y = -140;
var rightHeadlight = self.addChild(new Container());
var rightHeadlightAsset = rightHeadlight.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 40,
tint: 0xFFFFAA
});
rightHeadlight.x = 60;
rightHeadlight.y = -140;
self.jumping = false;
self.jumpHeight = 100;
self.originalY = 0;
self.lastY = 0; // Track last Y position for intersection detection
self.jump = function () {
if (self.jumping) {
return;
}
self.jumping = true;
self.originalY = self.y;
// Play jump sound
LK.getSound('jump').play();
// Jump animation using tween
tween(self, {
y: self.y - self.jumpHeight
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Fall back down
tween(self, {
y: self.originalY
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
self.jumping = false;
}
});
}
});
};
self.down = function () {
self.jump();
};
self.update = function () {
self.lastY = self.y;
// Create headlight glow effect
if (LK.ticks % 5 === 0) {
leftHeadlightAsset.alpha = 0.7 + Math.random() * 0.3;
rightHeadlightAsset.alpha = 0.7 + Math.random() * 0.3;
}
};
return self;
});
var Line = Container.expand(function () {
var self = Container.call(this);
// Create road line marking
var lineGraphics = self.attachAsset('line', {
anchorX: 0.5,
anchorY: 0.5
});
// Make line vertical
lineGraphics.rotation = Math.PI / 2; // Rotate 90 degrees
// Road lines have fixed width to better match car size
lineGraphics.width = 400; // Line length (now vertical)
lineGraphics.height = 60; // Line thickness (more visible)
self.lastY = 0; // Track last Y position for intersection detection
self.speed = 15; // Default speed
self.active = true; // Whether the line is active for scoring
self.scored = false; // Whether this line has been scored already
// Add visual effect to make lines more prominent
var glowEffect = self.addChild(new Container());
var glowAsset = glowEffect.attachAsset('line', {
anchorX: 0.5,
anchorY: 0.5,
width: lineGraphics.width + 20,
height: lineGraphics.height + 20,
tint: 0xFFFF00,
alpha: 0.3
});
glowAsset.rotation = Math.PI / 2;
self.update = function () {
self.lastY = self.y; // Store last position for collision detection
self.y += self.speed;
// Pulse glow effect
if (LK.ticks % 10 === 0) {
glowAsset.alpha = 0.2 + Math.random() * 0.2;
}
// Remove when off screen
if (self.y > 2732 + lineGraphics.width / 2) {
self.active = false;
}
};
return self;
});
var ScoreEffect = Container.expand(function () {
var self = Container.call(this);
var effectGraphics = self.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
self.show = function (x, y, isPositive) {
self.x = x;
self.y = y;
if (!isPositive) {
effectGraphics.tint = 0xFF0000;
// Play penalty sound
LK.getSound('penalty').play();
} else {
effectGraphics.tint = 0x00FF00;
// Play point sound
LK.getSound('point').play();
}
// Animation for the effect
tween(self, {
alpha: 0,
y: y - 100
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
self.alpha = 1;
self.visible = false;
}
});
self.visible = true;
};
return self;
});
var Store = Container.expand(function () {
var self = Container.call(this);
// Create store building
var storeGraphics = self.attachAsset('building', {
anchorX: 0.5,
anchorY: 0,
width: 200,
height: 250,
tint: 0xFAE5D3 // Light beige for store
});
// Add store sign
var storeSign = self.addChild(new Container());
var storeSignAsset = storeSign.attachAsset('building', {
anchorX: 0.5,
anchorY: 0.5,
width: 180,
height: 60,
tint: 0x3498DB // Blue for store sign
});
storeSign.y = 40;
// Add store window
var storeWindow = self.addChild(new Container());
var storeWindowAsset = storeWindow.attachAsset('building', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 100,
tint: 0xD6EAF8 // Light blue for window
});
storeWindow.y = 120;
// Add store door
var storeDoor = self.addChild(new Container());
var storeDoorAsset = storeDoor.attachAsset('building', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 100,
tint: 0x784212 // Brown for door
});
storeDoor.y = 180;
self.speed = 10; // Default speed
self.update = function () {
self.y += self.speed;
// Reset store when it goes off screen
if (self.y > 2732 + storeGraphics.height) {
self.y = -storeGraphics.height;
// Randomize store appearance
var storeTypes = [{
building: 0xFAE5D3,
sign: 0x3498DB,
window: 0xD6EAF8
},
// Blue store
{
building: 0xFCF3CF,
sign: 0xE74C3C,
window: 0xFADBD8
},
// Red store
{
building: 0xD5F5E3,
sign: 0x8E44AD,
window: 0xEBDEF0
},
// Purple store
{
building: 0xEAEDED,
sign: 0xF39C12,
window: 0xFAE5D3
} // Orange store
];
var type = storeTypes[Math.floor(Math.random() * storeTypes.length)];
storeGraphics.tint = type.building;
storeSignAsset.tint = type.sign;
storeWindowAsset.tint = type.window;
// Vary store size slightly
var sizeVar = 0.8 + Math.random() * 0.4;
storeGraphics.width = 200 * sizeVar;
storeGraphics.height = 250 * sizeVar;
storeSign.scale.set(sizeVar);
storeWindow.scale.set(sizeVar);
storeDoor.scale.set(sizeVar);
// Adjust positions based on new size
storeSign.y = 40 * sizeVar;
storeWindow.y = 120 * sizeVar;
storeDoor.y = 180 * sizeVar;
}
};
return self;
});
var StreetLight = Container.expand(function () {
var self = Container.call(this);
// Create pole
var pole = self.addChild(new Container());
var poleAsset = pole.attachAsset('building', {
anchorX: 0.5,
anchorY: 1.0,
width: 20,
height: 200,
tint: 0x555555 // Dark gray for pole
});
// Create light
var light = self.addChild(new Container());
var lightAsset = light.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 40,
tint: 0xFFFF99 // Yellow light
});
light.y = -200;
// Light arm
var arm = self.addChild(new Container());
var armAsset = arm.attachAsset('building', {
anchorX: 0,
anchorY: 0.5,
width: 40,
height: 10,
tint: 0x555555 // Dark gray for arm
});
arm.y = -190;
self.speed = 10; // Default speed
self.isLit = Math.random() > 0.2; // Most lights are on
self.update = function () {
self.y += self.speed;
// Make light flicker occasionally
if (self.isLit && LK.ticks % 20 === 0 && Math.random() > 0.7) {
lightAsset.alpha = 0.6 + Math.random() * 0.4;
}
// Reset light when it goes off screen
if (self.y > 2732 + 200) {
self.y = -200;
self.isLit = Math.random() > 0.2;
lightAsset.alpha = self.isLit ? 1 : 0.2;
}
};
return self;
});
var TapEffect = Container.expand(function () {
var self = Container.call(this);
var effectGraphics = self.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 300,
alpha: 0.6
});
self.show = function (x, y, isPositive) {
self.x = x;
self.y = y;
// Set color based on whether it's a positive or negative tap
if (isPositive) {
effectGraphics.tint = 0x00FF00; // Green for good taps
// Play point sound with lower volume to avoid overwhelming
LK.getSound('point').play({
volume: 0.3
});
} else {
effectGraphics.tint = 0xFF0000; // Red for bad taps
// Play penalty sound with lower volume to avoid overwhelming
LK.getSound('penalty').play({
volume: 0.3
});
}
// Reset properties before animation
self.alpha = 1;
effectGraphics.alpha = 0.6;
effectGraphics.scale.x = 0.5;
effectGraphics.scale.y = 0.5;
self.visible = true;
// Animate the effect - expand and fade out for a nice ripple effect
tween(effectGraphics, {
alpha: 0,
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
self.visible = false;
effectGraphics.scale.x = 0.5;
effectGraphics.scale.y = 0.5;
}
});
};
return self;
});
var Tree = Container.expand(function () {
var self = Container.call(this);
// Create tree trunk
var trunk = self.addChild(new Container());
var trunkAsset = trunk.attachAsset('building', {
anchorX: 0.5,
anchorY: 1.0,
width: 40,
height: 120,
tint: 0x8B4513 // Brown color for trunk
});
// Create tree foliage (multiple layers for depth)
var foliage1 = self.addChild(new Container());
var foliageAsset1 = foliage1.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.8,
width: 150,
height: 150,
tint: 0x228B22 // Forest green
});
foliage1.y = -80;
var foliage2 = self.addChild(new Container());
var foliageAsset2 = foliage2.attachAsset('scoreEffect', {
anchorX: 0.5,
anchorY: 0.7,
width: 120,
height: 120,
tint: 0x32CD32 // Lime green for highlight
});
foliage2.y = -100;
self.speed = 10; // Default speed
self.update = function () {
self.y += self.speed;
// Reset tree when it goes off screen
if (self.y > 2732 + 150) {
self.y = -150;
// Randomize tree appearance when resetting
trunkAsset.height = 80 + Math.random() * 80;
trunkAsset.width = 30 + Math.random() * 20;
var scale = 0.8 + Math.random() * 0.5;
foliage1.scale.set(scale);
foliage2.scale.set(scale * 0.9);
// Slightly vary green shades
var greenVariation = Math.floor(Math.random() * 40);
foliageAsset1.tint = 34 + greenVariation << 16 | 139 + greenVariation << 8 | 34;
foliageAsset2.tint = 50 + greenVariation << 16 | 205 + greenVariation << 8 | 50;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game variables
var car;
var lines = [];
var buildings = [];
var trees = [];
var stores = [];
var streetLights = [];
var score = 0;
var scoreText;
var speedMultiplier = 1;
var lineSpawnRate = 60; // Frames between line spawns
var difficulty = 1;
var isGameActive = true;
var scoreEffects = [];
var tapEffects = [];
var effectIndex = 0;
var tapEffectIndex = 0;
var lastJumpScore = 0;
// Create a sky background
game.setBackgroundColor(0x87CEEB); // Light blue sky
// Initialize the road
var road = game.addChild(LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
width: 1200,
// Make the road narrower than the full width
tint: 0x444444 // Darker gray for asphalt
}));
// Add road edges/sidewalks
var leftSidewalk = game.addChild(LK.getAsset('building', {
anchorX: 0,
anchorY: 0.5,
width: (2048 - road.width) / 2,
height: 2732,
x: 0,
y: 2732 / 2,
tint: 0x999999 // Light gray for sidewalk
}));
var rightSidewalk = game.addChild(LK.getAsset('building', {
anchorX: 1,
anchorY: 0.5,
width: (2048 - road.width) / 2,
height: 2732,
x: 2048,
y: 2732 / 2,
tint: 0x999999 // Light gray for sidewalk
}));
// Initialize score display
scoreText = new Text2('SCORE: 0', {
size: 80,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 5
});
scoreText.anchor.set(0.5, 0);
scoreText.y = 50; // Move down slightly from top
LK.gui.top.addChild(scoreText);
// Add game instructions
var instructionsText = new Text2('Tap when no lines\nAvoid tapping on lines\nScore 1000 to win', {
size: 80,
fill: 0xFFFFFF,
stroke: 0x800080,
strokeThickness: 10,
fontWeight: 'bold',
align: 'center'
});
instructionsText.anchor.set(0.5, 0);
instructionsText.y = 150;
LK.gui.top.addChild(instructionsText);
// Create buildings for city skyline on both sides of the road (not on the road)
for (var i = 0; i < 20; i++) {
var building = new Building();
// Position buildings only on the sides, ensuring they stay off the road
var roadLeftEdge = (2048 - road.width) / 2;
var roadRightEdge = 2048 - roadLeftEdge;
if (i % 2 === 0) {
// Left side only - ensure buildings stay completely on sidewalk
var buildingWidth = building.children[0].width;
building.x = Math.random() * (roadLeftEdge - buildingWidth / 2); // Left side only, accounting for building width
} else {
// Right side only - ensure buildings stay completely on sidewalk
var buildingWidth = building.children[0].width;
building.x = roadRightEdge + Math.random() * (roadLeftEdge - buildingWidth / 2); // Right side only
}
building.y = Math.random() * 2732;
buildings.push(building);
game.addChild(building);
}
// Add trees along the road
var trees = [];
for (var i = 0; i < 15; i++) {
var tree = new Tree();
// Position trees on the sidewalks
if (i % 2 === 0) {
// Left side trees - closer to road
tree.x = (2048 - road.width) / 2 - 80 - Math.random() * 60;
} else {
// Right side trees - closer to road
tree.x = 2048 - (2048 - road.width) / 2 + 80 + Math.random() * 60;
}
// Distribute trees along the road
tree.y = -150 + i * 350 + Math.random() * 200;
trees.push(tree);
game.addChild(tree);
}
// Add stores on the sides
var stores = [];
for (var i = 0; i < 8; i++) {
var store = new Store();
// Position stores on the sidewalks, further from the road than trees
if (i % 2 === 0) {
// Left side stores
store.x = (2048 - road.width) / 4;
} else {
// Right side stores
store.x = 2048 - (2048 - road.width) / 4;
}
// Distribute stores along the road with some spacing
store.y = -300 + i * 650 + Math.random() * 300;
stores.push(store);
game.addChild(store);
}
// Add street lights
var streetLights = [];
for (var i = 0; i < 12; i++) {
var light = new StreetLight();
// Position lights on the edge of the road
if (i % 2 === 0) {
// Left side lights
light.x = (2048 - road.width) / 2 - 20;
} else {
// Right side lights
light.x = 2048 - (2048 - road.width) / 2 + 20;
}
// Distribute lights evenly
light.y = -200 + i * 400 + i % 2 * 200;
streetLights.push(light);
game.addChild(light);
}
// Create car
car = new Car();
car.x = 2048 / 2;
car.y = 2732 - 400;
game.addChild(car);
// Bring car to front to make sure it appears on top of lines
game.setChildIndex(car, game.children.length - 1);
// Create score effects pool
for (var i = 0; i < 5; i++) {
var effect = new ScoreEffect();
effect.visible = false;
scoreEffects.push(effect);
game.addChild(effect);
}
// Create tap effects pool - increased size to handle multiple effects
for (var i = 0; i < 10; i++) {
var tapEffect = new TapEffect();
tapEffect.visible = false;
tapEffects.push(tapEffect);
game.addChild(tapEffect);
}
// Game click handler
game.down = function (x, y, obj) {
if (!isGameActive) {
return;
}
// Check if car is already jumping
if (!car.jumping) {
car.jump();
// Check if car is over a road line
var overLine = false;
for (var i = 0; i < lines.length; i++) {
// Check if the car intersects with any vertical road line
if (lines[i].active && car.intersects(lines[i])) {
overLine = true;
break;
}
}
// Score or penalty based on jump
if (overLine) {
// Penalty for jumping over a line
score = Math.max(0, score - 10);
// Show penalty effect
var effect = scoreEffects[effectIndex];
effectIndex = (effectIndex + 1) % scoreEffects.length;
effect.show(car.x, car.y - 50, false);
// Show tap feedback effect (red for penalty)
var tapEffect = tapEffects[tapEffectIndex];
tapEffectIndex = (tapEffectIndex + 1) % tapEffects.length;
tapEffect.show(x, y, false);
// Add multiple red effects for stronger visual feedback
for (var j = 0; j < 2; j++) {
var extraEffect = tapEffects[tapEffectIndex];
tapEffectIndex = (tapEffectIndex + 1) % tapEffects.length;
extraEffect.show(x + (Math.random() * 100 - 50), y + (Math.random() * 100 - 50), false);
}
// Flash screen red for bad tap
LK.effects.flashScreen(0xff0000, 300);
} else {
// Points for jumping on empty road
var pointsGained = 20 * difficulty;
score += pointsGained;
lastJumpScore = pointsGained;
// Show score effect
var effect = scoreEffects[effectIndex];
effectIndex = (effectIndex + 1) % scoreEffects.length;
effect.show(car.x, car.y - 50, true);
// Show tap feedback effect (green for points)
var tapEffect = tapEffects[tapEffectIndex];
tapEffectIndex = (tapEffectIndex + 1) % tapEffects.length;
tapEffect.show(x, y, true);
// Add multiple green effects for stronger visual feedback
for (var j = 0; j < 2; j++) {
var extraEffect = tapEffects[tapEffectIndex];
tapEffectIndex = (tapEffectIndex + 1) % tapEffects.length;
extraEffect.show(x + (Math.random() * 100 - 50), y + (Math.random() * 100 - 50), true);
}
}
// Update score display
scoreText.setText(Math.round(score));
LK.setScore(Math.round(score));
}
};
// Start background music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 0.4,
duration: 1000
}
});
// Game update function
game.update = function () {
if (!isGameActive) {
return;
}
// Update car
car.update();
// Update all lines
for (var i = lines.length - 1; i >= 0; i--) {
lines[i].update();
// We don't penalize for just passing over a line anymore
// Only interactions happen when player taps
if (!lines[i].scored && lines[i].lastY < car.y && lines[i].y >= car.y) {
lines[i].scored = true; // Mark as scored
}
// Remove inactive lines
if (!lines[i].active) {
lines[i].destroy();
lines.splice(i, 1);
}
}
// Update all buildings
for (var i = 0; i < buildings.length; i++) {
buildings[i].speed = 5 * speedMultiplier;
buildings[i].update();
}
// Update all trees
for (var i = 0; i < trees.length; i++) {
trees[i].speed = 7 * speedMultiplier;
trees[i].update();
}
// Update all stores
for (var i = 0; i < stores.length; i++) {
stores[i].speed = 5 * speedMultiplier;
stores[i].update();
}
// Update all street lights
for (var i = 0; i < streetLights.length; i++) {
streetLights[i].speed = 7 * speedMultiplier;
streetLights[i].update();
}
// Create new lines at intervals - always come from center of the screen
if (LK.ticks % Math.floor(lineSpawnRate / speedMultiplier) === 0) {
var line = new Line();
// Position line at the center of the screen
line.x = 2048 / 2; // Center X coordinate
line.y = -50;
line.speed = 15 * speedMultiplier;
lines.push(line);
game.addChild(line);
// Ensure car is always rendered on top of lines
game.setChildIndex(car, game.children.length - 1);
}
// Increase difficulty over time
if (LK.ticks % 600 === 0) {
// Every 10 seconds
difficulty += 0.1;
// Speed up game after 500 points, but not too much, and slow down a bit after 800
if (score > 800) {
speedMultiplier = 1 + (difficulty - 1) * 1.0; // Reduced speed multiplier after 800 points
lineSpawnRate = Math.max(15, 40 - difficulty * 3); // Reduced line spawn rate
} else if (score > 500) {
speedMultiplier = 1 + (difficulty - 1) * 1.1; // Moderate speed increase after 500 points
lineSpawnRate = Math.max(12, 40 - difficulty * 4); // Moderate increase in line spawn rate
} else {
speedMultiplier = 1 + (difficulty - 1) * 0.5;
lineSpawnRate = Math.max(15, 60 - difficulty * 5);
}
}
// Check for game over condition
if (score < 0) {
isGameActive = false;
LK.showGameOver();
}
// Check for win condition (1000 points to win)
if (score >= 1000) {
isGameActive = false;
LK.showYouWin();
}
};