Code edit (7 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
None of the UI buttons are working can you double check everything.
Code edit (3 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Can't find variable: launchQuality' in or related to this line: 'var launchBonus = launchQuality === 'perfect' ? 300 : launchQuality === 'good' ? 150 : 50;' Line Number: 249
Code edit (1 edits merged)
Please save this source code
User prompt
Take the vehicle customization + drag racing game concept we just defined and now turn it into a complete vertical slice implementation plan and code structure. I want a playable core loop: 1. Player starts in a garage 2. Player can view car stats and buy/install upgrades 3. Installed upgrades change the car’s performance stats 4. Player enters a drag race against 1 AI opponent 5. Race result calculates payout based on win/loss, finish time, reaction time, launch quality, and top speed 6. Player earns cash 7. Player returns to garage to upgrade again and repeat the loop Requirements: - Make this modular and production-ready - Separate systems into clear scripts/managers - Use data-driven design for cars, upgrades, races, and rewards - Include a save/load system for cash, owned upgrades, installed parts, and progression - Include a basic UI flow for Garage, Shop, Race HUD, and Results Screen - Include at least one starter car and one higher-tier opponent - Include upgrade categories: engine, turbo, tires, transmission, weight reduction, and visuals - Each upgrade must affect real car stats - AI opponent should scale by race tier - Keep the first version simple but expandable Output format: - First give me the full system architecture - Then list the exact scripts/classes to create - Then explain the order they should be built in - Then give starter code for the most important core systems first - Use clean naming and avoid overly complex features in version 1 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Build, Race, Upgrade
Initial prompt
Create a stylized vehicle customization and drag racing game with a satisfying repeatable gameplay loop. The player starts in a garage where they can buy and install car parts such as engine upgrades, turbo, tires, transmission, suspension, weight reduction, paint, wheels, and body kits. After customizing the car, the player enters a drag race event, competes in a short straight-line race, and earns cash based on performance including win/loss, finish time, reaction time, top speed, and launch quality. That cash is then used to upgrade the car and enter harder races, repeating the loop. Build the game with clean modular systems for Garage, Car Stats, Drag Race Manager, Reward System, Upgrade Shop, and Save/Load. Include a simple but polished UI, clear progression, increasingly difficult AI racers, and data-driven tuning so cars and upgrades are easy to balance. Build a game called “Build, Race, Upgrade” focused on car progression through drag racing. The core loop is: enter garage, customize and tune car, start drag race, earn money from race results, return to garage, buy better upgrades, and repeat. I want the garage to feel rewarding, with visible stat changes for horsepower, torque, grip, weight, and gearing whenever parts are installed. The race should emphasize launch timing, gear shifts, acceleration, and top-end speed rather than open-world driving. Include beginner, intermediate, and advanced race tiers, each with larger rewards and tougher opponents. Create systems for economy balancing, upgrade unlocks, car stat calculation, opponent scaling, and persistent save data. Make the code structure clean and expandable so more cars, tracks, parts, and race modes can be added later. Design a full arcade drag racing game where customization directly affects race performance and earnings. The player begins with a basic starter car and limited cash. In the garage they can modify visuals and performance, including paint color, rims, ride height, spoilers, engine internals, ECU tune, turbo, nitrous, gearbox, and tire compound. Each installed part should update the car’s stats in real time and change how the car performs in drag races. Races should be short, intense, and replayable, with a countdown, launch window, shifting mechanic, speed readout, opponent AI, finish results, and cash payout. Payouts should scale using placement, race class, clean shifting, reaction time, and personal best performance. After each race, the player returns to the garage to spend winnings and improve the car. Create the game so this loop feels addictive, polished, and easy to expand with more content later. Generate a car-building progression game with a strong “garage to race to upgrade” gameplay loop. The experience starts in a customization garage where the player can inspect their car, install upgrades, see part costs, compare old vs new stats, and preview visual changes. Once ready, the player enters a drag strip event where the gameplay focuses on launch timing, traction, acceleration, gear shifting, and beating an AI rival to the finish. When the race ends, award money based on race outcome and performance metrics, then send the player back to the garage. Include an upgrade tree or shop system that gradually unlocks better parts and encourages strategic choices between speed, grip, reliability, and tuning. Build a complete vertical slice with one garage, one starter car, multiple upgrades, several AI racers, and a satisfying progression loop that makes players want to race again immediately. Create a polished prototype for a vehicle customization drag racer with progression, tuning, and replayability. The player loop should be: buy or modify parts in garage, tune the car for drag performance, enter a race, try to get the best launch and shift timing, finish the race, earn cash rewards, unlock better parts, and repeat with faster opponents. Include systems for part categories, stat calculation, race payouts, difficulty progression, basic tuning adjustments, and a simple save system. The garage should clearly show current cash, installed parts, car stats, and upgrade options. The drag race should feel responsive and arcade-friendly, with visible countdown lights, RPM control before launch, gear changes during the run, and a post-race summary screen. Write the project in a scalable way with separate managers and ScriptableObject-style data structures so the game can grow into a larger car-building drag racing title.
/****
* Plugins
****/
var storage = LK.import("@upit/storage.v1", {
playerCash: 5000,
playerTier: 0,
carColor: 14427686,
engineLevel: 0,
turboLevel: 0,
transmissionLevel: 0,
suspensionLevel: 0,
weightLevel: 0,
tireLevel: 0,
wheelLevel: 0,
bodyKitLevel: 0,
bestTime: 0,
raceCount: 0
});
/****
* Initialize Game
****/
/****
* Game
****/
var game = new LK.Game({
backgroundColor: 0x0b1020
});
/****
* Game Code
****/
var WIDTH = 2048;
var HEIGHT = 2732;
var scene = null;
var currentState = 'garage';
var playerCar = null;
var opponentCar = null;
var launchLights = [];
var rpmText = null;
var speedText = null;
var gearText = null;
var distanceText = null;
var promptText = null;
var raceTimer = 0;
var playerFinishTime = 0;
var opponentFinishTime = 0;
var countdownIndex = 0;
var countdownElapsed = 0;
var raceStarted = false;
var raceFinished = false;
var playerCanShift = false;
var playerLaunched = false;
var playerSpeed = 0;
var opponentSpeed = 0;
var playerDistance = 0;
var opponentDistance = 0;
var playerGear = 1;
var opponentGear = 1;
var playerRPM = 1000;
var launchAccuracy = 0;
var launchQuality = 'MISS';
var playerShiftWindowMin = 5200;
var playerShiftWindowMax = 6600;
var upgradeData = [{
key: 'engine',
label: 'Engine',
baseCost: 1000,
maxLevel: 5
}, {
key: 'turbo',
label: 'Turbo',
baseCost: 1400,
maxLevel: 5
}, {
key: 'transmission',
label: 'Transmission',
baseCost: 900,
maxLevel: 5
}, {
key: 'suspension',
label: 'Suspension',
baseCost: 700,
maxLevel: 5
}, {
key: 'weight',
label: 'Weight Reduction',
baseCost: 1100,
maxLevel: 5
}, {
key: 'tire',
label: 'Tires',
baseCost: 600,
maxLevel: 5
}];
/****
* Helpers
****/
function clearScene() {
if (scene) {
scene.destroy();
scene = null;
}
game.update = function () {};
game.down = function () {};
}
function formatCash(v) {
return '$' + Math.floor(v);
}
function getUpgradeLevel(key) {
return storage[key + 'Level'] || 0;
}
function getUpgradeCost(key, baseCost) {
var level = getUpgradeLevel(key);
return baseCost + level * Math.floor(baseCost * 0.65);
}
function getPlayerStats() {
var engine = getUpgradeLevel('engine');
var turbo = getUpgradeLevel('turbo');
var transmission = getUpgradeLevel('transmission');
var suspension = getUpgradeLevel('suspension');
var weight = getUpgradeLevel('weight');
var tire = getUpgradeLevel('tire');
var hp = 160 + engine * 28 + turbo * 36;
var torque = 210 + engine * 22 + turbo * 30;
var grip = 72 + tire * 6 + suspension * 4;
var mass = 1450 - weight * 55;
var shiftSpeed = 1 + transmission * 0.08;
var traction = 1 + tire * 0.06 + suspension * 0.03;
return {
hp: hp,
torque: torque,
grip: grip,
mass: Math.max(980, mass),
shiftSpeed: shiftSpeed,
traction: traction
};
}
function getOpponentStats() {
var tier = storage.playerTier || 0;
return {
hp: 170 + tier * 35,
torque: 220 + tier * 28,
grip: 75 + tier * 5,
mass: 1425 - tier * 35,
shiftSpeed: 1.03 + tier * 0.05,
traction: 1.02 + tier * 0.05
};
}
function makeLabel(parent, txt, x, y, size, color, ax, ay) {
var t = parent.addChild(new Text2(txt, {
size: size || 32,
fill: color || '#FFFFFF'
}));
t.anchor.set(ax == null ? 0.5 : ax, ay == null ? 0.5 : ay);
t.x = x;
t.y = y;
return t;
}
function createCar(isOpponent) {
var c = new Container();
var body = c.addChild(LK.getAsset(isOpponent ? 'opponentBody' : 'carBody', {
anchorX: 0.5,
anchorY: 0.5
}));
var windowObj = c.addChild(LK.getAsset('carWindow', {
anchorX: 0.5,
anchorY: 0.5
}));
windowObj.y = -10;
var wheel1 = c.addChild(LK.getAsset('wheel', {
anchorX: 0.5,
anchorY: 0.5
}));
var wheel2 = c.addChild(LK.getAsset('wheel', {
anchorX: 0.5,
anchorY: 0.5
}));
var wheel3 = c.addChild(LK.getAsset('wheel', {
anchorX: 0.5,
anchorY: 0.5
}));
var wheel4 = c.addChild(LK.getAsset('wheel', {
anchorX: 0.5,
anchorY: 0.5
}));
var rim1 = c.addChild(LK.getAsset('rim', {
anchorX: 0.5,
anchorY: 0.5
}));
var rim2 = c.addChild(LK.getAsset('rim', {
anchorX: 0.5,
anchorY: 0.5
}));
var rim3 = c.addChild(LK.getAsset('rim', {
anchorX: 0.5,
anchorY: 0.5
}));
var rim4 = c.addChild(LK.getAsset('rim', {
anchorX: 0.5,
anchorY: 0.5
}));
var positions = [{
x: -45,
y: -36
}, {
x: 45,
y: -36
}, {
x: -45,
y: 36
}, {
x: 45,
y: 36
}];
var wheels = [wheel1, wheel2, wheel3, wheel4];
var rims = [rim1, rim2, rim3, rim4];
for (var i = 0; i < 4; i++) {
wheels[i].x = positions[i].x;
wheels[i].y = positions[i].y;
rims[i].x = positions[i].x;
rims[i].y = positions[i].y;
}
c.setColor = function (color) {
body.tint = color;
};
c.spinWheels = function (speed) {
rim1.rotation += speed;
rim2.rotation += speed;
rim3.rotation += speed;
rim4.rotation += speed;
};
return c;
}
function resetRaceValues() {
raceTimer = 0;
playerFinishTime = 0;
opponentFinishTime = 0;
countdownIndex = 0;
countdownElapsed = 0;
raceStarted = false;
raceFinished = false;
playerCanShift = false;
playerLaunched = false;
playerSpeed = 0;
opponentSpeed = 0;
playerDistance = 0;
opponentDistance = 0;
playerGear = 1;
opponentGear = 1;
playerRPM = 1000;
launchAccuracy = 0;
launchQuality = 'MISS';
}
/****
* Garage
****/
function initGarage() {
clearScene();
currentState = 'garage';
LK.playMusic('garageMusic', {
loop: true
});
scene = game.addChild(new Container());
makeLabel(scene, 'MIDNIGHT STRIP', WIDTH / 2, 120, 84, '#FFFFFF');
makeLabel(scene, 'Build • Launch • Shift • Upgrade', WIDTH / 2, 205, 34, '#93c5fd');
var cashPanel = scene.addChild(LK.getAsset('panel', {
anchorX: 0.5,
anchorY: 0.5
}));
cashPanel.x = WIDTH / 2;
cashPanel.y = 340;
makeLabel(scene, 'Cash: ' + formatCash(storage.playerCash || 0), WIDTH / 2, 340, 42, '#22c55e');
makeLabel(scene, 'Tier: ' + ((storage.playerTier || 0) + 1), WIDTH / 2, 405, 28, '#facc15');
playerCar = scene.addChild(createCar(false));
playerCar.x = WIDTH / 2;
playerCar.y = 730;
playerCar.setColor(storage.carColor || 0xdc2626);
playerCar.scale.set(1.55);
var stats = getPlayerStats();
makeLabel(scene, 'HP: ' + stats.hp, 250, 1020, 34, '#FFFFFF', 0, 0.5);
makeLabel(scene, 'Torque: ' + stats.torque, 250, 1075, 34, '#FFFFFF', 0, 0.5);
makeLabel(scene, 'Grip: ' + stats.grip, 250, 1130, 34, '#FFFFFF', 0, 0.5);
makeLabel(scene, 'Weight: ' + stats.mass + 'kg', 250, 1185, 34, '#FFFFFF', 0, 0.5);
makeLabel(scene, 'Shift Speed: ' + stats.shiftSpeed.toFixed(2), 250, 1240, 34, '#FFFFFF', 0, 0.5);
makeLabel(scene, 'UPGRADES', WIDTH / 2, 1360, 46, '#facc15');
for (var i = 0; i < upgradeData.length; i++) {
(function (data, index) {
var level = getUpgradeLevel(data.key);
var cost = getUpgradeCost(data.key, data.baseCost);
var card = scene.addChild(LK.getAsset('upgradeCard', {
anchorX: 0.5,
anchorY: 0.5
}));
card.x = 560 + index % 2 * 930;
card.y = 1510 + Math.floor(index / 2) * 150;
makeLabel(scene, data.label + ' Lv.' + level, card.x, card.y - 22, 30, '#FFFFFF');
makeLabel(scene, level >= data.maxLevel ? 'MAXED' : 'Buy: ' + formatCash(cost), card.x, card.y + 20, 24, level >= data.maxLevel ? '#facc15' : '#22c55e');
card.down = function () {
buyUpgrade(data);
};
})(upgradeData[i], i);
}
var raceBtn = scene.addChild(LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
}));
raceBtn.x = WIDTH / 2;
raceBtn.y = 2400;
makeLabel(scene, 'START RACE', WIDTH / 2, 2400, 40, '#08110d');
raceBtn.down = function () {
startCountdown();
};
}
function buyUpgrade(data) {
var level = getUpgradeLevel(data.key);
if (level >= data.maxLevel) return;
var cost = getUpgradeCost(data.key, data.baseCost);
if ((storage.playerCash || 0) < cost) return;
storage.playerCash -= cost;
storage[data.key + 'Level'] = level + 1;
LK.getSound('cashReward').play();
initGarage();
}
/****
* Countdown / launch
****/
function startCountdown() {
resetRaceValues();
clearScene();
currentState = 'countdown';
LK.playMusic('raceMusic', {
loop: true
});
scene = game.addChild(new Container());
var track = scene.addChild(LK.getAsset('track', {
anchorX: 0.5,
anchorY: 0.5
}));
track.x = WIDTH / 2;
track.y = 1500;
var finish = scene.addChild(LK.getAsset('finishLine', {
anchorX: 0.5,
anchorY: 0.5
}));
finish.x = 1760;
finish.y = 1500;
playerCar = scene.addChild(createCar(false));
playerCar.x = 340;
playerCar.y = 1380;
playerCar.setColor(storage.carColor || 0xdc2626);
opponentCar = scene.addChild(createCar(true));
opponentCar.x = 340;
opponentCar.y = 1620;
makeLabel(scene, 'LAUNCH ON GREEN', WIDTH / 2, 180, 58, '#FFFFFF');
makeLabel(scene, 'Tap too early = weak launch. Tap on green with strong RPM = best launch.', WIDTH / 2, 255, 28, '#facc15');
var lightX = WIDTH / 2;
var startY = 520;
launchLights = [];
for (var i = 0; i < 3; i++) {
var light = scene.addChild(LK.getAsset('lightOff', {
anchorX: 0.5,
anchorY: 0.5
}));
light.x = lightX;
light.y = startY + i * 72;
launchLights.push(light);
}
rpmText = makeLabel(scene, 'RPM: 1000', WIDTH / 2, 920, 42, '#22c55e');
promptText = makeLabel(scene, 'Hold steady and tap on GREEN', WIDTH / 2, 1000, 34, '#FFFFFF');
game.down = function () {
if (currentState !== 'countdown') return;
if (countdownIndex < 3) {
launchQuality = 'FOUL';
launchAccuracy = 0;
playerLaunched = true;
beginRace();
return;
}
playerLaunched = true;
var rpmScore = 1 - Math.min(1, Math.abs(5000 - playerRPM) / 2200);
launchAccuracy = Math.max(0, rpmScore);
if (launchAccuracy > 0.88) launchQuality = 'PERFECT';else if (launchAccuracy > 0.68) launchQuality = 'GOOD';else if (launchAccuracy > 0.45) launchQuality = 'OK';else launchQuality = 'POOR';
LK.getSound('launch').play();
beginRace();
};
game.update = function () {
if (currentState !== 'countdown') return;
countdownElapsed += 1 / 60;
playerRPM += 85;
if (playerRPM > 7000) playerRPM = 3200;
rpmText.setText('RPM: ' + Math.floor(playerRPM));
if (countdownElapsed >= 0.75 && countdownIndex < 3) {
countdownElapsed = 0;
launchLights[countdownIndex].destroy();
launchLights[countdownIndex] = scene.addChild(LK.getAsset(countdownIndex < 2 ? 'lightRed' : 'lightGreen', {
anchorX: 0.5,
anchorY: 0.5
}));
launchLights[countdownIndex].x = WIDTH / 2;
launchLights[countdownIndex].y = 520 + countdownIndex * 72;
countdownIndex++;
}
if (countdownIndex >= 3 && !playerLaunched && countdownElapsed >= 0.40) {
launchQuality = 'LATE';
launchAccuracy = 0.2;
playerLaunched = true;
beginRace();
}
};
}
/****
* Race
****/
function beginRace() {
currentState = 'race';
raceStarted = true;
raceFinished = false;
raceTimer = 0;
playerCanShift = false;
var playerStats = getPlayerStats();
var aiStats = getOpponentStats();
speedText = makeLabel(scene, 'Speed: 0 mph', 150, 120, 34, '#22c55e', 0, 0.5);
gearText = makeLabel(scene, 'Gear: 1', 150, 170, 34, '#facc15', 0, 0.5);
distanceText = makeLabel(scene, 'Distance: 0 m', 150, 220, 34, '#FFFFFF', 0, 0.5);
promptText.setText('Tap to shift in the sweet spot');
game.down = function () {
if (currentState !== 'race' || !raceStarted || raceFinished) return;
if (playerCanShift) {
playerGear++;
playerCanShift = false;
playerRPM = 4200 - getUpgradeLevel('transmission') * 120;
LK.getSound('shiftGear').play();
promptText.setText('GOOD SHIFT');
}
};
game.update = function () {
if (currentState !== 'race' || raceFinished) return;
raceTimer += 1 / 60;
var playerLaunchBoost = 0.75 + launchAccuracy * 0.65;
var playerAccel = playerStats.torque / playerStats.mass * 2.4 * playerLaunchBoost / Math.max(1, playerGear * 0.82);
var aiLaunchBoost = 0.92 + (storage.playerTier || 0) * 0.03;
var aiAccel = aiStats.torque / aiStats.mass * 2.15 * aiLaunchBoost / Math.max(1, opponentGear * 0.84);
playerRPM += 95 + playerGear * 24 + getUpgradeLevel('engine') * 6;
if (playerRPM >= playerShiftWindowMin && playerRPM <= playerShiftWindowMax) {
playerCanShift = true;
promptText.setText('SHIFT NOW');
} else if (playerRPM > playerShiftWindowMax + 350) {
playerCanShift = false;
promptText.setText('LATE SHIFT');
}
if (playerRPM > 7600) {
playerRPM = 5200;
playerSpeed *= 0.985;
}
if (playerGear < 6) {
playerSpeed += playerAccel;
} else {
playerSpeed += playerAccel * 0.5;
}
if (opponentGear < 6 && Math.random() < 0.025 + (storage.playerTier || 0) * 0.005) {
opponentGear++;
}
opponentSpeed += aiAccel;
playerSpeed = Math.min(playerSpeed, 60 + playerStats.hp * 0.62);
opponentSpeed = Math.min(opponentSpeed, 60 + aiStats.hp * 0.6);
playerDistance += playerSpeed * 0.17;
opponentDistance += opponentSpeed * 0.17;
playerCar.x = 340 + Math.min(1320, playerDistance * 0.42);
opponentCar.x = 340 + Math.min(1320, opponentDistance * 0.42);
playerCar.spinWheels(playerSpeed * 0.01);
opponentCar.spinWheels(opponentSpeed * 0.01);
speedText.setText('Speed: ' + Math.floor(playerSpeed) + ' mph');
gearText.setText('Gear: ' + playerGear);
distanceText.setText('Distance: ' + Math.floor(playerDistance) + ' m');
if (!playerFinishTime && playerDistance >= 3000) {
playerFinishTime = raceTimer;
}
if (!opponentFinishTime && opponentDistance >= 3000) {
opponentFinishTime = raceTimer;
}
if (playerFinishTime && opponentFinishTime) {
raceFinished = true;
showResults();
}
};
}
/****
* Results
****/
function showResults() {
currentState = 'results';
var playerWon = playerFinishTime <= opponentFinishTime;
var tier = storage.playerTier || 0;
var baseReward = playerWon ? 900 + tier * 250 : 300;
var launchReward = launchQuality === 'PERFECT' ? 300 : launchQuality === 'GOOD' ? 180 : launchQuality === 'OK' ? 100 : launchQuality === 'POOR' ? 40 : 0;
var timeBonus = Math.max(0, Math.floor((12 - playerFinishTime) * 80));
var reward = baseReward + launchReward + timeBonus;
storage.playerCash = (storage.playerCash || 0) + reward;
storage.raceCount = (storage.raceCount || 0) + 1;
if (playerWon && tier < 5) {
storage.playerTier = tier + 1;
}
if (!storage.bestTime || playerFinishTime < storage.bestTime) {
storage.bestTime = playerFinishTime;
}
if (playerWon) LK.getSound('raceWin').play();else LK.getSound('raceLose').play();
clearScene();
scene = game.addChild(new Container());
makeLabel(scene, playerWon ? 'YOU WIN' : 'YOU LOSE', WIDTH / 2, 180, 86, playerWon ? '#22c55e' : '#ef4444');
makeLabel(scene, 'Launch: ' + launchQuality, WIDTH / 2, 420, 42, '#facc15');
makeLabel(scene, 'Your Time: ' + playerFinishTime.toFixed(2) + 's', WIDTH / 2, 520, 40, '#FFFFFF');
makeLabel(scene, 'Opponent Time: ' + opponentFinishTime.toFixed(2) + 's', WIDTH / 2, 600, 40, '#FFFFFF');
makeLabel(scene, 'Reward: ' + formatCash(reward), WIDTH / 2, 720, 48, '#22c55e');
makeLabel(scene, 'Best Time: ' + (storage.bestTime ? storage.bestTime.toFixed(2) + 's' : '--'), WIDTH / 2, 810, 34, '#93c5fd');
makeLabel(scene, 'Cash Total: ' + formatCash(storage.playerCash || 0), WIDTH / 2, 880, 34, '#FFFFFF');
var carPreview = scene.addChild(createCar(false));
carPreview.x = WIDTH / 2;
carPreview.y = 1300;
carPreview.scale.set(1.7);
carPreview.setColor(storage.carColor || 0xdc2626);
var garageBtn = scene.addChild(LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
}));
garageBtn.x = WIDTH / 2;
garageBtn.y = 2200;
makeLabel(scene, 'BACK TO GARAGE', WIDTH / 2, 2200, 38, '#08110d');
garageBtn.down = function () {
initGarage();
};
game.down = function () {};
game.update = function () {};
}
/****
* Start
****/
initGarage(); ===================================================================
--- original.js
+++ change.js
@@ -1,624 +1,538 @@
/****
* Plugins
****/
-var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
playerCash: 5000,
playerTier: 0,
- carColor: 16711680,
+ carColor: 14427686,
engineLevel: 0,
turboLevel: 0,
transmissionLevel: 0,
suspensionLevel: 0,
weightLevel: 0,
tireLevel: 0,
wheelLevel: 0,
bodyKitLevel: 0,
- raceHistory: []
+ bestTime: 0,
+ raceCount: 0
});
/****
-* Classes
+* Initialize Game
****/
-var OpponentCar = Container.expand(function () {
- var self = Container.call(this);
- var carBody = self.attachAsset('opponentCar', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- var carWindow = self.addChild(LK.getAsset('opponentWindow', {
- anchorX: 0.5,
- anchorY: 0.5
+/****
+* Game
+****/
+var game = new LK.Game({
+ backgroundColor: 0x0b1020
+});
+
+/****
+* Game Code
+****/
+var WIDTH = 2048;
+var HEIGHT = 2732;
+var scene = null;
+var currentState = 'garage';
+var playerCar = null;
+var opponentCar = null;
+var launchLights = [];
+var rpmText = null;
+var speedText = null;
+var gearText = null;
+var distanceText = null;
+var promptText = null;
+var raceTimer = 0;
+var playerFinishTime = 0;
+var opponentFinishTime = 0;
+var countdownIndex = 0;
+var countdownElapsed = 0;
+var raceStarted = false;
+var raceFinished = false;
+var playerCanShift = false;
+var playerLaunched = false;
+var playerSpeed = 0;
+var opponentSpeed = 0;
+var playerDistance = 0;
+var opponentDistance = 0;
+var playerGear = 1;
+var opponentGear = 1;
+var playerRPM = 1000;
+var launchAccuracy = 0;
+var launchQuality = 'MISS';
+var playerShiftWindowMin = 5200;
+var playerShiftWindowMax = 6600;
+var upgradeData = [{
+ key: 'engine',
+ label: 'Engine',
+ baseCost: 1000,
+ maxLevel: 5
+}, {
+ key: 'turbo',
+ label: 'Turbo',
+ baseCost: 1400,
+ maxLevel: 5
+}, {
+ key: 'transmission',
+ label: 'Transmission',
+ baseCost: 900,
+ maxLevel: 5
+}, {
+ key: 'suspension',
+ label: 'Suspension',
+ baseCost: 700,
+ maxLevel: 5
+}, {
+ key: 'weight',
+ label: 'Weight Reduction',
+ baseCost: 1100,
+ maxLevel: 5
+}, {
+ key: 'tire',
+ label: 'Tires',
+ baseCost: 600,
+ maxLevel: 5
+}];
+/****
+* Helpers
+****/
+function clearScene() {
+ if (scene) {
+ scene.destroy();
+ scene = null;
+ }
+ game.update = function () {};
+ game.down = function () {};
+}
+function formatCash(v) {
+ return '$' + Math.floor(v);
+}
+function getUpgradeLevel(key) {
+ return storage[key + 'Level'] || 0;
+}
+function getUpgradeCost(key, baseCost) {
+ var level = getUpgradeLevel(key);
+ return baseCost + level * Math.floor(baseCost * 0.65);
+}
+function getPlayerStats() {
+ var engine = getUpgradeLevel('engine');
+ var turbo = getUpgradeLevel('turbo');
+ var transmission = getUpgradeLevel('transmission');
+ var suspension = getUpgradeLevel('suspension');
+ var weight = getUpgradeLevel('weight');
+ var tire = getUpgradeLevel('tire');
+ var hp = 160 + engine * 28 + turbo * 36;
+ var torque = 210 + engine * 22 + turbo * 30;
+ var grip = 72 + tire * 6 + suspension * 4;
+ var mass = 1450 - weight * 55;
+ var shiftSpeed = 1 + transmission * 0.08;
+ var traction = 1 + tire * 0.06 + suspension * 0.03;
+ return {
+ hp: hp,
+ torque: torque,
+ grip: grip,
+ mass: Math.max(980, mass),
+ shiftSpeed: shiftSpeed,
+ traction: traction
+ };
+}
+function getOpponentStats() {
+ var tier = storage.playerTier || 0;
+ return {
+ hp: 170 + tier * 35,
+ torque: 220 + tier * 28,
+ grip: 75 + tier * 5,
+ mass: 1425 - tier * 35,
+ shiftSpeed: 1.03 + tier * 0.05,
+ traction: 1.02 + tier * 0.05
+ };
+}
+function makeLabel(parent, txt, x, y, size, color, ax, ay) {
+ var t = parent.addChild(new Text2(txt, {
+ size: size || 32,
+ fill: color || '#FFFFFF'
}));
- carWindow.y = -40;
- var wheelFrontLeft = self.addChild(LK.getAsset('wheel', {
+ t.anchor.set(ax == null ? 0.5 : ax, ay == null ? 0.5 : ay);
+ t.x = x;
+ t.y = y;
+ return t;
+}
+function createCar(isOpponent) {
+ var c = new Container();
+ var body = c.addChild(LK.getAsset(isOpponent ? 'opponentBody' : 'carBody', {
anchorX: 0.5,
anchorY: 0.5
}));
- wheelFrontLeft.x = -35;
- wheelFrontLeft.y = 60;
- wheelFrontLeft.tint = 0x555555;
- var wheelFrontRight = self.addChild(LK.getAsset('wheel', {
+ var windowObj = c.addChild(LK.getAsset('carWindow', {
anchorX: 0.5,
anchorY: 0.5
}));
- wheelFrontRight.x = 35;
- wheelFrontRight.y = 60;
- wheelFrontRight.tint = 0x555555;
- var wheelRearLeft = self.addChild(LK.getAsset('wheel', {
+ windowObj.y = -10;
+ var wheel1 = c.addChild(LK.getAsset('wheel', {
anchorX: 0.5,
anchorY: 0.5
}));
- wheelRearLeft.x = -35;
- wheelRearLeft.y = -60;
- wheelRearLeft.tint = 0x555555;
- var wheelRearRight = self.addChild(LK.getAsset('wheel', {
+ var wheel2 = c.addChild(LK.getAsset('wheel', {
anchorX: 0.5,
anchorY: 0.5
}));
- wheelRearRight.x = 35;
- wheelRearRight.y = -60;
- wheelRearRight.tint = 0x555555;
- self.difficulty = 1;
- self.getStats = function () {
- var baseHP = 150 + self.difficulty * 20;
- var baseTorque = 200 + self.difficulty * 15;
- var baseGrip = 70 + self.difficulty * 5;
- var baseWeight = 1500 - self.difficulty * 30;
- var baseGearing = 3.5 + self.difficulty * 0.1;
- return {
- hp: baseHP,
- torque: baseTorque,
- grip: baseGrip,
- weight: baseWeight,
- gearing: baseGearing
- };
- };
- return self;
-});
-var PlayerCar = Container.expand(function () {
- var self = Container.call(this);
- var carBody = self.attachAsset('carBody', {
+ var wheel3 = c.addChild(LK.getAsset('wheel', {
anchorX: 0.5,
anchorY: 0.5
- });
- var carWindow = self.addChild(LK.getAsset('carWindow', {
- anchorX: 0.5,
- anchorY: 0.5
}));
- carWindow.y = -40;
- var wheelFrontLeft = self.addChild(LK.getAsset('wheel', {
+ var wheel4 = c.addChild(LK.getAsset('wheel', {
anchorX: 0.5,
anchorY: 0.5
}));
- wheelFrontLeft.x = -35;
- wheelFrontLeft.y = 60;
- var wheelFrontLeftRim = self.addChild(LK.getAsset('wheelRim', {
+ var rim1 = c.addChild(LK.getAsset('rim', {
anchorX: 0.5,
anchorY: 0.5
}));
- wheelFrontLeftRim.x = -35;
- wheelFrontLeftRim.y = 60;
- var wheelFrontRight = self.addChild(LK.getAsset('wheel', {
+ var rim2 = c.addChild(LK.getAsset('rim', {
anchorX: 0.5,
anchorY: 0.5
}));
- wheelFrontRight.x = 35;
- wheelFrontRight.y = 60;
- var wheelFrontRightRim = self.addChild(LK.getAsset('wheelRim', {
+ var rim3 = c.addChild(LK.getAsset('rim', {
anchorX: 0.5,
anchorY: 0.5
}));
- wheelFrontRightRim.x = 35;
- wheelFrontRightRim.y = 60;
- var wheelRearLeft = self.addChild(LK.getAsset('wheel', {
+ var rim4 = c.addChild(LK.getAsset('rim', {
anchorX: 0.5,
anchorY: 0.5
}));
- wheelRearLeft.x = -35;
- wheelRearLeft.y = -60;
- var wheelRearLeftRim = self.addChild(LK.getAsset('wheelRim', {
- anchorX: 0.5,
- anchorY: 0.5
- }));
- wheelRearLeftRim.x = -35;
- wheelRearLeftRim.y = -60;
- var wheelRearRight = self.addChild(LK.getAsset('wheel', {
- anchorX: 0.5,
- anchorY: 0.5
- }));
- wheelRearRight.x = 35;
- wheelRearRight.y = -60;
- var wheelRearRightRim = self.addChild(LK.getAsset('wheelRim', {
- anchorX: 0.5,
- anchorY: 0.5
- }));
- wheelRearRightRim.x = 35;
- wheelRearRightRim.y = -60;
- self.getStats = function () {
- var baseHP = 150;
- var baseTorque = 200;
- var baseGrip = 70;
- var baseWeight = 1500;
- var baseGearing = 3.5;
- var hpBoost = storage.engineLevel * 30 + storage.turboLevel * 40;
- var torqueBoost = storage.engineLevel * 20 + storage.turboLevel * 35;
- var gripBoost = storage.tireLevel * 15 + storage.suspensionLevel * 10;
- var weightReduction = storage.weightLevel * 50;
- var gearingAdjust = storage.transmissionLevel * 0.1;
- return {
- hp: baseHP + hpBoost,
- torque: baseTorque + torqueBoost,
- grip: Math.min(100, baseGrip + gripBoost),
- weight: Math.max(800, baseWeight - weightReduction),
- gearing: baseGearing + gearingAdjust
- };
+ var positions = [{
+ x: -45,
+ y: -36
+ }, {
+ x: 45,
+ y: -36
+ }, {
+ x: -45,
+ y: 36
+ }, {
+ x: 45,
+ y: 36
+ }];
+ var wheels = [wheel1, wheel2, wheel3, wheel4];
+ var rims = [rim1, rim2, rim3, rim4];
+ for (var i = 0; i < 4; i++) {
+ wheels[i].x = positions[i].x;
+ wheels[i].y = positions[i].y;
+ rims[i].x = positions[i].x;
+ rims[i].y = positions[i].y;
+ }
+ c.setColor = function (color) {
+ body.tint = color;
};
- self.setColor = function (color) {
- carBody.tint = color;
+ c.spinWheels = function (speed) {
+ rim1.rotation += speed;
+ rim2.rotation += speed;
+ rim3.rotation += speed;
+ rim4.rotation += speed;
};
- return self;
-});
-var UpgradePart = Container.expand(function (name, category, level, baseCost) {
- var self = Container.call(this);
- var box = self.attachAsset('upgradePartBox', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- var nameText = self.addChild(new Text2(name, {
- size: 30,
- fill: '#FFFFFF'
- }));
- nameText.anchor.set(0.5, 0.5);
- nameText.y = -25;
- var levelText = self.addChild(new Text2('Lv.' + level, {
- size: 20,
- fill: '#FFFF00'
- }));
- levelText.anchor.set(0.5, 0.5);
- levelText.y = 0;
- var costText = self.addChild(new Text2('$' + (baseCost + level * 500), {
- size: 18,
- fill: '#00FF00'
- }));
- costText.anchor.set(0.5, 0.5);
- costText.y = 25;
- self.name = name;
- self.category = category;
- self.level = level;
- self.cost = baseCost + level * 500;
- return self;
-});
-
+ return c;
+}
+function resetRaceValues() {
+ raceTimer = 0;
+ playerFinishTime = 0;
+ opponentFinishTime = 0;
+ countdownIndex = 0;
+ countdownElapsed = 0;
+ raceStarted = false;
+ raceFinished = false;
+ playerCanShift = false;
+ playerLaunched = false;
+ playerSpeed = 0;
+ opponentSpeed = 0;
+ playerDistance = 0;
+ opponentDistance = 0;
+ playerGear = 1;
+ opponentGear = 1;
+ playerRPM = 1000;
+ launchAccuracy = 0;
+ launchQuality = 'MISS';
+}
/****
-* Initialize Game
+* Garage
****/
-var game = new LK.Game({
- backgroundColor: 0x1a1a1a
-});
-
-/****
-* Game Code
-****/
-var gameState = 'garage';
-var playerCar = null;
-var opponentCar = null;
-var playerSpeed = 0;
-var opponentSpeed = 0;
-var playerNextShiftDistance = 300;
-var opponentNextShiftDistance = 300;
-var playerRPM = 0;
-var opponentRPM = 0;
-var playerFinished = false;
-var opponentFinished = false;
-var raceDistance = 3000;
-var playerDistance = 0;
-var opponentDistance = 0;
-var raceStarted = false;
-var launchPhaseActive = false;
-var launchCountdown = 3;
-var optimalRPMMin = 3500;
-var optimalRPMMax = 5500;
-var reactionTime = 0;
-var launchQuality = 'poor';
-var launchBonus = launchQuality === 'perfect' ? 300 : launchQuality === 'good' ? 150 : 50;
-var garageContainer = null;
-var raceContainer = null;
-var launchContainer = null;
-var resultsContainer = null;
-var cashDisplay = null;
-var statsDisplay = null;
-var selectedUpgrades = {};
function initGarage() {
- gameState = 'garage';
- if (!storage.playerCash) {
- storage.playerCash = 5000;
- }
- if (!storage.playerTier) {
- storage.playerTier = 0;
- }
- if (!storage.carColor) {
- storage.carColor = 16711680;
- }
+ clearScene();
+ currentState = 'garage';
LK.playMusic('garageMusic', {
loop: true
});
- garageContainer = game.addChild(new Container());
- var titleText = garageContainer.addChild(new Text2('GARAGE', {
- size: 80,
- fill: '#FFFFFF'
+ scene = game.addChild(new Container());
+ makeLabel(scene, 'MIDNIGHT STRIP', WIDTH / 2, 120, 84, '#FFFFFF');
+ makeLabel(scene, 'Build • Launch • Shift • Upgrade', WIDTH / 2, 205, 34, '#93c5fd');
+ var cashPanel = scene.addChild(LK.getAsset('panel', {
+ anchorX: 0.5,
+ anchorY: 0.5
}));
- titleText.anchor.set(0.5, 0);
- titleText.x = 1024;
- titleText.y = 50;
- cashDisplay = garageContainer.addChild(new Text2('Cash: $' + (storage.playerCash || 0), {
- size: 40,
- fill: '#00FF00'
- }));
- cashDisplay.anchor.set(0.5, 0);
- cashDisplay.x = 1024;
- cashDisplay.y = 150;
- playerCar = garageContainer.addChild(new PlayerCar());
- playerCar.x = 1024;
- playerCar.y = 600;
- playerCar.setColor(storage.carColor);
- var statsContainer = garageContainer.addChild(new Container());
- statsContainer.x = 50;
- statsContainer.y = 300;
- var statsLabel = statsContainer.addChild(new Text2('CAR STATS', {
- size: 35,
- fill: '#FFFF00'
- }));
- statsLabel.anchor.set(0, 0);
- var carStats = playerCar.getStats();
- var statTexts = ['HP: ' + Math.round(carStats.hp), 'Torque: ' + Math.round(carStats.torque), 'Grip: ' + Math.round(carStats.grip) + '%', 'Weight: ' + Math.round(carStats.weight) + 'kg', 'Gearing: ' + carStats.gearing.toFixed(2)];
- for (var i = 0; i < statTexts.length; i++) {
- var statText = statsContainer.addChild(new Text2(statTexts[i], {
- size: 25,
- fill: '#FFFFFF'
- }));
- statText.anchor.set(0, 0);
- statText.y = 50 + i * 40;
- }
- var upgradesContainer = garageContainer.addChild(new Container());
- upgradesContainer.x = 1400;
- upgradesContainer.y = 250;
- var upgradesLabel = upgradesContainer.addChild(new Text2('UPGRADES', {
- size: 35,
- fill: '#FFFF00'
- }));
- upgradesLabel.anchor.set(0, 0);
- upgradesLabel.y = -50;
- var upgradeParts = [{
- name: 'Engine',
- category: 'engine',
- baseCost: 1000
- }, {
- name: 'Turbo',
- category: 'turbo',
- baseCost: 1500
- }, {
- name: 'Transmission',
- category: 'transmission',
- baseCost: 800
- }, {
- name: 'Suspension',
- category: 'suspension',
- baseCost: 600
- }, {
- name: 'Weight Reduction',
- category: 'weight',
- baseCost: 1200
- }, {
- name: 'Tires',
- category: 'tire',
- baseCost: 400
- }, {
- name: 'Wheels',
- category: 'wheel',
- baseCost: 700
- }, {
- name: 'Body Kit',
- category: 'bodyKit',
- baseCost: 900
- }];
- for (var i = 0; i < upgradeParts.length; i++) {
- var part = upgradeParts[i];
- var currentLevel = storage[part.category + 'Level'] || 0;
- var upgradePart = upgradesContainer.addChild(new UpgradePart(part.name, part.category, currentLevel, part.baseCost));
- upgradePart.x = i % 2 * 250;
- upgradePart.y = Math.floor(i / 2) * 120;
- (function (p, upgrade) {
- upgrade.down = function () {
- purchaseUpgrade(p);
+ cashPanel.x = WIDTH / 2;
+ cashPanel.y = 340;
+ makeLabel(scene, 'Cash: ' + formatCash(storage.playerCash || 0), WIDTH / 2, 340, 42, '#22c55e');
+ makeLabel(scene, 'Tier: ' + ((storage.playerTier || 0) + 1), WIDTH / 2, 405, 28, '#facc15');
+ playerCar = scene.addChild(createCar(false));
+ playerCar.x = WIDTH / 2;
+ playerCar.y = 730;
+ playerCar.setColor(storage.carColor || 0xdc2626);
+ playerCar.scale.set(1.55);
+ var stats = getPlayerStats();
+ makeLabel(scene, 'HP: ' + stats.hp, 250, 1020, 34, '#FFFFFF', 0, 0.5);
+ makeLabel(scene, 'Torque: ' + stats.torque, 250, 1075, 34, '#FFFFFF', 0, 0.5);
+ makeLabel(scene, 'Grip: ' + stats.grip, 250, 1130, 34, '#FFFFFF', 0, 0.5);
+ makeLabel(scene, 'Weight: ' + stats.mass + 'kg', 250, 1185, 34, '#FFFFFF', 0, 0.5);
+ makeLabel(scene, 'Shift Speed: ' + stats.shiftSpeed.toFixed(2), 250, 1240, 34, '#FFFFFF', 0, 0.5);
+ makeLabel(scene, 'UPGRADES', WIDTH / 2, 1360, 46, '#facc15');
+ for (var i = 0; i < upgradeData.length; i++) {
+ (function (data, index) {
+ var level = getUpgradeLevel(data.key);
+ var cost = getUpgradeCost(data.key, data.baseCost);
+ var card = scene.addChild(LK.getAsset('upgradeCard', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ }));
+ card.x = 560 + index % 2 * 930;
+ card.y = 1510 + Math.floor(index / 2) * 150;
+ makeLabel(scene, data.label + ' Lv.' + level, card.x, card.y - 22, 30, '#FFFFFF');
+ makeLabel(scene, level >= data.maxLevel ? 'MAXED' : 'Buy: ' + formatCash(cost), card.x, card.y + 20, 24, level >= data.maxLevel ? '#facc15' : '#22c55e');
+ card.down = function () {
+ buyUpgrade(data);
};
- })(part, upgradePart);
+ })(upgradeData[i], i);
}
- var raceButtonContainer = garageContainer.addChild(new Container());
- raceButtonContainer.x = 1024;
- raceButtonContainer.y = 2550;
- var raceButton = raceButtonContainer.addChild(LK.getAsset('button', {
+ var raceBtn = scene.addChild(LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
}));
- var raceButtonText = raceButtonContainer.addChild(new Text2('RACE', {
- size: 40,
- fill: '#000000'
- }));
- raceButtonText.anchor.set(0.5, 0.5);
- raceButtonContainer.down = function () {
- startRace();
+ raceBtn.x = WIDTH / 2;
+ raceBtn.y = 2400;
+ makeLabel(scene, 'START RACE', WIDTH / 2, 2400, 40, '#08110d');
+ raceBtn.down = function () {
+ startCountdown();
};
}
-function purchaseUpgrade(part) {
- if ((storage.playerCash || 0) >= part.cost) {
- storage.playerCash = (storage.playerCash || 0) - part.cost;
- storage[part.category + 'Level'] = (storage[part.category + 'Level'] || 0) + 1;
- LK.getSound('cashReward').play();
- garageContainer.destroy();
- initGarage();
- }
+function buyUpgrade(data) {
+ var level = getUpgradeLevel(data.key);
+ if (level >= data.maxLevel) return;
+ var cost = getUpgradeCost(data.key, data.baseCost);
+ if ((storage.playerCash || 0) < cost) return;
+ storage.playerCash -= cost;
+ storage[data.key + 'Level'] = level + 1;
+ LK.getSound('cashReward').play();
+ initGarage();
}
-function resetRaceState() {
- playerSpeed = 0;
- opponentSpeed = 0;
- playerShiftedGears = 0;
- opponentShiftedGears = 0;
- playerRPM = 1000;
- opponentRPM = 0;
- playerFinished = false;
- opponentFinished = false;
- playerDistance = 0;
- opponentDistance = 0;
- raceStarted = false;
- launchPhaseActive = false;
- launchCountdown = 3;
- reactionTime = 0;
- launchQuality = 'poor';
-}
-function startRace() {
- garageContainer.destroy();
- gameState = 'launch';
+/****
+* Countdown / launch
+****/
+function startCountdown() {
+ resetRaceValues();
+ clearScene();
+ currentState = 'countdown';
LK.playMusic('raceMusic', {
loop: true
});
- initLaunchPhase();
-}
-function initLaunchPhase() {
- launchContainer = game.addChild(new Container());
- var titleText = launchContainer.addChild(new Text2('GET READY TO LAUNCH', {
- size: 60,
- fill: '#FFFFFF'
+ scene = game.addChild(new Container());
+ var track = scene.addChild(LK.getAsset('track', {
+ anchorX: 0.5,
+ anchorY: 0.5
}));
- titleText.anchor.set(0.5, 0.5);
- titleText.x = 1024;
- titleText.y = 400;
- var countdownText = launchContainer.addChild(new Text2(String(launchCountdown), {
- size: 100,
- fill: '#FF0000'
- }));
- countdownText.anchor.set(0.5, 0.5);
- countdownText.x = 1024;
- countdownText.y = 1200;
- var instructionText = launchContainer.addChild(new Text2('TAP TO LAUNCH\nOptimal RPM: ' + optimalRPMMin + ' - ' + optimalRPMMax, {
- size: 35,
- fill: '#FFFF00'
- }));
- instructionText.anchor.set(0.5, 0.5);
- instructionText.x = 1024;
- instructionText.y = 1800;
- var rpmText = launchContainer.addChild(new Text2('RPM: ' + playerRPM, {
- size: 40,
- fill: '#00FF00'
- }));
- rpmText.anchor.set(0.5, 0.5);
- rpmText.x = 1024;
- rpmText.y = 2000;
- launchPhaseActive = true;
- var launchTimer = LK.setInterval(function () {
- launchCountdown--;
- if (launchCountdown <= 0) {
- LK.clearInterval(launchTimer);
- launchContainer.destroy();
- initRacePhase();
- } else {
- countdownText.setText(String(launchCountdown));
- }
- }, 1000);
- playerRPM = 1000;
- var rpmIncrement = LK.setInterval(function () {
- if (launchPhaseActive) {
- playerRPM = Math.min(7000, playerRPM + 200);
- rpmText.setText('RPM: ' + Math.round(playerRPM));
- }
- }, 100);
- game.down = function (x, y, obj) {
- if (launchPhaseActive && playerRPM >= optimalRPMMin && playerRPM <= optimalRPMMax) {
- reactionTime = (playerRPM - optimalRPMMin) / (optimalRPMMax - optimalRPMMin);
- reactionTime = Math.abs(0.5 - reactionTime) * 200;
- launchQuality = reactionTime < 50 ? 'perfect' : reactionTime < 150 ? 'good' : 'poor';
- launchPhaseActive = false;
- LK.clearInterval(launchTimer);
- LK.clearInterval(rpmIncrement);
- LK.getSound('launch').play();
- }
- };
-}
-function initRacePhase() {
- gameState = 'race';
- raceContainer = game.addChild(new Container());
- playerCar = raceContainer.addChild(new PlayerCar());
- playerCar.x = 300;
- playerCar.y = 600;
- playerCar.setColor(storage.carColor);
- opponentCar = raceContainer.addChild(new OpponentCar());
- opponentCar.x = 1750;
- opponentCar.y = 600;
- opponentCar.difficulty = (storage.playerTier || 0) + 1;
- var finishLine = raceContainer.addChild(LK.getAsset('finishLine', {
+ track.x = WIDTH / 2;
+ track.y = 1500;
+ var finish = scene.addChild(LK.getAsset('finishLine', {
anchorX: 0.5,
anchorY: 0.5
}));
- finishLine.x = 1024;
- finishLine.y = 200;
- var roadLines = [];
- for (var i = 0; i < 20; i++) {
- var roadLine = raceContainer.addChild(LK.getAsset('roadLine', {
+ finish.x = 1760;
+ finish.y = 1500;
+ playerCar = scene.addChild(createCar(false));
+ playerCar.x = 340;
+ playerCar.y = 1380;
+ playerCar.setColor(storage.carColor || 0xdc2626);
+ opponentCar = scene.addChild(createCar(true));
+ opponentCar.x = 340;
+ opponentCar.y = 1620;
+ makeLabel(scene, 'LAUNCH ON GREEN', WIDTH / 2, 180, 58, '#FFFFFF');
+ makeLabel(scene, 'Tap too early = weak launch. Tap on green with strong RPM = best launch.', WIDTH / 2, 255, 28, '#facc15');
+ var lightX = WIDTH / 2;
+ var startY = 520;
+ launchLights = [];
+ for (var i = 0; i < 3; i++) {
+ var light = scene.addChild(LK.getAsset('lightOff', {
anchorX: 0.5,
anchorY: 0.5
}));
- roadLine.x = 1024;
- roadLine.y = 300 + i * 200;
- roadLines.push(roadLine);
+ light.x = lightX;
+ light.y = startY + i * 72;
+ launchLights.push(light);
}
- var speedText = raceContainer.addChild(new Text2('Speed: 0 mph', {
- size: 40,
- fill: '#00FF00'
- }));
- speedText.anchor.set(0, 0);
- speedText.x = 50;
- speedText.y = 100;
- var distanceText = raceContainer.addChild(new Text2('Distance: 0m', {
- size: 40,
- fill: '#00FF00'
- }));
- distanceText.anchor.set(0, 0);
- distanceText.x = 50;
- distanceText.y = 200;
- var gearText = raceContainer.addChild(new Text2('Gear: 1', {
- size: 40,
- fill: '#FFFF00'
- }));
- gearText.anchor.set(0, 0);
- gearText.x = 50;
- gearText.y = 300;
- var shiftPromptText = raceContainer.addChild(new Text2('', {
- size: 50,
- fill: '#FF0000'
- }));
- shiftPromptText.anchor.set(0.5, 0);
- shiftPromptText.x = 1024;
- shiftPromptText.y = 400;
- playerSpeed = 0;
- opponentSpeed = 0;
- playerDistance = 0;
- opponentDistance = 0;
- playerFinished = false;
- opponentFinished = false;
+ rpmText = makeLabel(scene, 'RPM: 1000', WIDTH / 2, 920, 42, '#22c55e');
+ promptText = makeLabel(scene, 'Hold steady and tap on GREEN', WIDTH / 2, 1000, 34, '#FFFFFF');
+ game.down = function () {
+ if (currentState !== 'countdown') return;
+ if (countdownIndex < 3) {
+ launchQuality = 'FOUL';
+ launchAccuracy = 0;
+ playerLaunched = true;
+ beginRace();
+ return;
+ }
+ playerLaunched = true;
+ var rpmScore = 1 - Math.min(1, Math.abs(5000 - playerRPM) / 2200);
+ launchAccuracy = Math.max(0, rpmScore);
+ if (launchAccuracy > 0.88) launchQuality = 'PERFECT';else if (launchAccuracy > 0.68) launchQuality = 'GOOD';else if (launchAccuracy > 0.45) launchQuality = 'OK';else launchQuality = 'POOR';
+ LK.getSound('launch').play();
+ beginRace();
+ };
+ game.update = function () {
+ if (currentState !== 'countdown') return;
+ countdownElapsed += 1 / 60;
+ playerRPM += 85;
+ if (playerRPM > 7000) playerRPM = 3200;
+ rpmText.setText('RPM: ' + Math.floor(playerRPM));
+ if (countdownElapsed >= 0.75 && countdownIndex < 3) {
+ countdownElapsed = 0;
+ launchLights[countdownIndex].destroy();
+ launchLights[countdownIndex] = scene.addChild(LK.getAsset(countdownIndex < 2 ? 'lightRed' : 'lightGreen', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ }));
+ launchLights[countdownIndex].x = WIDTH / 2;
+ launchLights[countdownIndex].y = 520 + countdownIndex * 72;
+ countdownIndex++;
+ }
+ if (countdownIndex >= 3 && !playerLaunched && countdownElapsed >= 0.40) {
+ launchQuality = 'LATE';
+ launchAccuracy = 0.2;
+ playerLaunched = true;
+ beginRace();
+ }
+ };
+}
+/****
+* Race
+****/
+function beginRace() {
+ currentState = 'race';
raceStarted = true;
- playerShiftedGears = 0;
- opponentShiftedGears = 0;
- var playerStats = playerCar.getStats();
- var opponentStats = opponentCar.getStats();
- var acceleration = playerStats.torque / playerStats.weight * 0.05;
- var opponentAcceleration = opponentStats.torque / opponentStats.weight * 0.05;
- var shiftTimer = null;
- var nextShiftDistance = 300;
- game.down = function (x, y, obj) {
- if (raceStarted && playerDistance >= nextShiftDistance && playerDistance < raceDistance) {
- playerShiftedGears++;
- nextShiftDistance += 400;
- shiftPromptText.setText('');
+ raceFinished = false;
+ raceTimer = 0;
+ playerCanShift = false;
+ var playerStats = getPlayerStats();
+ var aiStats = getOpponentStats();
+ speedText = makeLabel(scene, 'Speed: 0 mph', 150, 120, 34, '#22c55e', 0, 0.5);
+ gearText = makeLabel(scene, 'Gear: 1', 150, 170, 34, '#facc15', 0, 0.5);
+ distanceText = makeLabel(scene, 'Distance: 0 m', 150, 220, 34, '#FFFFFF', 0, 0.5);
+ promptText.setText('Tap to shift in the sweet spot');
+ game.down = function () {
+ if (currentState !== 'race' || !raceStarted || raceFinished) return;
+ if (playerCanShift) {
+ playerGear++;
+ playerCanShift = false;
+ playerRPM = 4200 - getUpgradeLevel('transmission') * 120;
LK.getSound('shiftGear').play();
+ promptText.setText('GOOD SHIFT');
}
};
game.update = function () {
- if (!raceStarted) {
- return;
+ if (currentState !== 'race' || raceFinished) return;
+ raceTimer += 1 / 60;
+ var playerLaunchBoost = 0.75 + launchAccuracy * 0.65;
+ var playerAccel = playerStats.torque / playerStats.mass * 2.4 * playerLaunchBoost / Math.max(1, playerGear * 0.82);
+ var aiLaunchBoost = 0.92 + (storage.playerTier || 0) * 0.03;
+ var aiAccel = aiStats.torque / aiStats.mass * 2.15 * aiLaunchBoost / Math.max(1, opponentGear * 0.84);
+ playerRPM += 95 + playerGear * 24 + getUpgradeLevel('engine') * 6;
+ if (playerRPM >= playerShiftWindowMin && playerRPM <= playerShiftWindowMax) {
+ playerCanShift = true;
+ promptText.setText('SHIFT NOW');
+ } else if (playerRPM > playerShiftWindowMax + 350) {
+ playerCanShift = false;
+ promptText.setText('LATE SHIFT');
}
- if (!playerFinished) {
- playerSpeed = Math.min(playerStats.hp * 3, playerSpeed + acceleration);
- playerDistance += playerSpeed * 0.01;
- if (playerDistance >= nextShiftDistance && playerDistance < raceDistance && shiftPromptText.text === '') {
- shiftPromptText.setText('SHIFT!');
- }
- if (playerDistance >= raceDistance) {
- playerFinished = true;
- }
+ if (playerRPM > 7600) {
+ playerRPM = 5200;
+ playerSpeed *= 0.985;
}
- if (!opponentFinished) {
- var aiShiftTiming = Math.random() * 100 - 50;
- if (opponentDistance >= nextShiftDistance - aiShiftTiming && opponentShiftedGears === playerShiftedGears) {
- opponentShiftedGears++;
- }
- opponentSpeed = Math.min(opponentStats.hp * 3, opponentSpeed + opponentAcceleration);
- opponentDistance += opponentSpeed * 0.01;
- if (opponentDistance >= raceDistance) {
- opponentFinished = true;
- }
+ if (playerGear < 6) {
+ playerSpeed += playerAccel;
+ } else {
+ playerSpeed += playerAccel * 0.5;
}
- playerCar.x = 300 + playerDistance / raceDistance * 400;
- opponentCar.x = 1750 - opponentDistance / raceDistance * 400;
- speedText.setText('Speed: ' + Math.round(playerSpeed) + ' mph');
- distanceText.setText('Distance: ' + Math.round(playerDistance) + 'm');
- gearText.setText('Gear: ' + (playerShiftedGears + 1));
- for (var i = 0; i < roadLines.length; i++) {
- roadLines[i].y = (300 + i * 200 - playerDistance * 0.1) % 2400;
+ if (opponentGear < 6 && Math.random() < 0.025 + (storage.playerTier || 0) * 0.005) {
+ opponentGear++;
}
- if (playerFinished && opponentFinished) {
- raceStarted = false;
- raceContainer.destroy();
+ opponentSpeed += aiAccel;
+ playerSpeed = Math.min(playerSpeed, 60 + playerStats.hp * 0.62);
+ opponentSpeed = Math.min(opponentSpeed, 60 + aiStats.hp * 0.6);
+ playerDistance += playerSpeed * 0.17;
+ opponentDistance += opponentSpeed * 0.17;
+ playerCar.x = 340 + Math.min(1320, playerDistance * 0.42);
+ opponentCar.x = 340 + Math.min(1320, opponentDistance * 0.42);
+ playerCar.spinWheels(playerSpeed * 0.01);
+ opponentCar.spinWheels(opponentSpeed * 0.01);
+ speedText.setText('Speed: ' + Math.floor(playerSpeed) + ' mph');
+ gearText.setText('Gear: ' + playerGear);
+ distanceText.setText('Distance: ' + Math.floor(playerDistance) + ' m');
+ if (!playerFinishTime && playerDistance >= 3000) {
+ playerFinishTime = raceTimer;
+ }
+ if (!opponentFinishTime && opponentDistance >= 3000) {
+ opponentFinishTime = raceTimer;
+ }
+ if (playerFinishTime && opponentFinishTime) {
+ raceFinished = true;
showResults();
}
};
}
+/****
+* Results
+****/
function showResults() {
- gameState = 'results';
- resultsContainer = game.addChild(new Container());
- var playerStats = playerCar.getStats();
- var opponentStats = opponentCar.getStats();
- var playerFinalSpeed = playerSpeed > 0 ? playerSpeed : 1;
- var opponentFinalSpeed = opponentSpeed > 0 ? opponentSpeed : 1;
- var playerTime = raceDistance / playerFinalSpeed;
- var opponentTime = raceDistance / opponentFinalSpeed;
- var playerWon = playerDistance >= raceDistance && (opponentDistance < raceDistance || playerTime < opponentTime);
- var resultTitle = resultsContainer.addChild(new Text2(playerWon ? 'YOU WIN!' : 'YOU LOSE!', {
- size: 80,
- fill: playerWon ? '#00FF00' : '#FF0000'
- }));
- resultTitle.anchor.set(0.5, 0);
- resultTitle.x = 1024;
- resultTitle.y = 200;
- var reward = 0;
- if (playerWon) {
- reward = 1000 + storage.playerTier * 500 + Math.round(reactionTime * 10);
- storage.playerCash = (storage.playerCash || 0) + reward;
- if (storage.playerTier < 2) {
- storage.playerTier = (storage.playerTier || 0) + 1;
- }
- LK.getSound('raceWin').play();
- } else {
- reward = 500 + Math.round(reactionTime * 5);
- storage.playerCash = (storage.playerCash || 0) + reward;
- LK.getSound('raceLose').play();
+ currentState = 'results';
+ var playerWon = playerFinishTime <= opponentFinishTime;
+ var tier = storage.playerTier || 0;
+ var baseReward = playerWon ? 900 + tier * 250 : 300;
+ var launchReward = launchQuality === 'PERFECT' ? 300 : launchQuality === 'GOOD' ? 180 : launchQuality === 'OK' ? 100 : launchQuality === 'POOR' ? 40 : 0;
+ var timeBonus = Math.max(0, Math.floor((12 - playerFinishTime) * 80));
+ var reward = baseReward + launchReward + timeBonus;
+ storage.playerCash = (storage.playerCash || 0) + reward;
+ storage.raceCount = (storage.raceCount || 0) + 1;
+ if (playerWon && tier < 5) {
+ storage.playerTier = tier + 1;
}
- var resultDetails = ['Reaction Time: ' + Math.round(reactionTime) + 'ms (' + launchQuality + ')', 'Finish Time: ' + (raceDistance / playerSpeed).toFixed(2) + 's', 'Top Speed: ' + Math.round(playerSpeed) + ' mph', 'Gears Shifted: ' + (playerShiftedGears + 1), 'Earnings: $' + reward];
- for (var i = 0; i < resultDetails.length; i++) {
- var detailText = resultsContainer.addChild(new Text2(resultDetails[i], {
- size: 35,
- fill: '#FFFFFF'
- }));
- detailText.anchor.set(0.5, 0);
- detailText.x = 1024;
- detailText.y = 500 + i * 80;
+ if (!storage.bestTime || playerFinishTime < storage.bestTime) {
+ storage.bestTime = playerFinishTime;
}
- var continueButtonContainer = resultsContainer.addChild(new Container());
- continueButtonContainer.x = 1024;
- continueButtonContainer.y = 1800;
- var continueButton = continueButtonContainer.addChild(LK.getAsset('button', {
+ if (playerWon) LK.getSound('raceWin').play();else LK.getSound('raceLose').play();
+ clearScene();
+ scene = game.addChild(new Container());
+ makeLabel(scene, playerWon ? 'YOU WIN' : 'YOU LOSE', WIDTH / 2, 180, 86, playerWon ? '#22c55e' : '#ef4444');
+ makeLabel(scene, 'Launch: ' + launchQuality, WIDTH / 2, 420, 42, '#facc15');
+ makeLabel(scene, 'Your Time: ' + playerFinishTime.toFixed(2) + 's', WIDTH / 2, 520, 40, '#FFFFFF');
+ makeLabel(scene, 'Opponent Time: ' + opponentFinishTime.toFixed(2) + 's', WIDTH / 2, 600, 40, '#FFFFFF');
+ makeLabel(scene, 'Reward: ' + formatCash(reward), WIDTH / 2, 720, 48, '#22c55e');
+ makeLabel(scene, 'Best Time: ' + (storage.bestTime ? storage.bestTime.toFixed(2) + 's' : '--'), WIDTH / 2, 810, 34, '#93c5fd');
+ makeLabel(scene, 'Cash Total: ' + formatCash(storage.playerCash || 0), WIDTH / 2, 880, 34, '#FFFFFF');
+ var carPreview = scene.addChild(createCar(false));
+ carPreview.x = WIDTH / 2;
+ carPreview.y = 1300;
+ carPreview.scale.set(1.7);
+ carPreview.setColor(storage.carColor || 0xdc2626);
+ var garageBtn = scene.addChild(LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
}));
- var continueButtonText = continueButtonContainer.addChild(new Text2('BACK TO GARAGE', {
- size: 35,
- fill: '#000000'
- }));
- continueButtonText.anchor.set(0.5, 0.5);
- continueButtonContainer.down = function () {
- resultsContainer.destroy();
+ garageBtn.x = WIDTH / 2;
+ garageBtn.y = 2200;
+ makeLabel(scene, 'BACK TO GARAGE', WIDTH / 2, 2200, 38, '#08110d');
+ garageBtn.down = function () {
initGarage();
};
- game.update = function () {};
game.down = function () {};
+ game.update = function () {};
}
+/****
+* Start
+****/
initGarage();
\ No newline at end of file
Fullscreen modern App Store landscape banner, 16:9, high definition, for a game titled "Build, Race, Upgrade" and with the description "A stylized drag racing game where players build and tune cars in a garage, compete in timed drag races against AI opponents, and reinvest winnings into performance upgrades. Progression spans beginner, intermediate, and advanced race tiers with increasingly difficult rivals and larger payouts. Clean modular systems enable easy balancing and future expansion.". No text on banner!