/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Obstacle = Container.expand(function (lane, distance) {
var self = Container.call(this);
var graphics = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 1
});
self.lane = lane;
self.distance = distance;
self.update = function () {
self.x = 100 + self.distance / raceDistance * 1800;
};
return self;
});
var Runner = Container.expand(function (color, lane) {
var self = Container.call(this);
var graphics = self.attachAsset(color, {
anchorX: 0.5,
anchorY: 1
});
self.speed = 0;
self.maxSpeed = self.isPlayer ? 14 : 12; // Higher max speed for player
self.lane = lane;
self.distance = 0;
self.isJumping = false;
self.jumpHeight = 0;
self.isPlayer = false;
self.update = function () {
if (gameState !== 'racing') return;
// Handle jumping - graphics position updated by jumpHeight
if (self.isJumping) {
graphics.y = -self.jumpHeight;
}
// Move forward
self.distance += self.speed;
self.x = 100 + self.distance / raceDistance * 1800;
// Check for obstacles
if (!self.isJumping && self.jumpHeight === 0) {
for (var i = 0; i < obstacles.length; i++) {
var obs = obstacles[i];
if (obs.lane === self.lane && Math.abs(self.distance - obs.distance) < 50) {
// Hit obstacle
self.speed *= 0.3;
break;
}
}
}
};
self.jump = function () {
if (!self.isJumping && self.jumpHeight === 0) {
self.isJumping = true;
self.jumpHeight = 0;
LK.getSound('jump').play();
// Use tween to animate jump - go up then down over 0.5 seconds total
tween(self, {
jumpHeight: 100
}, {
duration: 250,
easing: tween.easeOut,
onFinish: function onFinish() {
// After reaching peak, tween back down
tween(self, {
jumpHeight: 0
}, {
duration: 250,
easing: tween.easeIn,
onFinish: function onFinish() {
self.isJumping = false;
}
});
}
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game variables
var gameState = 'tutorial'; // tutorial, ready, racing, finished
var currentRound = 1;
var raceDistance = 2000;
var runners = [];
var obstacles = [];
var playerRunner;
var tapCount = 0;
var lastTapTime = 0;
var raceStartTime = 0;
// UI Elements
var roundText = new Text2('Round 1', {
size: 80,
fill: 0x000000
});
roundText.anchor.set(0.5, 0);
LK.gui.top.addChild(roundText);
roundText.y = 100;
var speedText = new Text2('Speed: 0', {
size: 60,
fill: 0x000000
});
speedText.anchor.set(0, 0);
LK.gui.left.addChild(speedText);
speedText.x = 20;
speedText.y = -300;
var instructionText = new Text2('TAP TO SPRINT!', {
size: 100,
fill: 0xFF0000
});
instructionText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(instructionText);
instructionText.x = 0;
instructionText.y = 200;
// Tutorial text explaining controls
var tutorialText = new Text2('TAP SCREEN TO RUN FASTER\nTAP UPPER AREA TO JUMP OVER OBSTACLES', {
size: 60,
fill: 0x000000
});
tutorialText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(tutorialText);
tutorialText.x = 0;
tutorialText.y = -100;
// Continue text
var continueText = new Text2('TAP TO CONTINUE', {
size: 50,
fill: 0x666666
});
continueText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(continueText);
continueText.x = 0;
continueText.y = 400;
var positionText = new Text2('Position: 5/5', {
size: 60,
fill: 0x000000
});
positionText.anchor.set(1, 0);
LK.gui.topRight.addChild(positionText);
positionText.x = -20;
positionText.y = 150;
// Create stadium background
var stadium = game.addChild(LK.getAsset('stadium', {
anchorX: 0,
anchorY: 0
}));
stadium.x = 0;
stadium.y = 0;
// Create track
var trackY = 1500;
var track = game.addChild(LK.getAsset('track', {
anchorX: 0,
anchorY: 0.5
}));
track.x = 0;
track.y = trackY;
// Create lane lines
for (var i = 1; i < 5; i++) {
var lane = game.addChild(LK.getAsset('lane', {
anchorX: 0,
anchorY: 0.5
}));
lane.x = 0;
lane.y = trackY - 150 + i * 60;
}
// Create finish line
var finishLine = game.addChild(LK.getAsset('finish', {
anchorX: 0.5,
anchorY: 0.5
}));
finishLine.x = 1950;
finishLine.y = trackY;
function initializeRace() {
// Clear previous runners and obstacles
for (var i = 0; i < runners.length; i++) {
runners[i].destroy();
}
for (var j = 0; j < obstacles.length; j++) {
obstacles[j].destroy();
}
runners = [];
obstacles = [];
// Create runners
var colors = ['player', 'ai1', 'ai2', 'ai3', 'ai4'];
for (var k = 0; k < 5; k++) {
var runner = new Runner(colors[k], k);
runner.x = 100;
runner.y = trackY - 120 + k * 60;
if (k === 0) {
runner.isPlayer = true;
runner.maxSpeed = 14; // Set higher max speed for player
playerRunner = runner;
} else {
// Set random base speed for each AI runner that will persist this round
runner.baseSpeed = (3 + Math.random() * 2) * 1.22; // Random base speed between 3.66-6.1 (1.22x faster)
runner.maxSpeed = (runner.baseSpeed + 1 + Math.random() * 1) * 1.22; // Max speed based on base speed (1.22x faster)
}
runners.push(runner);
game.addChild(runner);
}
// Create obstacles for rounds 2+
if (currentRound >= 2) {
var obstacleCount = Math.min(currentRound, 5);
var usedLanes = [];
for (var l = 0; l < obstacleCount; l++) {
var obsLane;
// Keep trying random lanes until we find one that's not used
do {
obsLane = Math.floor(Math.random() * 5);
} while (usedLanes.indexOf(obsLane) !== -1);
usedLanes.push(obsLane);
var obsDistance = 800 + Math.random() * 800;
var obstacle = new Obstacle(obsLane, obsDistance);
obstacle.y = trackY - 120 + obsLane * 60;
obstacles.push(obstacle);
game.addChild(obstacle);
}
}
if (currentRound === 1) {
gameState = 'tutorial';
tutorialText.visible = true;
continueText.visible = true;
instructionText.visible = false;
} else {
gameState = 'ready';
tutorialText.visible = false;
continueText.visible = false;
instructionText.visible = true;
}
tapCount = 0;
roundText.setText('Round ' + currentRound);
}
function startRace() {
gameState = 'racing';
raceStartTime = LK.ticks;
instructionText.visible = false;
}
function updatePlayerSpeed() {
var currentTime = LK.ticks;
var timeSinceLastTap = currentTime - lastTapTime;
if (timeSinceLastTap > 30) {
// Decrease speed if not tapping
playerRunner.speed *= 0.95;
}
if (playerRunner.speed > playerRunner.maxSpeed) {
playerRunner.speed = playerRunner.maxSpeed;
}
speedText.setText('Speed: ' + Math.floor(playerRunner.speed));
}
function updateAI() {
for (var i = 1; i < runners.length; i++) {
var aiRunner = runners[i];
// Use consistent base speed for this round with small random variations
var roundBonus = currentRound * 0.2;
var targetSpeed = (aiRunner.baseSpeed + roundBonus) * 1.22;
// Add small random variation (±10%) to make movement feel natural
var variation = targetSpeed * (0.8 + Math.random() * 0.2);
aiRunner.speed = variation;
// Cap speed at the runner's max speed
if (aiRunner.speed > aiRunner.maxSpeed) {
aiRunner.speed = aiRunner.maxSpeed;
}
// AI obstacle avoidance
for (var j = 0; j < obstacles.length; j++) {
var obs = obstacles[j];
if (obs.lane === aiRunner.lane && Math.abs(aiRunner.distance - obs.distance) < 150 && Math.random() < 0.7) {
aiRunner.jump();
break;
}
}
}
}
function updatePositions() {
var sortedRunners = runners.slice().sort(function (a, b) {
return b.distance - a.distance;
});
var playerPosition = sortedRunners.indexOf(playerRunner) + 1;
positionText.setText('Position: ' + playerPosition + '/5');
}
function checkRaceEnd() {
var finishedRunners = [];
for (var i = 0; i < runners.length; i++) {
if (runners[i].distance >= raceDistance) {
finishedRunners.push(runners[i]);
}
}
if (finishedRunners.length > 0) {
gameState = 'finished';
// Sort by distance to determine winner
finishedRunners.sort(function (a, b) {
return b.distance - a.distance;
});
if (finishedRunners[0] === playerRunner) {
// Player won
currentRound++;
LK.setScore(currentRound - 1);
LK.setTimeout(function () {
initializeRace();
}, 2000);
} else {
// Player lost
LK.setTimeout(function () {
LK.showGameOver();
}, 2000);
}
}
}
// Touch controls
game.down = function (x, y, obj) {
if (gameState === 'tutorial') {
// Move from tutorial to ready state
gameState = 'ready';
tutorialText.visible = false;
continueText.visible = false;
instructionText.visible = true;
}
if (gameState === 'ready') {
startRace();
}
if (gameState === 'racing') {
tapCount++;
lastTapTime = LK.ticks;
// Increase player speed
playerRunner.speed += 0.8;
// Jump if tapping in upper screen area
if (y < 1200) {
playerRunner.jump();
}
LK.getSound('tap').play();
}
};
// Game update loop
game.update = function () {
if (gameState === 'racing') {
updatePlayerSpeed();
updateAI();
updatePositions();
checkRaceEnd();
}
};
// Initialize first race
initializeRace(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Obstacle = Container.expand(function (lane, distance) {
var self = Container.call(this);
var graphics = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 1
});
self.lane = lane;
self.distance = distance;
self.update = function () {
self.x = 100 + self.distance / raceDistance * 1800;
};
return self;
});
var Runner = Container.expand(function (color, lane) {
var self = Container.call(this);
var graphics = self.attachAsset(color, {
anchorX: 0.5,
anchorY: 1
});
self.speed = 0;
self.maxSpeed = self.isPlayer ? 14 : 12; // Higher max speed for player
self.lane = lane;
self.distance = 0;
self.isJumping = false;
self.jumpHeight = 0;
self.isPlayer = false;
self.update = function () {
if (gameState !== 'racing') return;
// Handle jumping - graphics position updated by jumpHeight
if (self.isJumping) {
graphics.y = -self.jumpHeight;
}
// Move forward
self.distance += self.speed;
self.x = 100 + self.distance / raceDistance * 1800;
// Check for obstacles
if (!self.isJumping && self.jumpHeight === 0) {
for (var i = 0; i < obstacles.length; i++) {
var obs = obstacles[i];
if (obs.lane === self.lane && Math.abs(self.distance - obs.distance) < 50) {
// Hit obstacle
self.speed *= 0.3;
break;
}
}
}
};
self.jump = function () {
if (!self.isJumping && self.jumpHeight === 0) {
self.isJumping = true;
self.jumpHeight = 0;
LK.getSound('jump').play();
// Use tween to animate jump - go up then down over 0.5 seconds total
tween(self, {
jumpHeight: 100
}, {
duration: 250,
easing: tween.easeOut,
onFinish: function onFinish() {
// After reaching peak, tween back down
tween(self, {
jumpHeight: 0
}, {
duration: 250,
easing: tween.easeIn,
onFinish: function onFinish() {
self.isJumping = false;
}
});
}
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game variables
var gameState = 'tutorial'; // tutorial, ready, racing, finished
var currentRound = 1;
var raceDistance = 2000;
var runners = [];
var obstacles = [];
var playerRunner;
var tapCount = 0;
var lastTapTime = 0;
var raceStartTime = 0;
// UI Elements
var roundText = new Text2('Round 1', {
size: 80,
fill: 0x000000
});
roundText.anchor.set(0.5, 0);
LK.gui.top.addChild(roundText);
roundText.y = 100;
var speedText = new Text2('Speed: 0', {
size: 60,
fill: 0x000000
});
speedText.anchor.set(0, 0);
LK.gui.left.addChild(speedText);
speedText.x = 20;
speedText.y = -300;
var instructionText = new Text2('TAP TO SPRINT!', {
size: 100,
fill: 0xFF0000
});
instructionText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(instructionText);
instructionText.x = 0;
instructionText.y = 200;
// Tutorial text explaining controls
var tutorialText = new Text2('TAP SCREEN TO RUN FASTER\nTAP UPPER AREA TO JUMP OVER OBSTACLES', {
size: 60,
fill: 0x000000
});
tutorialText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(tutorialText);
tutorialText.x = 0;
tutorialText.y = -100;
// Continue text
var continueText = new Text2('TAP TO CONTINUE', {
size: 50,
fill: 0x666666
});
continueText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(continueText);
continueText.x = 0;
continueText.y = 400;
var positionText = new Text2('Position: 5/5', {
size: 60,
fill: 0x000000
});
positionText.anchor.set(1, 0);
LK.gui.topRight.addChild(positionText);
positionText.x = -20;
positionText.y = 150;
// Create stadium background
var stadium = game.addChild(LK.getAsset('stadium', {
anchorX: 0,
anchorY: 0
}));
stadium.x = 0;
stadium.y = 0;
// Create track
var trackY = 1500;
var track = game.addChild(LK.getAsset('track', {
anchorX: 0,
anchorY: 0.5
}));
track.x = 0;
track.y = trackY;
// Create lane lines
for (var i = 1; i < 5; i++) {
var lane = game.addChild(LK.getAsset('lane', {
anchorX: 0,
anchorY: 0.5
}));
lane.x = 0;
lane.y = trackY - 150 + i * 60;
}
// Create finish line
var finishLine = game.addChild(LK.getAsset('finish', {
anchorX: 0.5,
anchorY: 0.5
}));
finishLine.x = 1950;
finishLine.y = trackY;
function initializeRace() {
// Clear previous runners and obstacles
for (var i = 0; i < runners.length; i++) {
runners[i].destroy();
}
for (var j = 0; j < obstacles.length; j++) {
obstacles[j].destroy();
}
runners = [];
obstacles = [];
// Create runners
var colors = ['player', 'ai1', 'ai2', 'ai3', 'ai4'];
for (var k = 0; k < 5; k++) {
var runner = new Runner(colors[k], k);
runner.x = 100;
runner.y = trackY - 120 + k * 60;
if (k === 0) {
runner.isPlayer = true;
runner.maxSpeed = 14; // Set higher max speed for player
playerRunner = runner;
} else {
// Set random base speed for each AI runner that will persist this round
runner.baseSpeed = (3 + Math.random() * 2) * 1.22; // Random base speed between 3.66-6.1 (1.22x faster)
runner.maxSpeed = (runner.baseSpeed + 1 + Math.random() * 1) * 1.22; // Max speed based on base speed (1.22x faster)
}
runners.push(runner);
game.addChild(runner);
}
// Create obstacles for rounds 2+
if (currentRound >= 2) {
var obstacleCount = Math.min(currentRound, 5);
var usedLanes = [];
for (var l = 0; l < obstacleCount; l++) {
var obsLane;
// Keep trying random lanes until we find one that's not used
do {
obsLane = Math.floor(Math.random() * 5);
} while (usedLanes.indexOf(obsLane) !== -1);
usedLanes.push(obsLane);
var obsDistance = 800 + Math.random() * 800;
var obstacle = new Obstacle(obsLane, obsDistance);
obstacle.y = trackY - 120 + obsLane * 60;
obstacles.push(obstacle);
game.addChild(obstacle);
}
}
if (currentRound === 1) {
gameState = 'tutorial';
tutorialText.visible = true;
continueText.visible = true;
instructionText.visible = false;
} else {
gameState = 'ready';
tutorialText.visible = false;
continueText.visible = false;
instructionText.visible = true;
}
tapCount = 0;
roundText.setText('Round ' + currentRound);
}
function startRace() {
gameState = 'racing';
raceStartTime = LK.ticks;
instructionText.visible = false;
}
function updatePlayerSpeed() {
var currentTime = LK.ticks;
var timeSinceLastTap = currentTime - lastTapTime;
if (timeSinceLastTap > 30) {
// Decrease speed if not tapping
playerRunner.speed *= 0.95;
}
if (playerRunner.speed > playerRunner.maxSpeed) {
playerRunner.speed = playerRunner.maxSpeed;
}
speedText.setText('Speed: ' + Math.floor(playerRunner.speed));
}
function updateAI() {
for (var i = 1; i < runners.length; i++) {
var aiRunner = runners[i];
// Use consistent base speed for this round with small random variations
var roundBonus = currentRound * 0.2;
var targetSpeed = (aiRunner.baseSpeed + roundBonus) * 1.22;
// Add small random variation (±10%) to make movement feel natural
var variation = targetSpeed * (0.8 + Math.random() * 0.2);
aiRunner.speed = variation;
// Cap speed at the runner's max speed
if (aiRunner.speed > aiRunner.maxSpeed) {
aiRunner.speed = aiRunner.maxSpeed;
}
// AI obstacle avoidance
for (var j = 0; j < obstacles.length; j++) {
var obs = obstacles[j];
if (obs.lane === aiRunner.lane && Math.abs(aiRunner.distance - obs.distance) < 150 && Math.random() < 0.7) {
aiRunner.jump();
break;
}
}
}
}
function updatePositions() {
var sortedRunners = runners.slice().sort(function (a, b) {
return b.distance - a.distance;
});
var playerPosition = sortedRunners.indexOf(playerRunner) + 1;
positionText.setText('Position: ' + playerPosition + '/5');
}
function checkRaceEnd() {
var finishedRunners = [];
for (var i = 0; i < runners.length; i++) {
if (runners[i].distance >= raceDistance) {
finishedRunners.push(runners[i]);
}
}
if (finishedRunners.length > 0) {
gameState = 'finished';
// Sort by distance to determine winner
finishedRunners.sort(function (a, b) {
return b.distance - a.distance;
});
if (finishedRunners[0] === playerRunner) {
// Player won
currentRound++;
LK.setScore(currentRound - 1);
LK.setTimeout(function () {
initializeRace();
}, 2000);
} else {
// Player lost
LK.setTimeout(function () {
LK.showGameOver();
}, 2000);
}
}
}
// Touch controls
game.down = function (x, y, obj) {
if (gameState === 'tutorial') {
// Move from tutorial to ready state
gameState = 'ready';
tutorialText.visible = false;
continueText.visible = false;
instructionText.visible = true;
}
if (gameState === 'ready') {
startRace();
}
if (gameState === 'racing') {
tapCount++;
lastTapTime = LK.ticks;
// Increase player speed
playerRunner.speed += 0.8;
// Jump if tapping in upper screen area
if (y < 1200) {
playerRunner.jump();
}
LK.getSound('tap').play();
}
};
// Game update loop
game.update = function () {
if (gameState === 'racing') {
updatePlayerSpeed();
updateAI();
updatePositions();
checkRaceEnd();
}
};
// Initialize first race
initializeRace();
runner with a red shirt. In-Game asset. 2d. High contrast. No shadows
runner with a green shirt. In-Game asset. 2d. High contrast. No shadows
runner with a yellow shirt. In-Game asset. 2d. High contrast. No shadows
runner with a blue shirt. In-Game asset. 2d. High contrast. No shadows
runner with a black shirt with number 7. In-Game asset. 2d. High contrast. No shadows
running obstacle. In-Game asset. 2d. High contrast. No shadows
running finish line. In-Game asset. 2d. High contrast. No shadows
horizontal white lines for running. In-Game asset. 2d. High contrast. No shadows
straight running lines. In-Game asset. 2d. High contrast. No shadows