/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var EnemyChaser = Container.expand(function () {
var self = Container.call(this);
// Enemy state
self.lane = 1; // Start in middle lane
self.speed = GAME_SPEED * 0.8; // Slightly slower than player initially
self.targetX = LANE_POSITIONS[self.lane];
self.chasingPlayer = false;
self.swerveTimer = 0;
self.swerveInterval = 120; // Time between lane changes when not chasing
self.detectionRange = 500; // Distance to start chasing player
// Create enemy graphics
var graphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFF0000 // Red tint to distinguish from player
});
// Random swerving behavior
self.swerve = function () {
var newLane;
// Choose a random lane that's different from current
do {
newLane = Math.floor(Math.random() * 3);
} while (newLane === self.lane);
self.moveToLane(newLane);
};
// Chase player behavior
self.chase = function () {
// Move toward player's lane
if (self.lane < player.lane) {
self.moveToLane(self.lane + 1);
} else if (self.lane > player.lane) {
self.moveToLane(self.lane - 1);
}
};
// Move enemy to lane
self.moveToLane = function (laneIndex) {
if (laneIndex < 0 || laneIndex > 2) {
return;
}
self.lane = laneIndex;
self.targetX = LANE_POSITIONS[laneIndex];
tween.stop(self, {
x: true
});
tween(self, {
x: self.targetX
}, {
duration: 300,
easing: tween.easeOut
});
};
self.update = function () {
// Move forward
// In scared mode, enemies move 4x faster than player
if (scaredMode) {
self.y += self.speed * 4;
} else {
self.y += self.speed;
}
// Check if close enough to chase player
var distanceToPlayer = Math.abs(player.y - self.y);
self.chasingPlayer = distanceToPlayer < self.detectionRange;
// Update behavior based on proximity to player
if (self.chasingPlayer) {
// Chase every 60 frames when close (or faster in scared mode)
if (LK.ticks % (scaredMode ? 30 : 60) === 0) {
self.chase();
}
// Speed up when chasing
self.speed = Math.min(GAME_SPEED * 1.1, MAX_GAME_SPEED);
} else {
// Random swerving when not chasing
self.swerveTimer++;
if (self.swerveTimer >= self.swerveInterval) {
self.swerve();
self.swerveTimer = 0;
// Randomize next swerve interval
self.swerveInterval = 90 + Math.floor(Math.random() * 90);
}
// Normal speed when not chasing
self.speed = GAME_SPEED * 0.8;
}
// Reset enemy if it goes off screen
if (self.y > 2732 + graphics.height) {
self.y = -300;
self.swerveTimer = 0;
}
};
return self;
});
var Obstacle = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'car';
self.lane = Math.floor(Math.random() * 3); // Random lane (0, 1, 2)
self.speed = GAME_SPEED;
var assetType;
switch (self.type) {
case 'car':
assetType = 'car';
break;
case 'pedestrian':
assetType = 'pedestrian';
break;
case 'pothole':
assetType = 'pothole';
break;
case 'construction':
assetType = 'construction';
break;
default:
assetType = 'car';
}
var graphics = self.attachAsset(assetType, {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
self.y += self.speed;
// Mark for removal if off screen
if (self.y > 2732 + graphics.height) {
self.markedForRemoval = true;
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Player state
self.lane = 1; // Middle lane
self.isInvincible = false;
self.invincibleTimer = null;
self.isFallen = false;
self.fallTimer = null;
// Fall over animation
self.fallOver = function () {
if (self.isFallen) {
return;
} // Already fallen
self.isFallen = true;
// Stop any ongoing tweens
tween.stop(self, {
rotation: true
});
// Play fall animation
tween(playerGraphics, {
rotation: Math.PI / 2 // 90 degrees - fall on side
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Show game over immediately
LK.showGameOver();
}
});
};
// Get back up animation
self.getBackUp = function () {
self.isFallen = false;
if (self.fallTimer) {
LK.clearTimeout(self.fallTimer);
self.fallTimer = null;
}
// Play get back up animation
tween(playerGraphics, {
rotation: 0 // Upright position
}, {
duration: 300,
easing: tween.easeOut
});
};
// Move player to lane
self.moveToLane = function (laneIndex) {
if (laneIndex < 0 || laneIndex > 2 || self.isFallen) {
return;
}
self.lane = laneIndex;
var targetX = LANE_POSITIONS[laneIndex];
tween.stop(self, {
x: true
});
tween(self, {
x: targetX
}, {
duration: self.moveSpeed || 150,
// Use moveSpeed if set (for scared mode)
easing: tween.easeOut
});
};
// Make player invincible for a duration
self.setInvincible = function (duration) {
self.isInvincible = true;
playerGraphics.tint = 0x2ECC71; // Green tint
if (self.invincibleTimer) {
LK.clearTimeout(self.invincibleTimer);
}
self.invincibleTimer = LK.setTimeout(function () {
self.isInvincible = false;
playerGraphics.tint = 0xFFFFFF; // Reset tint
}, duration);
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
self.lane = Math.floor(Math.random() * 3); // Random lane (0, 1, 2)
self.speed = GAME_SPEED;
var graphics = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
// Add pulsing animation
function pulse() {
tween(graphics, {
scale: 1.2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(graphics, {
scale: 1.0
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: pulse
});
}
});
}
pulse();
self.update = function () {
self.y += self.speed;
// Mark for removal if off screen
if (self.y > 2732 + graphics.height) {
self.markedForRemoval = true;
}
};
return self;
});
var SettingsMenu = Container.expand(function () {
var self = Container.call(this);
// Load settings from storage (with defaults)
var settings = {
sound: storage.sound !== undefined ? storage.sound : true,
sfx: storage.sfx !== undefined ? storage.sfx : true,
language: storage.language || 'en',
testCodes: storage.testCodes || false,
debug: storage.debug || false
};
// Create background panel
var panel = LK.getAsset('construction', {
anchorX: 0.5,
anchorY: 0.5,
width: 1200,
height: 1100,
tint: 0x333333
});
self.addChild(panel);
// Create title
var title = new Text2('SETTINGS', {
size: 100,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
title.anchor.set(0.5, 0.5);
title.y = -400;
self.addChild(title);
// Helper function to create toggle buttons
function createToggleButton(labelText, _isOn, y, callback) {
var container = new Container();
container.y = y;
self.addChild(container);
// Label
var label = new Text2(labelText, {
size: 70,
fill: 0xFFFFFF
});
label.anchor.set(0, 0.5);
label.x = -450;
container.addChild(label);
// Toggle button background
var toggleBg = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 100,
tint: 0x666666
});
toggleBg.x = 350;
container.addChild(toggleBg);
// Toggle indicator
var toggleIndicator = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80,
tint: _isOn ? 0x00CC00 : 0xCC0000
});
toggleIndicator.x = 350 + (_isOn ? 40 : -40);
container.addChild(toggleIndicator);
// Status text
var statusText = new Text2(_isOn ? "ON" : "OFF", {
size: 50,
fill: 0xFFFFFF
});
statusText.anchor.set(0.5, 0.5);
statusText.x = 350;
container.addChild(statusText);
// Make toggle interactive
toggleBg.interactive = true;
toggleIndicator.interactive = true;
statusText.interactive = true;
// Toggle functionality
function toggle() {
// If this is test codes toggle or debug toggle and turning it on, verify developer name
if ((labelText === "Test Codes" || labelText === "Debug Mode") && !_isOn) {
var updateInput = function updateInput(key) {
if (key === "DEL") {
currentInput = currentInput.slice(0, -1);
} else {
currentInput += key;
}
inputText.setText(currentInput);
}; // Function to check developer name and close dialog
var checkAndClose = function checkAndClose() {
if (currentInput.toLowerCase() === "chazlin") {
// Correct developer name
inputContainer.destroy();
// Toggle on
_isOn = true;
toggleIndicator.tint = 0x00CC00;
toggleIndicator.x = 350 + 40;
statusText.setText("ON");
if (callback) {
callback(true);
}
} else {
// Wrong developer name - shake dialog
tween(dialogBg, {
x: 10
}, {
duration: 50,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(dialogBg, {
x: -10
}, {
duration: 50,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(dialogBg, {
x: 0
}, {
duration: 50,
easing: tween.easeInOut
});
}
});
}
});
// Clear input
currentInput = "";
inputText.setText("");
}
};
// Create input dialog for developer name
var inputContainer = new Container();
inputContainer.x = 0;
inputContainer.y = 0;
self.addChild(inputContainer);
// Dialog background
var dialogBg = LK.getAsset('construction', {
anchorX: 0.5,
anchorY: 0.5,
width: 800,
height: 400,
tint: 0x444444
});
inputContainer.addChild(dialogBg);
// Prompt text
var promptText = new Text2("Enter developer name:", {
size: 60,
fill: 0xFFFFFF
});
promptText.anchor.set(0.5, 0.5);
promptText.y = -100;
inputContainer.addChild(promptText);
// Input field background
var inputFieldBg = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 600,
height: 80,
tint: 0x666666
});
inputFieldBg.y = 0;
inputContainer.addChild(inputFieldBg);
// Input text (initially empty)
var inputText = new Text2("", {
size: 50,
fill: 0xFFFFFF
});
inputText.anchor.set(0.5, 0.5);
inputText.y = 0;
inputContainer.addChild(inputText);
// Create keyboard buttons
var keys = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
var keyButtons = [];
var currentInput = "";
// Function to update input text
for (var i = 0; i < keys.length; i++) {
var keyButton = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 40,
tint: 0x888888
});
var keyLabel = new Text2(keys[i], {
size: 25,
fill: 0xFFFFFF
});
keyLabel.anchor.set(0.5, 0.5);
// Position keys in rows
var row = Math.floor(i / 13);
var col = i % 13;
keyButton.x = (col - 6) * 45;
keyButton.y = 100 + row * 45;
keyLabel.x = keyButton.x;
keyLabel.y = keyButton.y;
inputContainer.addChild(keyButton);
inputContainer.addChild(keyLabel);
// Make button interactive
keyButton.interactive = true;
keyLabel.interactive = true;
// Capture current key for closure
(function (currentKey) {
keyButton.down = keyLabel.down = function () {
updateInput(currentKey);
};
})(keys[i]);
}
// Add delete button
var delButton = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 100,
height: 40,
tint: 0xCC4444
});
delButton.x = 230;
delButton.y = 100;
inputContainer.addChild(delButton);
var delLabel = new Text2("DEL", {
size: 25,
fill: 0xFFFFFF
});
delLabel.anchor.set(0.5, 0.5);
delLabel.x = delButton.x;
delLabel.y = delButton.y;
inputContainer.addChild(delLabel);
delButton.interactive = true;
delLabel.interactive = true;
delButton.down = delLabel.down = function () {
updateInput("DEL");
};
// Add submit button
var submitButton = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 150,
height: 60,
tint: 0x44CC44
});
submitButton.y = 180;
inputContainer.addChild(submitButton);
var submitLabel = new Text2("Submit", {
size: 30,
fill: 0xFFFFFF
});
submitLabel.anchor.set(0.5, 0.5);
submitLabel.y = submitButton.y;
inputContainer.addChild(submitLabel);
submitButton.interactive = true;
submitLabel.interactive = true;
submitButton.down = submitLabel.down = checkAndClose;
// Add cancel button
var cancelButton = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 150,
height: 60,
tint: 0xCC4444
});
cancelButton.x = -200;
cancelButton.y = 180;
inputContainer.addChild(cancelButton);
var cancelLabel = new Text2("Cancel", {
size: 30,
fill: 0xFFFFFF
});
cancelLabel.anchor.set(0.5, 0.5);
cancelLabel.x = cancelButton.x;
cancelLabel.y = cancelButton.y;
inputContainer.addChild(cancelLabel);
cancelButton.interactive = true;
cancelLabel.interactive = true;
cancelButton.down = cancelLabel.down = function () {
inputContainer.destroy();
};
} else {
// Normal toggle behavior for other settings
_isOn = !_isOn;
toggleIndicator.tint = _isOn ? 0x00CC00 : 0xCC0000;
toggleIndicator.x = 350 + (_isOn ? 40 : -40);
statusText.setText(_isOn ? "ON" : "OFF");
if (callback) {
callback(_isOn);
}
}
}
toggleBg.down = toggleIndicator.down = statusText.down = toggle;
return {
container: container,
toggle: toggle,
isOn: function isOn() {
return _isOn;
}
};
}
// Helper function to create language selector
function createLanguageSelector(y) {
var container = new Container();
container.y = y;
self.addChild(container);
// Label
var label = new Text2("Language", {
size: 70,
fill: 0xFFFFFF
});
label.anchor.set(0, 0.5);
label.x = -450;
container.addChild(label);
// Language options
var languages = ["English", "Welsh"];
var currentLang = settings.language === "cy" ? 1 : 0;
// Create language button
var langButton = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 100,
tint: 0x4488FF
});
langButton.x = 350;
container.addChild(langButton);
// Language text
var langText = new Text2(languages[currentLang], {
size: 50,
fill: 0xFFFFFF
});
langText.anchor.set(0.5, 0.5);
langText.x = 350;
container.addChild(langText);
// Make language button interactive
langButton.interactive = true;
langText.interactive = true;
// Toggle functionality
function toggle() {
currentLang = (currentLang + 1) % languages.length;
langText.setText(languages[currentLang]);
settings.language = currentLang === 0 ? "en" : "cy";
storage.language = settings.language;
}
langButton.down = langText.down = toggle;
return container;
}
// Create setting toggles
var soundToggle = createToggleButton("Music", settings.sound, -300, function (isOn) {
settings.sound = isOn;
storage.sound = isOn;
if (isOn) {
LK.playMusic('gameMusic');
} else {
LK.stopMusic();
}
});
var sfxToggle = createToggleButton("Sound Effects", settings.sfx, -200, function (isOn) {
settings.sfx = isOn;
storage.sfx = isOn;
});
var testCodesToggle = createToggleButton("Test Codes", settings.testCodes, -100, function (isOn) {
settings.testCodes = isOn;
storage.testCodes = isOn;
});
// Add debug toggle
var debugToggle = createToggleButton("Debug Mode", settings.debug || false, 0, function (isOn) {
settings.debug = isOn;
storage.debug = isOn;
});
// Create language selector
var languageSelector = createLanguageSelector(0);
// Create leaderboard button
var leaderboardBtn = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 600,
height: 120,
tint: 0x4444FF
});
leaderboardBtn.y = 100;
self.addChild(leaderboardBtn);
var leaderboardText = new Text2("Leaderboard", {
size: 70,
fill: 0xFFFFFF
});
leaderboardText.anchor.set(0.5, 0.5);
leaderboardText.y = 100;
self.addChild(leaderboardText);
// Leaderboard button functionality
leaderboardBtn.interactive = true;
leaderboardText.interactive = true;
leaderboardBtn.down = leaderboardText.down = function () {
// Using LK.showLeaderboard to display the leaderboard (corrected syntax)
LK.showLeaderboard();
};
// Create back button
var backBtn = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 120,
tint: 0xCC4444
});
backBtn.y = 250;
self.addChild(backBtn);
var backText = new Text2("Back", {
size: 70,
fill: 0xFFFFFF
});
backText.anchor.set(0.5, 0.5);
backText.y = 250;
self.addChild(backText);
// Back button functionality
backBtn.interactive = true;
backText.interactive = true;
backBtn.down = backText.down = function () {
self.visible = false;
startMenu.visible = true;
};
// Public method to apply settings
self.applySettings = function () {
// Apply sound settings
if (!settings.sound) {
LK.stopMusic();
}
};
// Method to check if SFX is enabled
self.isSfxEnabled = function () {
return settings.sfx;
};
return self;
});
var StartMenu = Container.expand(function () {
var self = Container.call(this);
// Create background panel
var panel = LK.getAsset('construction', {
anchorX: 0.5,
anchorY: 0.5,
width: 1200,
height: 1000,
// Increased height for settings button
tint: 0x333333
});
self.addChild(panel);
// Create title
var title = new Text2('LANE RIDER', {
size: 180,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
title.anchor.set(0.5, 0.5);
title.y = -250; // Adjusted position
self.addChild(title);
// Create normal mode button (now "Average Joe")
var normalBtn = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 700,
height: 180,
tint: 0xFF8C00
});
normalBtn.y = 0; // Adjusted position
self.addChild(normalBtn);
var normalText = new Text2('Average Joe', {
size: 90,
fill: 0xFFFFFF
});
normalText.anchor.set(0.5, 0.5);
normalText.y = 0; // Adjusted position
self.addChild(normalText);
// Create easy mode button
var easyBtn = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 700,
height: 180,
tint: 0x00CC00
});
easyBtn.y = 200; // Adjusted position
self.addChild(easyBtn);
var easyText = new Text2('Easy Mode', {
size: 90,
fill: 0xFFFFFF
});
easyText.anchor.set(0.5, 0.5);
easyText.y = 200; // Adjusted position
self.addChild(easyText);
// Create hard mode button
var hardBtn = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 700,
height: 180,
tint: 0xFF0000
});
hardBtn.y = 400; // Adjusted position
self.addChild(hardBtn);
var hardText = new Text2('Hard Mode', {
size: 90,
fill: 0xFFFFFF
});
hardText.anchor.set(0.5, 0.5);
hardText.y = 400; // Adjusted position
self.addChild(hardText);
// Create scared mode button
var scaredBtn = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 700,
height: 180,
tint: 0x800080 // Purple tint
});
scaredBtn.y = 600; // Adjusted position
self.addChild(scaredBtn);
var scaredText = new Text2('NO DONT DO IT IM SCARED', {
size: 50,
fill: 0xFFFFFF
});
scaredText.anchor.set(0.5, 0.5);
scaredText.y = 600; // Adjusted position
self.addChild(scaredText);
// Create settings button
var settingsBtn = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 700,
height: 180,
tint: 0x00AAAA // Teal color
});
settingsBtn.y = 800; // Position below other buttons
self.addChild(settingsBtn);
var settingsText = new Text2('Settings', {
size: 90,
fill: 0xFFFFFF
});
settingsText.anchor.set(0.5, 0.5);
settingsText.y = 800;
self.addChild(settingsText);
// Add interaction for normal mode
normalBtn.interactive = true;
normalBtn.down = normalText.down = function () {
self.visible = false;
easyMode = false;
hardMode = false;
startGame();
};
// Add interaction for easy mode
easyBtn.interactive = true;
easyBtn.down = easyText.down = function () {
self.visible = false;
easyMode = true;
hardMode = false;
startGame();
};
// Add interaction for hard mode
hardBtn.interactive = true;
hardBtn.down = hardText.down = function () {
self.visible = false;
easyMode = false;
hardMode = true;
scaredMode = false;
startGame();
};
// Add interaction for scared mode
scaredBtn.interactive = true;
scaredBtn.down = scaredText.down = function () {
self.visible = false;
easyMode = false;
hardMode = false;
scaredMode = true;
startGame();
};
// Add interaction for settings button
settingsBtn.interactive = true;
settingsText.interactive = true;
settingsBtn.down = settingsText.down = function () {
self.visible = false;
settingsMenu.visible = true;
};
return self;
});
var WinningZone = Container.expand(function () {
var self = Container.call(this);
self.width = 2048;
self.height = 300;
// Create background for winning zone
var background = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: self.width,
height: self.height,
tint: 0xFFD700 // Gold color
});
self.addChild(background);
// Create winning text
var winText = new Text2(scaredMode ? welshTranslations['FINISH LINE'] : 'FINISH LINE', {
size: 100,
fill: 0x000000
});
winText.anchor.set(0.5, 0.5);
self.addChild(winText);
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x7DCEA0 // Greenish background
});
/****
* Game Code
****/
// Constants
var LANE_WIDTH = 2048 / 3;
var LANE_POSITIONS = [LANE_WIDTH / 2, LANE_WIDTH + LANE_WIDTH / 2, 2 * LANE_WIDTH + LANE_WIDTH / 2];
var MIN_OBSTACLE_INTERVAL = 60;
var MAX_OBSTACLE_INTERVAL = 120;
var GAME_SPEED = 6;
var MAX_GAME_SPEED = 15;
var SPEED_INCREASE_RATE = 0.0005;
var OBSTACLE_TYPES = ['car', 'pedestrian', 'pothole', 'construction'];
var POWERUP_CHANCE = 0.1; // 10% chance per obstacle spawn
var INVINCIBLE_DURATION = 5000; // 5 seconds (base value)
// Game variables
var player;
var enemy;
var secondEnemy; // Second enemy for hard mode
var obstacles = [];
var powerups = [];
var lanes = [];
var obstacleTimer = 0;
var nextObstacleTime = 100;
var distance = 0;
var highScore = storage.highScore || 0;
var isGameStarted = false;
var gameStartTime = 0;
var currentTime = 0;
var easyMode = false;
var hardMode = false;
var scaredMode = false; // New scared mode
var startMenu;
var settingsMenu;
var winningZone;
var winningDistance = 0; // Distance required to win based on difficulty
var redOverlay; // Reddish overlay for scared mode
// Welsh translations for scared mode
var welshTranslations = {
'Distance': 'Pellter',
'Best': 'Gorau',
'Time': 'Amser',
'FINISH LINE': 'LLINELL DERFYN',
'Easy Mode': 'Modd Hawdd',
'Average Joe': 'Joe Cyffredin',
'Hard Mode': 'Modd Caled',
'LANE RIDER': 'RIDER LÔN',
'NO DONT DO IT IM SCARED': 'PEIDIWCH Â GWNEUD OFNAIS WYFI'
};
// GUI elements
var scoreTxt;
var highScoreTxt;
var timeTxt;
var startInstructions;
// Initialize game UI
function initUI() {
// Create score display
scoreTxt = new Text2('Distance: 0', {
size: 70,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.visible = false;
LK.gui.top.addChild(scoreTxt);
// Create high score display
highScoreTxt = new Text2('Best: ' + highScore, {
size: 50,
fill: 0xFFFFFF
});
highScoreTxt.anchor.set(0.5, 0);
highScoreTxt.y = 80;
highScoreTxt.visible = false;
LK.gui.top.addChild(highScoreTxt);
// Create stopwatch display
timeTxt = new Text2('Time: 0.0s', {
size: 50,
fill: 0xFFFFFF
});
timeTxt.anchor.set(0.5, 0);
timeTxt.y = 150;
timeTxt.visible = false;
LK.gui.top.addChild(timeTxt);
// Create start instructions
startInstructions = new Text2('Tap to start\nSwipe left/right to change lanes', {
size: 80,
fill: 0xFFFFFF
});
startInstructions.anchor.set(0.5, 0.5);
startInstructions.visible = false;
LK.gui.center.addChild(startInstructions);
}
// Initialize game world
function initWorld() {
// Create the three lanes
for (var i = 0; i < 3; i++) {
var lane = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
width: 20,
height: 2732
});
lane.x = (i + 1) * LANE_WIDTH;
game.addChild(lane);
lanes.push(lane);
}
// Create player
player = new Player();
player.x = LANE_POSITIONS[1]; // Start in middle lane
player.y = 2732 - 300; // Position near bottom of screen
game.addChild(player);
// Hide player initially
player.visible = false;
// Create enemy chaser
enemy = new EnemyChaser();
enemy.x = LANE_POSITIONS[1]; // Start in middle lane
enemy.y = -500; // Start above screen
game.addChild(enemy);
// Hide enemy initially
enemy.visible = false;
// Create second enemy chaser for hard mode
secondEnemy = new EnemyChaser();
secondEnemy.x = LANE_POSITIONS[0]; // Start in left lane
secondEnemy.y = -800; // Start above screen but offset from first enemy
game.addChild(secondEnemy);
// Hide second enemy initially
secondEnemy.visible = false;
// Create and display start menu
startMenu = new StartMenu();
startMenu.x = 2048 / 2;
startMenu.y = 2732 / 2;
game.addChild(startMenu);
// Create settings menu (hidden initially)
settingsMenu = new SettingsMenu();
settingsMenu.x = 2048 / 2;
settingsMenu.y = 2732 / 2;
settingsMenu.visible = false;
game.addChild(settingsMenu);
}
// Start the game
function startGame() {
isGameStarted = true;
player.visible = true;
// Show appropriate enemies based on game mode
enemy.visible = !easyMode && !scaredMode;
secondEnemy.visible = hardMode;
startInstructions.visible = true;
// Show UI elements
scoreTxt.visible = true;
highScoreTxt.visible = true;
timeTxt.visible = true;
// Reset game state
obstacles.forEach(function (obstacle) {
obstacle.destroy();
});
obstacles = [];
powerups.forEach(function (powerup) {
powerup.destroy();
});
powerups = [];
distance = 0;
// Handle scared mode speeds
if (scaredMode) {
GAME_SPEED = 3; // Half the normal speed
player.moveSpeed = 300; // Double the lane change time (slower movement)
// Make player start with invincibility to give them a better chance
player.setInvincible(10000); // 10 seconds of initial invincibility
// Create red overlay if it doesn't exist
if (!redOverlay) {
redOverlay = LK.getAsset('lane', {
anchorX: 0,
anchorY: 0,
width: 2048,
height: 2732,
tint: 0xFF0000
});
redOverlay.alpha = 0.3;
game.addChild(redOverlay);
} else {
redOverlay.visible = true;
}
// Translate text to Welsh for scared mode
scoreTxt.setText(welshTranslations['Distance'] + ': 0');
highScoreTxt.setText(welshTranslations['Best'] + ': ' + highScore);
timeTxt.setText(welshTranslations['Time'] + ': 0.0s');
startInstructions.setText(welshTranslations['Tap to start'] + '\n' + welshTranslations['Swipe left/right to change lanes']);
} else {
GAME_SPEED = hardMode ? 8 : 6; // Higher starting speed in hard mode
player.moveSpeed = 150; // Normal movement speed
// Hide red overlay if it exists
if (redOverlay) {
redOverlay.visible = false;
}
// Reset text to English
scoreTxt.setText('Distance: 0');
highScoreTxt.setText('Best: ' + highScore);
timeTxt.setText('Time: 0.0s');
startInstructions.setText('Tap to start\nSwipe left/right to change lanes');
}
obstacleTimer = 0;
nextObstacleTime = hardMode ? 70 : scaredMode ? 200 : 100; // Adjust obstacle intervals
// Set winning distance based on difficulty
if (hardMode) {
winningDistance = 1000;
} else if (easyMode) {
winningDistance = 5000;
} else if (scaredMode) {
winningDistance = 3000; // Scared mode winning distance
} else {
winningDistance = 2000; // Average Joe mode
}
// Reset enemy positions
enemy.y = -500;
enemy.lane = 1;
enemy.x = LANE_POSITIONS[1];
enemy.swerveTimer = 0;
// Reset second enemy
secondEnemy.y = -800;
secondEnemy.lane = 0;
secondEnemy.x = LANE_POSITIONS[0];
secondEnemy.swerveTimer = 50; // Offset timer so enemies don't swerve at the same time
// Reset stopwatch
gameStartTime = Date.now();
currentTime = 0;
// Play different background music based on game mode if enabled
if (storage.sound !== false) {
if (scaredMode) {
// Play eerie music for scared mode
LK.playMusic('scaredMusic', {
fade: {
start: 0,
end: 0.5,
duration: 500
}
});
} else if (hardMode) {
// Play intense music for hard mode
LK.playMusic('hardMusic', {
fade: {
start: 0,
end: 1,
duration: 300
}
});
} else if (easyMode) {
// Play relaxed music for easy mode
LK.playMusic('easyMusic', {
fade: {
start: 0,
end: 0.7,
duration: 400
}
});
} else {
// Play standard music for normal/average joe mode
LK.playMusic('gameMusic');
}
}
// Apply settings
settingsMenu.applySettings();
}
// Spawn a new obstacle
function spawnObstacle() {
// Choose random obstacle type
var type = OBSTACLE_TYPES[Math.floor(Math.random() * OBSTACLE_TYPES.length)];
var obstacle = new Obstacle(type);
// Ensure obstacle is in a valid lane
obstacle.x = LANE_POSITIONS[obstacle.lane];
obstacle.y = -200; // Start above screen
obstacles.push(obstacle);
game.addChild(obstacle);
// In scared mode, spawn more obstacles
if (scaredMode && Math.random() < 0.8) {
// 80% chance of additional obstacle in scared mode
for (var i = 0; i < 2; i++) {
// Two more obstacles
var extraType = OBSTACLE_TYPES[Math.floor(Math.random() * OBSTACLE_TYPES.length)];
var extraObstacle = new Obstacle(extraType);
// Try to find an available lane
var freeLanes = [0, 1, 2].filter(function (lane) {
return !obstacles.some(function (obs) {
return obs.lane === lane && Math.abs(obs.y + 200) < 300;
});
});
if (freeLanes.length > 0) {
extraObstacle.lane = freeLanes[Math.floor(Math.random() * freeLanes.length)];
extraObstacle.x = LANE_POSITIONS[extraObstacle.lane];
extraObstacle.y = -200 - i * 150; // Stagger obstacles
obstacles.push(extraObstacle);
game.addChild(extraObstacle);
}
}
}
// In hard mode, possibly spawn a second obstacle in a different lane
else if (hardMode && Math.random() < 0.6) {
// 60% chance of additional obstacle
var secondType = OBSTACLE_TYPES[Math.floor(Math.random() * OBSTACLE_TYPES.length)];
var secondObstacle = new Obstacle(secondType);
// Make sure it's in a different lane
do {
secondObstacle.lane = Math.floor(Math.random() * 3);
} while (secondObstacle.lane === obstacle.lane);
secondObstacle.x = LANE_POSITIONS[secondObstacle.lane];
secondObstacle.y = -350; // Offset from first obstacle
obstacles.push(secondObstacle);
game.addChild(secondObstacle);
}
// Possibly spawn a power-up
if (Math.random() < (scaredMode ? POWERUP_CHANCE * 2 : POWERUP_CHANCE)) {
// 2x more powerups in scared mode
spawnPowerUp();
}
// Set next obstacle spawn time
nextObstacleTime = MIN_OBSTACLE_INTERVAL + Math.floor(Math.random() * (MAX_OBSTACLE_INTERVAL - MIN_OBSTACLE_INTERVAL));
// Reduce spawn interval as game progresses
nextObstacleTime = Math.max(nextObstacleTime * (1 - distance / 50000), MIN_OBSTACLE_INTERVAL);
// In scared mode, increase obstacle spawn time
if (scaredMode) {
nextObstacleTime = Math.floor(nextObstacleTime * 2); // Double the time between obstacles
}
// In hard mode, reduce obstacle spawn time further
else if (hardMode) {
nextObstacleTime = Math.floor(nextObstacleTime * 0.7);
}
}
// Spawn a power-up
function spawnPowerUp() {
var powerup = new PowerUp();
// Make sure power-up is not in same lane as last obstacle
var lastObstacle = obstacles[obstacles.length - 1];
do {
powerup.lane = Math.floor(Math.random() * 3);
} while (powerup.lane === lastObstacle.lane);
powerup.x = LANE_POSITIONS[powerup.lane];
powerup.y = -500; // Start above screen, after the obstacle
powerups.push(powerup);
game.addChild(powerup);
}
// Check collisions between player and obstacles/powerups
function checkCollisions() {
// Check obstacle collisions
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
if (player.lane === obstacle.lane && Math.abs(player.y - obstacle.y) < 100 && !player.isInvincible && !player.isFallen) {
// Collision detected - Play crash sound
if (settingsMenu.isSfxEnabled()) {
LK.getSound('crash').play();
}
// Flash screen to indicate collision
LK.effects.flashScreen(0xFF0000, 500);
// Reset distance counter
distance = 0;
scoreTxt.setText(scaredMode ? welshTranslations['Distance'] + ': 0' : 'Distance: 0');
// Make player fall over
player.fallOver();
return;
}
}
// Check enemy collision (skip in easy mode)
if (!easyMode && player.lane === enemy.lane && Math.abs(player.y - enemy.y) < 100 && !player.isInvincible && !player.isFallen) {
// Collision detected - Play crash sound
if (settingsMenu.isSfxEnabled()) {
LK.getSound('crash').play();
}
// Flash screen to indicate collision
LK.effects.flashScreen(0xFF0000, 500);
// Reset distance counter
distance = 0;
scoreTxt.setText(scaredMode ? welshTranslations['Distance'] + ': 0' : 'Distance: 0');
// Make player fall over
player.fallOver();
return;
}
// Check second enemy collision (hard mode only)
if (hardMode && player.lane === secondEnemy.lane && Math.abs(player.y - secondEnemy.y) < 100 && !player.isInvincible && !player.isFallen) {
// Collision detected - Play crash sound
if (settingsMenu.isSfxEnabled()) {
LK.getSound('crash').play();
}
// Flash screen to indicate collision
LK.effects.flashScreen(0xFF0000, 500);
// Reset distance counter
distance = 0;
scoreTxt.setText(scaredMode ? welshTranslations['Distance'] + ': 0' : 'Distance: 0');
// Make player fall over
player.fallOver();
return;
}
// Check power-up collisions
for (var j = powerups.length - 1; j >= 0; j--) {
var powerup = powerups[j];
if (player.lane === powerup.lane && Math.abs(player.y - powerup.y) < 100) {
// Collect power-up
if (settingsMenu.isSfxEnabled()) {
LK.getSound('powerup').play();
}
// Apply power-up effect (invincibility) - longer in scared mode
player.setInvincible(scaredMode ? INVINCIBLE_DURATION * 3 : INVINCIBLE_DURATION); // 3x longer invincibility in scared mode
// Remove power-up
powerup.destroy();
powerups.splice(j, 1);
}
}
}
// Handle touch events for swiping
var touchStartX = 0;
game.down = function (x, y) {
if (!isGameStarted) {
return;
}
touchStartX = x;
};
game.up = function (x, y) {
if (!isGameStarted) {
return;
}
var swipeDistance = x - touchStartX;
// Determine swipe direction and move player
if (Math.abs(swipeDistance) > 50) {
// Minimum swipe distance threshold
if (swipeDistance > 0) {
// Swipe right
player.moveToLane(Math.min(player.lane + 1, 2));
} else {
// Swipe left
player.moveToLane(Math.max(player.lane - 1, 0));
}
}
};
// Main game update loop
game.update = function () {
if (!isGameStarted) {
return;
}
// Update stopwatch
currentTime = (Date.now() - gameStartTime) / 1000;
if (scaredMode) {
timeTxt.setText(welshTranslations['Time'] + ': ' + currentTime.toFixed(1) + 's');
// Increment distance at half the rate in scared mode
distance += GAME_SPEED / 20;
scoreTxt.setText(welshTranslations['Distance'] + ': ' + Math.floor(distance));
} else {
timeTxt.setText('Time: ' + currentTime.toFixed(1) + 's');
// Normal distance increment
distance += GAME_SPEED / 10;
scoreTxt.setText('Distance: ' + Math.floor(distance));
}
// Check for win condition
if (distance >= winningDistance) {
// Player has reached the required distance, show win
LK.showYouWin();
// Update high score if needed
if (distance > highScore) {
highScore = Math.floor(distance);
storage.highScore = highScore;
highScoreTxt.setText('Best: ' + highScore);
}
return;
}
// Display winning zone when approaching the finish
if (distance >= (scaredMode ? winningDistance - 1000 : winningDistance - 500) && !winningZone) {
winningZone = new WinningZone();
winningZone.x = 2048 / 2;
winningZone.y = -300; // Start off-screen
game.addChild(winningZone);
}
// Move winning zone toward player as they approach finish
if (winningZone) {
winningZone.y += GAME_SPEED;
}
// Gradually increase game speed
GAME_SPEED = Math.min(GAME_SPEED + SPEED_INCREASE_RATE, MAX_GAME_SPEED);
// Spawn obstacles
obstacleTimer++;
if (obstacleTimer >= nextObstacleTime) {
spawnObstacle();
obstacleTimer = 0;
}
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
obstacle.speed = scaredMode ? GAME_SPEED * 2 : GAME_SPEED; // Double speed in scared mode
// Remove obstacles marked for removal
if (obstacle.markedForRemoval) {
obstacle.destroy();
obstacles.splice(i, 1);
}
}
// Update power-ups
for (var j = powerups.length - 1; j >= 0; j--) {
var powerup = powerups[j];
powerup.speed = scaredMode ? GAME_SPEED * 2 : GAME_SPEED; // Double speed in scared mode
// Remove power-ups marked for removal
if (powerup.markedForRemoval) {
powerup.destroy();
powerups.splice(j, 1);
}
}
// Update enemy chasers (based on game mode)
if (!easyMode) {
enemy.update();
// Update second enemy in hard mode
if (hardMode) {
secondEnemy.update();
}
}
// Check for collisions
checkCollisions();
};
// Initialize UI and game world
initUI();
initWorld(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var EnemyChaser = Container.expand(function () {
var self = Container.call(this);
// Enemy state
self.lane = 1; // Start in middle lane
self.speed = GAME_SPEED * 0.8; // Slightly slower than player initially
self.targetX = LANE_POSITIONS[self.lane];
self.chasingPlayer = false;
self.swerveTimer = 0;
self.swerveInterval = 120; // Time between lane changes when not chasing
self.detectionRange = 500; // Distance to start chasing player
// Create enemy graphics
var graphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFF0000 // Red tint to distinguish from player
});
// Random swerving behavior
self.swerve = function () {
var newLane;
// Choose a random lane that's different from current
do {
newLane = Math.floor(Math.random() * 3);
} while (newLane === self.lane);
self.moveToLane(newLane);
};
// Chase player behavior
self.chase = function () {
// Move toward player's lane
if (self.lane < player.lane) {
self.moveToLane(self.lane + 1);
} else if (self.lane > player.lane) {
self.moveToLane(self.lane - 1);
}
};
// Move enemy to lane
self.moveToLane = function (laneIndex) {
if (laneIndex < 0 || laneIndex > 2) {
return;
}
self.lane = laneIndex;
self.targetX = LANE_POSITIONS[laneIndex];
tween.stop(self, {
x: true
});
tween(self, {
x: self.targetX
}, {
duration: 300,
easing: tween.easeOut
});
};
self.update = function () {
// Move forward
// In scared mode, enemies move 4x faster than player
if (scaredMode) {
self.y += self.speed * 4;
} else {
self.y += self.speed;
}
// Check if close enough to chase player
var distanceToPlayer = Math.abs(player.y - self.y);
self.chasingPlayer = distanceToPlayer < self.detectionRange;
// Update behavior based on proximity to player
if (self.chasingPlayer) {
// Chase every 60 frames when close (or faster in scared mode)
if (LK.ticks % (scaredMode ? 30 : 60) === 0) {
self.chase();
}
// Speed up when chasing
self.speed = Math.min(GAME_SPEED * 1.1, MAX_GAME_SPEED);
} else {
// Random swerving when not chasing
self.swerveTimer++;
if (self.swerveTimer >= self.swerveInterval) {
self.swerve();
self.swerveTimer = 0;
// Randomize next swerve interval
self.swerveInterval = 90 + Math.floor(Math.random() * 90);
}
// Normal speed when not chasing
self.speed = GAME_SPEED * 0.8;
}
// Reset enemy if it goes off screen
if (self.y > 2732 + graphics.height) {
self.y = -300;
self.swerveTimer = 0;
}
};
return self;
});
var Obstacle = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'car';
self.lane = Math.floor(Math.random() * 3); // Random lane (0, 1, 2)
self.speed = GAME_SPEED;
var assetType;
switch (self.type) {
case 'car':
assetType = 'car';
break;
case 'pedestrian':
assetType = 'pedestrian';
break;
case 'pothole':
assetType = 'pothole';
break;
case 'construction':
assetType = 'construction';
break;
default:
assetType = 'car';
}
var graphics = self.attachAsset(assetType, {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
self.y += self.speed;
// Mark for removal if off screen
if (self.y > 2732 + graphics.height) {
self.markedForRemoval = true;
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Player state
self.lane = 1; // Middle lane
self.isInvincible = false;
self.invincibleTimer = null;
self.isFallen = false;
self.fallTimer = null;
// Fall over animation
self.fallOver = function () {
if (self.isFallen) {
return;
} // Already fallen
self.isFallen = true;
// Stop any ongoing tweens
tween.stop(self, {
rotation: true
});
// Play fall animation
tween(playerGraphics, {
rotation: Math.PI / 2 // 90 degrees - fall on side
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Show game over immediately
LK.showGameOver();
}
});
};
// Get back up animation
self.getBackUp = function () {
self.isFallen = false;
if (self.fallTimer) {
LK.clearTimeout(self.fallTimer);
self.fallTimer = null;
}
// Play get back up animation
tween(playerGraphics, {
rotation: 0 // Upright position
}, {
duration: 300,
easing: tween.easeOut
});
};
// Move player to lane
self.moveToLane = function (laneIndex) {
if (laneIndex < 0 || laneIndex > 2 || self.isFallen) {
return;
}
self.lane = laneIndex;
var targetX = LANE_POSITIONS[laneIndex];
tween.stop(self, {
x: true
});
tween(self, {
x: targetX
}, {
duration: self.moveSpeed || 150,
// Use moveSpeed if set (for scared mode)
easing: tween.easeOut
});
};
// Make player invincible for a duration
self.setInvincible = function (duration) {
self.isInvincible = true;
playerGraphics.tint = 0x2ECC71; // Green tint
if (self.invincibleTimer) {
LK.clearTimeout(self.invincibleTimer);
}
self.invincibleTimer = LK.setTimeout(function () {
self.isInvincible = false;
playerGraphics.tint = 0xFFFFFF; // Reset tint
}, duration);
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
self.lane = Math.floor(Math.random() * 3); // Random lane (0, 1, 2)
self.speed = GAME_SPEED;
var graphics = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
// Add pulsing animation
function pulse() {
tween(graphics, {
scale: 1.2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(graphics, {
scale: 1.0
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: pulse
});
}
});
}
pulse();
self.update = function () {
self.y += self.speed;
// Mark for removal if off screen
if (self.y > 2732 + graphics.height) {
self.markedForRemoval = true;
}
};
return self;
});
var SettingsMenu = Container.expand(function () {
var self = Container.call(this);
// Load settings from storage (with defaults)
var settings = {
sound: storage.sound !== undefined ? storage.sound : true,
sfx: storage.sfx !== undefined ? storage.sfx : true,
language: storage.language || 'en',
testCodes: storage.testCodes || false,
debug: storage.debug || false
};
// Create background panel
var panel = LK.getAsset('construction', {
anchorX: 0.5,
anchorY: 0.5,
width: 1200,
height: 1100,
tint: 0x333333
});
self.addChild(panel);
// Create title
var title = new Text2('SETTINGS', {
size: 100,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
title.anchor.set(0.5, 0.5);
title.y = -400;
self.addChild(title);
// Helper function to create toggle buttons
function createToggleButton(labelText, _isOn, y, callback) {
var container = new Container();
container.y = y;
self.addChild(container);
// Label
var label = new Text2(labelText, {
size: 70,
fill: 0xFFFFFF
});
label.anchor.set(0, 0.5);
label.x = -450;
container.addChild(label);
// Toggle button background
var toggleBg = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 100,
tint: 0x666666
});
toggleBg.x = 350;
container.addChild(toggleBg);
// Toggle indicator
var toggleIndicator = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80,
tint: _isOn ? 0x00CC00 : 0xCC0000
});
toggleIndicator.x = 350 + (_isOn ? 40 : -40);
container.addChild(toggleIndicator);
// Status text
var statusText = new Text2(_isOn ? "ON" : "OFF", {
size: 50,
fill: 0xFFFFFF
});
statusText.anchor.set(0.5, 0.5);
statusText.x = 350;
container.addChild(statusText);
// Make toggle interactive
toggleBg.interactive = true;
toggleIndicator.interactive = true;
statusText.interactive = true;
// Toggle functionality
function toggle() {
// If this is test codes toggle or debug toggle and turning it on, verify developer name
if ((labelText === "Test Codes" || labelText === "Debug Mode") && !_isOn) {
var updateInput = function updateInput(key) {
if (key === "DEL") {
currentInput = currentInput.slice(0, -1);
} else {
currentInput += key;
}
inputText.setText(currentInput);
}; // Function to check developer name and close dialog
var checkAndClose = function checkAndClose() {
if (currentInput.toLowerCase() === "chazlin") {
// Correct developer name
inputContainer.destroy();
// Toggle on
_isOn = true;
toggleIndicator.tint = 0x00CC00;
toggleIndicator.x = 350 + 40;
statusText.setText("ON");
if (callback) {
callback(true);
}
} else {
// Wrong developer name - shake dialog
tween(dialogBg, {
x: 10
}, {
duration: 50,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(dialogBg, {
x: -10
}, {
duration: 50,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(dialogBg, {
x: 0
}, {
duration: 50,
easing: tween.easeInOut
});
}
});
}
});
// Clear input
currentInput = "";
inputText.setText("");
}
};
// Create input dialog for developer name
var inputContainer = new Container();
inputContainer.x = 0;
inputContainer.y = 0;
self.addChild(inputContainer);
// Dialog background
var dialogBg = LK.getAsset('construction', {
anchorX: 0.5,
anchorY: 0.5,
width: 800,
height: 400,
tint: 0x444444
});
inputContainer.addChild(dialogBg);
// Prompt text
var promptText = new Text2("Enter developer name:", {
size: 60,
fill: 0xFFFFFF
});
promptText.anchor.set(0.5, 0.5);
promptText.y = -100;
inputContainer.addChild(promptText);
// Input field background
var inputFieldBg = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 600,
height: 80,
tint: 0x666666
});
inputFieldBg.y = 0;
inputContainer.addChild(inputFieldBg);
// Input text (initially empty)
var inputText = new Text2("", {
size: 50,
fill: 0xFFFFFF
});
inputText.anchor.set(0.5, 0.5);
inputText.y = 0;
inputContainer.addChild(inputText);
// Create keyboard buttons
var keys = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
var keyButtons = [];
var currentInput = "";
// Function to update input text
for (var i = 0; i < keys.length; i++) {
var keyButton = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 40,
tint: 0x888888
});
var keyLabel = new Text2(keys[i], {
size: 25,
fill: 0xFFFFFF
});
keyLabel.anchor.set(0.5, 0.5);
// Position keys in rows
var row = Math.floor(i / 13);
var col = i % 13;
keyButton.x = (col - 6) * 45;
keyButton.y = 100 + row * 45;
keyLabel.x = keyButton.x;
keyLabel.y = keyButton.y;
inputContainer.addChild(keyButton);
inputContainer.addChild(keyLabel);
// Make button interactive
keyButton.interactive = true;
keyLabel.interactive = true;
// Capture current key for closure
(function (currentKey) {
keyButton.down = keyLabel.down = function () {
updateInput(currentKey);
};
})(keys[i]);
}
// Add delete button
var delButton = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 100,
height: 40,
tint: 0xCC4444
});
delButton.x = 230;
delButton.y = 100;
inputContainer.addChild(delButton);
var delLabel = new Text2("DEL", {
size: 25,
fill: 0xFFFFFF
});
delLabel.anchor.set(0.5, 0.5);
delLabel.x = delButton.x;
delLabel.y = delButton.y;
inputContainer.addChild(delLabel);
delButton.interactive = true;
delLabel.interactive = true;
delButton.down = delLabel.down = function () {
updateInput("DEL");
};
// Add submit button
var submitButton = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 150,
height: 60,
tint: 0x44CC44
});
submitButton.y = 180;
inputContainer.addChild(submitButton);
var submitLabel = new Text2("Submit", {
size: 30,
fill: 0xFFFFFF
});
submitLabel.anchor.set(0.5, 0.5);
submitLabel.y = submitButton.y;
inputContainer.addChild(submitLabel);
submitButton.interactive = true;
submitLabel.interactive = true;
submitButton.down = submitLabel.down = checkAndClose;
// Add cancel button
var cancelButton = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 150,
height: 60,
tint: 0xCC4444
});
cancelButton.x = -200;
cancelButton.y = 180;
inputContainer.addChild(cancelButton);
var cancelLabel = new Text2("Cancel", {
size: 30,
fill: 0xFFFFFF
});
cancelLabel.anchor.set(0.5, 0.5);
cancelLabel.x = cancelButton.x;
cancelLabel.y = cancelButton.y;
inputContainer.addChild(cancelLabel);
cancelButton.interactive = true;
cancelLabel.interactive = true;
cancelButton.down = cancelLabel.down = function () {
inputContainer.destroy();
};
} else {
// Normal toggle behavior for other settings
_isOn = !_isOn;
toggleIndicator.tint = _isOn ? 0x00CC00 : 0xCC0000;
toggleIndicator.x = 350 + (_isOn ? 40 : -40);
statusText.setText(_isOn ? "ON" : "OFF");
if (callback) {
callback(_isOn);
}
}
}
toggleBg.down = toggleIndicator.down = statusText.down = toggle;
return {
container: container,
toggle: toggle,
isOn: function isOn() {
return _isOn;
}
};
}
// Helper function to create language selector
function createLanguageSelector(y) {
var container = new Container();
container.y = y;
self.addChild(container);
// Label
var label = new Text2("Language", {
size: 70,
fill: 0xFFFFFF
});
label.anchor.set(0, 0.5);
label.x = -450;
container.addChild(label);
// Language options
var languages = ["English", "Welsh"];
var currentLang = settings.language === "cy" ? 1 : 0;
// Create language button
var langButton = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 100,
tint: 0x4488FF
});
langButton.x = 350;
container.addChild(langButton);
// Language text
var langText = new Text2(languages[currentLang], {
size: 50,
fill: 0xFFFFFF
});
langText.anchor.set(0.5, 0.5);
langText.x = 350;
container.addChild(langText);
// Make language button interactive
langButton.interactive = true;
langText.interactive = true;
// Toggle functionality
function toggle() {
currentLang = (currentLang + 1) % languages.length;
langText.setText(languages[currentLang]);
settings.language = currentLang === 0 ? "en" : "cy";
storage.language = settings.language;
}
langButton.down = langText.down = toggle;
return container;
}
// Create setting toggles
var soundToggle = createToggleButton("Music", settings.sound, -300, function (isOn) {
settings.sound = isOn;
storage.sound = isOn;
if (isOn) {
LK.playMusic('gameMusic');
} else {
LK.stopMusic();
}
});
var sfxToggle = createToggleButton("Sound Effects", settings.sfx, -200, function (isOn) {
settings.sfx = isOn;
storage.sfx = isOn;
});
var testCodesToggle = createToggleButton("Test Codes", settings.testCodes, -100, function (isOn) {
settings.testCodes = isOn;
storage.testCodes = isOn;
});
// Add debug toggle
var debugToggle = createToggleButton("Debug Mode", settings.debug || false, 0, function (isOn) {
settings.debug = isOn;
storage.debug = isOn;
});
// Create language selector
var languageSelector = createLanguageSelector(0);
// Create leaderboard button
var leaderboardBtn = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 600,
height: 120,
tint: 0x4444FF
});
leaderboardBtn.y = 100;
self.addChild(leaderboardBtn);
var leaderboardText = new Text2("Leaderboard", {
size: 70,
fill: 0xFFFFFF
});
leaderboardText.anchor.set(0.5, 0.5);
leaderboardText.y = 100;
self.addChild(leaderboardText);
// Leaderboard button functionality
leaderboardBtn.interactive = true;
leaderboardText.interactive = true;
leaderboardBtn.down = leaderboardText.down = function () {
// Using LK.showLeaderboard to display the leaderboard (corrected syntax)
LK.showLeaderboard();
};
// Create back button
var backBtn = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 120,
tint: 0xCC4444
});
backBtn.y = 250;
self.addChild(backBtn);
var backText = new Text2("Back", {
size: 70,
fill: 0xFFFFFF
});
backText.anchor.set(0.5, 0.5);
backText.y = 250;
self.addChild(backText);
// Back button functionality
backBtn.interactive = true;
backText.interactive = true;
backBtn.down = backText.down = function () {
self.visible = false;
startMenu.visible = true;
};
// Public method to apply settings
self.applySettings = function () {
// Apply sound settings
if (!settings.sound) {
LK.stopMusic();
}
};
// Method to check if SFX is enabled
self.isSfxEnabled = function () {
return settings.sfx;
};
return self;
});
var StartMenu = Container.expand(function () {
var self = Container.call(this);
// Create background panel
var panel = LK.getAsset('construction', {
anchorX: 0.5,
anchorY: 0.5,
width: 1200,
height: 1000,
// Increased height for settings button
tint: 0x333333
});
self.addChild(panel);
// Create title
var title = new Text2('LANE RIDER', {
size: 180,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
title.anchor.set(0.5, 0.5);
title.y = -250; // Adjusted position
self.addChild(title);
// Create normal mode button (now "Average Joe")
var normalBtn = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 700,
height: 180,
tint: 0xFF8C00
});
normalBtn.y = 0; // Adjusted position
self.addChild(normalBtn);
var normalText = new Text2('Average Joe', {
size: 90,
fill: 0xFFFFFF
});
normalText.anchor.set(0.5, 0.5);
normalText.y = 0; // Adjusted position
self.addChild(normalText);
// Create easy mode button
var easyBtn = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 700,
height: 180,
tint: 0x00CC00
});
easyBtn.y = 200; // Adjusted position
self.addChild(easyBtn);
var easyText = new Text2('Easy Mode', {
size: 90,
fill: 0xFFFFFF
});
easyText.anchor.set(0.5, 0.5);
easyText.y = 200; // Adjusted position
self.addChild(easyText);
// Create hard mode button
var hardBtn = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 700,
height: 180,
tint: 0xFF0000
});
hardBtn.y = 400; // Adjusted position
self.addChild(hardBtn);
var hardText = new Text2('Hard Mode', {
size: 90,
fill: 0xFFFFFF
});
hardText.anchor.set(0.5, 0.5);
hardText.y = 400; // Adjusted position
self.addChild(hardText);
// Create scared mode button
var scaredBtn = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 700,
height: 180,
tint: 0x800080 // Purple tint
});
scaredBtn.y = 600; // Adjusted position
self.addChild(scaredBtn);
var scaredText = new Text2('NO DONT DO IT IM SCARED', {
size: 50,
fill: 0xFFFFFF
});
scaredText.anchor.set(0.5, 0.5);
scaredText.y = 600; // Adjusted position
self.addChild(scaredText);
// Create settings button
var settingsBtn = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: 700,
height: 180,
tint: 0x00AAAA // Teal color
});
settingsBtn.y = 800; // Position below other buttons
self.addChild(settingsBtn);
var settingsText = new Text2('Settings', {
size: 90,
fill: 0xFFFFFF
});
settingsText.anchor.set(0.5, 0.5);
settingsText.y = 800;
self.addChild(settingsText);
// Add interaction for normal mode
normalBtn.interactive = true;
normalBtn.down = normalText.down = function () {
self.visible = false;
easyMode = false;
hardMode = false;
startGame();
};
// Add interaction for easy mode
easyBtn.interactive = true;
easyBtn.down = easyText.down = function () {
self.visible = false;
easyMode = true;
hardMode = false;
startGame();
};
// Add interaction for hard mode
hardBtn.interactive = true;
hardBtn.down = hardText.down = function () {
self.visible = false;
easyMode = false;
hardMode = true;
scaredMode = false;
startGame();
};
// Add interaction for scared mode
scaredBtn.interactive = true;
scaredBtn.down = scaredText.down = function () {
self.visible = false;
easyMode = false;
hardMode = false;
scaredMode = true;
startGame();
};
// Add interaction for settings button
settingsBtn.interactive = true;
settingsText.interactive = true;
settingsBtn.down = settingsText.down = function () {
self.visible = false;
settingsMenu.visible = true;
};
return self;
});
var WinningZone = Container.expand(function () {
var self = Container.call(this);
self.width = 2048;
self.height = 300;
// Create background for winning zone
var background = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: self.width,
height: self.height,
tint: 0xFFD700 // Gold color
});
self.addChild(background);
// Create winning text
var winText = new Text2(scaredMode ? welshTranslations['FINISH LINE'] : 'FINISH LINE', {
size: 100,
fill: 0x000000
});
winText.anchor.set(0.5, 0.5);
self.addChild(winText);
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x7DCEA0 // Greenish background
});
/****
* Game Code
****/
// Constants
var LANE_WIDTH = 2048 / 3;
var LANE_POSITIONS = [LANE_WIDTH / 2, LANE_WIDTH + LANE_WIDTH / 2, 2 * LANE_WIDTH + LANE_WIDTH / 2];
var MIN_OBSTACLE_INTERVAL = 60;
var MAX_OBSTACLE_INTERVAL = 120;
var GAME_SPEED = 6;
var MAX_GAME_SPEED = 15;
var SPEED_INCREASE_RATE = 0.0005;
var OBSTACLE_TYPES = ['car', 'pedestrian', 'pothole', 'construction'];
var POWERUP_CHANCE = 0.1; // 10% chance per obstacle spawn
var INVINCIBLE_DURATION = 5000; // 5 seconds (base value)
// Game variables
var player;
var enemy;
var secondEnemy; // Second enemy for hard mode
var obstacles = [];
var powerups = [];
var lanes = [];
var obstacleTimer = 0;
var nextObstacleTime = 100;
var distance = 0;
var highScore = storage.highScore || 0;
var isGameStarted = false;
var gameStartTime = 0;
var currentTime = 0;
var easyMode = false;
var hardMode = false;
var scaredMode = false; // New scared mode
var startMenu;
var settingsMenu;
var winningZone;
var winningDistance = 0; // Distance required to win based on difficulty
var redOverlay; // Reddish overlay for scared mode
// Welsh translations for scared mode
var welshTranslations = {
'Distance': 'Pellter',
'Best': 'Gorau',
'Time': 'Amser',
'FINISH LINE': 'LLINELL DERFYN',
'Easy Mode': 'Modd Hawdd',
'Average Joe': 'Joe Cyffredin',
'Hard Mode': 'Modd Caled',
'LANE RIDER': 'RIDER LÔN',
'NO DONT DO IT IM SCARED': 'PEIDIWCH Â GWNEUD OFNAIS WYFI'
};
// GUI elements
var scoreTxt;
var highScoreTxt;
var timeTxt;
var startInstructions;
// Initialize game UI
function initUI() {
// Create score display
scoreTxt = new Text2('Distance: 0', {
size: 70,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.visible = false;
LK.gui.top.addChild(scoreTxt);
// Create high score display
highScoreTxt = new Text2('Best: ' + highScore, {
size: 50,
fill: 0xFFFFFF
});
highScoreTxt.anchor.set(0.5, 0);
highScoreTxt.y = 80;
highScoreTxt.visible = false;
LK.gui.top.addChild(highScoreTxt);
// Create stopwatch display
timeTxt = new Text2('Time: 0.0s', {
size: 50,
fill: 0xFFFFFF
});
timeTxt.anchor.set(0.5, 0);
timeTxt.y = 150;
timeTxt.visible = false;
LK.gui.top.addChild(timeTxt);
// Create start instructions
startInstructions = new Text2('Tap to start\nSwipe left/right to change lanes', {
size: 80,
fill: 0xFFFFFF
});
startInstructions.anchor.set(0.5, 0.5);
startInstructions.visible = false;
LK.gui.center.addChild(startInstructions);
}
// Initialize game world
function initWorld() {
// Create the three lanes
for (var i = 0; i < 3; i++) {
var lane = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
width: 20,
height: 2732
});
lane.x = (i + 1) * LANE_WIDTH;
game.addChild(lane);
lanes.push(lane);
}
// Create player
player = new Player();
player.x = LANE_POSITIONS[1]; // Start in middle lane
player.y = 2732 - 300; // Position near bottom of screen
game.addChild(player);
// Hide player initially
player.visible = false;
// Create enemy chaser
enemy = new EnemyChaser();
enemy.x = LANE_POSITIONS[1]; // Start in middle lane
enemy.y = -500; // Start above screen
game.addChild(enemy);
// Hide enemy initially
enemy.visible = false;
// Create second enemy chaser for hard mode
secondEnemy = new EnemyChaser();
secondEnemy.x = LANE_POSITIONS[0]; // Start in left lane
secondEnemy.y = -800; // Start above screen but offset from first enemy
game.addChild(secondEnemy);
// Hide second enemy initially
secondEnemy.visible = false;
// Create and display start menu
startMenu = new StartMenu();
startMenu.x = 2048 / 2;
startMenu.y = 2732 / 2;
game.addChild(startMenu);
// Create settings menu (hidden initially)
settingsMenu = new SettingsMenu();
settingsMenu.x = 2048 / 2;
settingsMenu.y = 2732 / 2;
settingsMenu.visible = false;
game.addChild(settingsMenu);
}
// Start the game
function startGame() {
isGameStarted = true;
player.visible = true;
// Show appropriate enemies based on game mode
enemy.visible = !easyMode && !scaredMode;
secondEnemy.visible = hardMode;
startInstructions.visible = true;
// Show UI elements
scoreTxt.visible = true;
highScoreTxt.visible = true;
timeTxt.visible = true;
// Reset game state
obstacles.forEach(function (obstacle) {
obstacle.destroy();
});
obstacles = [];
powerups.forEach(function (powerup) {
powerup.destroy();
});
powerups = [];
distance = 0;
// Handle scared mode speeds
if (scaredMode) {
GAME_SPEED = 3; // Half the normal speed
player.moveSpeed = 300; // Double the lane change time (slower movement)
// Make player start with invincibility to give them a better chance
player.setInvincible(10000); // 10 seconds of initial invincibility
// Create red overlay if it doesn't exist
if (!redOverlay) {
redOverlay = LK.getAsset('lane', {
anchorX: 0,
anchorY: 0,
width: 2048,
height: 2732,
tint: 0xFF0000
});
redOverlay.alpha = 0.3;
game.addChild(redOverlay);
} else {
redOverlay.visible = true;
}
// Translate text to Welsh for scared mode
scoreTxt.setText(welshTranslations['Distance'] + ': 0');
highScoreTxt.setText(welshTranslations['Best'] + ': ' + highScore);
timeTxt.setText(welshTranslations['Time'] + ': 0.0s');
startInstructions.setText(welshTranslations['Tap to start'] + '\n' + welshTranslations['Swipe left/right to change lanes']);
} else {
GAME_SPEED = hardMode ? 8 : 6; // Higher starting speed in hard mode
player.moveSpeed = 150; // Normal movement speed
// Hide red overlay if it exists
if (redOverlay) {
redOverlay.visible = false;
}
// Reset text to English
scoreTxt.setText('Distance: 0');
highScoreTxt.setText('Best: ' + highScore);
timeTxt.setText('Time: 0.0s');
startInstructions.setText('Tap to start\nSwipe left/right to change lanes');
}
obstacleTimer = 0;
nextObstacleTime = hardMode ? 70 : scaredMode ? 200 : 100; // Adjust obstacle intervals
// Set winning distance based on difficulty
if (hardMode) {
winningDistance = 1000;
} else if (easyMode) {
winningDistance = 5000;
} else if (scaredMode) {
winningDistance = 3000; // Scared mode winning distance
} else {
winningDistance = 2000; // Average Joe mode
}
// Reset enemy positions
enemy.y = -500;
enemy.lane = 1;
enemy.x = LANE_POSITIONS[1];
enemy.swerveTimer = 0;
// Reset second enemy
secondEnemy.y = -800;
secondEnemy.lane = 0;
secondEnemy.x = LANE_POSITIONS[0];
secondEnemy.swerveTimer = 50; // Offset timer so enemies don't swerve at the same time
// Reset stopwatch
gameStartTime = Date.now();
currentTime = 0;
// Play different background music based on game mode if enabled
if (storage.sound !== false) {
if (scaredMode) {
// Play eerie music for scared mode
LK.playMusic('scaredMusic', {
fade: {
start: 0,
end: 0.5,
duration: 500
}
});
} else if (hardMode) {
// Play intense music for hard mode
LK.playMusic('hardMusic', {
fade: {
start: 0,
end: 1,
duration: 300
}
});
} else if (easyMode) {
// Play relaxed music for easy mode
LK.playMusic('easyMusic', {
fade: {
start: 0,
end: 0.7,
duration: 400
}
});
} else {
// Play standard music for normal/average joe mode
LK.playMusic('gameMusic');
}
}
// Apply settings
settingsMenu.applySettings();
}
// Spawn a new obstacle
function spawnObstacle() {
// Choose random obstacle type
var type = OBSTACLE_TYPES[Math.floor(Math.random() * OBSTACLE_TYPES.length)];
var obstacle = new Obstacle(type);
// Ensure obstacle is in a valid lane
obstacle.x = LANE_POSITIONS[obstacle.lane];
obstacle.y = -200; // Start above screen
obstacles.push(obstacle);
game.addChild(obstacle);
// In scared mode, spawn more obstacles
if (scaredMode && Math.random() < 0.8) {
// 80% chance of additional obstacle in scared mode
for (var i = 0; i < 2; i++) {
// Two more obstacles
var extraType = OBSTACLE_TYPES[Math.floor(Math.random() * OBSTACLE_TYPES.length)];
var extraObstacle = new Obstacle(extraType);
// Try to find an available lane
var freeLanes = [0, 1, 2].filter(function (lane) {
return !obstacles.some(function (obs) {
return obs.lane === lane && Math.abs(obs.y + 200) < 300;
});
});
if (freeLanes.length > 0) {
extraObstacle.lane = freeLanes[Math.floor(Math.random() * freeLanes.length)];
extraObstacle.x = LANE_POSITIONS[extraObstacle.lane];
extraObstacle.y = -200 - i * 150; // Stagger obstacles
obstacles.push(extraObstacle);
game.addChild(extraObstacle);
}
}
}
// In hard mode, possibly spawn a second obstacle in a different lane
else if (hardMode && Math.random() < 0.6) {
// 60% chance of additional obstacle
var secondType = OBSTACLE_TYPES[Math.floor(Math.random() * OBSTACLE_TYPES.length)];
var secondObstacle = new Obstacle(secondType);
// Make sure it's in a different lane
do {
secondObstacle.lane = Math.floor(Math.random() * 3);
} while (secondObstacle.lane === obstacle.lane);
secondObstacle.x = LANE_POSITIONS[secondObstacle.lane];
secondObstacle.y = -350; // Offset from first obstacle
obstacles.push(secondObstacle);
game.addChild(secondObstacle);
}
// Possibly spawn a power-up
if (Math.random() < (scaredMode ? POWERUP_CHANCE * 2 : POWERUP_CHANCE)) {
// 2x more powerups in scared mode
spawnPowerUp();
}
// Set next obstacle spawn time
nextObstacleTime = MIN_OBSTACLE_INTERVAL + Math.floor(Math.random() * (MAX_OBSTACLE_INTERVAL - MIN_OBSTACLE_INTERVAL));
// Reduce spawn interval as game progresses
nextObstacleTime = Math.max(nextObstacleTime * (1 - distance / 50000), MIN_OBSTACLE_INTERVAL);
// In scared mode, increase obstacle spawn time
if (scaredMode) {
nextObstacleTime = Math.floor(nextObstacleTime * 2); // Double the time between obstacles
}
// In hard mode, reduce obstacle spawn time further
else if (hardMode) {
nextObstacleTime = Math.floor(nextObstacleTime * 0.7);
}
}
// Spawn a power-up
function spawnPowerUp() {
var powerup = new PowerUp();
// Make sure power-up is not in same lane as last obstacle
var lastObstacle = obstacles[obstacles.length - 1];
do {
powerup.lane = Math.floor(Math.random() * 3);
} while (powerup.lane === lastObstacle.lane);
powerup.x = LANE_POSITIONS[powerup.lane];
powerup.y = -500; // Start above screen, after the obstacle
powerups.push(powerup);
game.addChild(powerup);
}
// Check collisions between player and obstacles/powerups
function checkCollisions() {
// Check obstacle collisions
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
if (player.lane === obstacle.lane && Math.abs(player.y - obstacle.y) < 100 && !player.isInvincible && !player.isFallen) {
// Collision detected - Play crash sound
if (settingsMenu.isSfxEnabled()) {
LK.getSound('crash').play();
}
// Flash screen to indicate collision
LK.effects.flashScreen(0xFF0000, 500);
// Reset distance counter
distance = 0;
scoreTxt.setText(scaredMode ? welshTranslations['Distance'] + ': 0' : 'Distance: 0');
// Make player fall over
player.fallOver();
return;
}
}
// Check enemy collision (skip in easy mode)
if (!easyMode && player.lane === enemy.lane && Math.abs(player.y - enemy.y) < 100 && !player.isInvincible && !player.isFallen) {
// Collision detected - Play crash sound
if (settingsMenu.isSfxEnabled()) {
LK.getSound('crash').play();
}
// Flash screen to indicate collision
LK.effects.flashScreen(0xFF0000, 500);
// Reset distance counter
distance = 0;
scoreTxt.setText(scaredMode ? welshTranslations['Distance'] + ': 0' : 'Distance: 0');
// Make player fall over
player.fallOver();
return;
}
// Check second enemy collision (hard mode only)
if (hardMode && player.lane === secondEnemy.lane && Math.abs(player.y - secondEnemy.y) < 100 && !player.isInvincible && !player.isFallen) {
// Collision detected - Play crash sound
if (settingsMenu.isSfxEnabled()) {
LK.getSound('crash').play();
}
// Flash screen to indicate collision
LK.effects.flashScreen(0xFF0000, 500);
// Reset distance counter
distance = 0;
scoreTxt.setText(scaredMode ? welshTranslations['Distance'] + ': 0' : 'Distance: 0');
// Make player fall over
player.fallOver();
return;
}
// Check power-up collisions
for (var j = powerups.length - 1; j >= 0; j--) {
var powerup = powerups[j];
if (player.lane === powerup.lane && Math.abs(player.y - powerup.y) < 100) {
// Collect power-up
if (settingsMenu.isSfxEnabled()) {
LK.getSound('powerup').play();
}
// Apply power-up effect (invincibility) - longer in scared mode
player.setInvincible(scaredMode ? INVINCIBLE_DURATION * 3 : INVINCIBLE_DURATION); // 3x longer invincibility in scared mode
// Remove power-up
powerup.destroy();
powerups.splice(j, 1);
}
}
}
// Handle touch events for swiping
var touchStartX = 0;
game.down = function (x, y) {
if (!isGameStarted) {
return;
}
touchStartX = x;
};
game.up = function (x, y) {
if (!isGameStarted) {
return;
}
var swipeDistance = x - touchStartX;
// Determine swipe direction and move player
if (Math.abs(swipeDistance) > 50) {
// Minimum swipe distance threshold
if (swipeDistance > 0) {
// Swipe right
player.moveToLane(Math.min(player.lane + 1, 2));
} else {
// Swipe left
player.moveToLane(Math.max(player.lane - 1, 0));
}
}
};
// Main game update loop
game.update = function () {
if (!isGameStarted) {
return;
}
// Update stopwatch
currentTime = (Date.now() - gameStartTime) / 1000;
if (scaredMode) {
timeTxt.setText(welshTranslations['Time'] + ': ' + currentTime.toFixed(1) + 's');
// Increment distance at half the rate in scared mode
distance += GAME_SPEED / 20;
scoreTxt.setText(welshTranslations['Distance'] + ': ' + Math.floor(distance));
} else {
timeTxt.setText('Time: ' + currentTime.toFixed(1) + 's');
// Normal distance increment
distance += GAME_SPEED / 10;
scoreTxt.setText('Distance: ' + Math.floor(distance));
}
// Check for win condition
if (distance >= winningDistance) {
// Player has reached the required distance, show win
LK.showYouWin();
// Update high score if needed
if (distance > highScore) {
highScore = Math.floor(distance);
storage.highScore = highScore;
highScoreTxt.setText('Best: ' + highScore);
}
return;
}
// Display winning zone when approaching the finish
if (distance >= (scaredMode ? winningDistance - 1000 : winningDistance - 500) && !winningZone) {
winningZone = new WinningZone();
winningZone.x = 2048 / 2;
winningZone.y = -300; // Start off-screen
game.addChild(winningZone);
}
// Move winning zone toward player as they approach finish
if (winningZone) {
winningZone.y += GAME_SPEED;
}
// Gradually increase game speed
GAME_SPEED = Math.min(GAME_SPEED + SPEED_INCREASE_RATE, MAX_GAME_SPEED);
// Spawn obstacles
obstacleTimer++;
if (obstacleTimer >= nextObstacleTime) {
spawnObstacle();
obstacleTimer = 0;
}
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
obstacle.speed = scaredMode ? GAME_SPEED * 2 : GAME_SPEED; // Double speed in scared mode
// Remove obstacles marked for removal
if (obstacle.markedForRemoval) {
obstacle.destroy();
obstacles.splice(i, 1);
}
}
// Update power-ups
for (var j = powerups.length - 1; j >= 0; j--) {
var powerup = powerups[j];
powerup.speed = scaredMode ? GAME_SPEED * 2 : GAME_SPEED; // Double speed in scared mode
// Remove power-ups marked for removal
if (powerup.markedForRemoval) {
powerup.destroy();
powerups.splice(j, 1);
}
}
// Update enemy chasers (based on game mode)
if (!easyMode) {
enemy.update();
// Update second enemy in hard mode
if (hardMode) {
secondEnemy.update();
}
}
// Check for collisions
checkCollisions();
};
// Initialize UI and game world
initUI();
initWorld();
A 2D, Pixalted Bike with one wheel in front and one at the back with a pixelated character on.. In-Game asset. 2d. High contrast. No shadows
A gem, a diamond with an emerald colour. In-Game asset. 2d. High contrast. No shadows
A pedestrian, walking down the street facing forwards. In-Game asset. 2d. High contrast. No shadows
A tarmac road, with 3 lanes, matching up with actual game lanes. In-Game asset. 2d. High contrast. No shadows
A person running backwards. In-Game asset. 2d. High contrast. No shadows
Make a pothole like hole in the road. In-Game asset. 2d. High contrast. No shadows