User prompt
place a mute button to the top left corner to mute the music
User prompt
make the music play on loop until the game ends
User prompt
Create a background music track for a fictional stock market simulation game. Mood & Style: - Lightly tense, yet smooth and rhythmic — to reflect the flow of time and price fluctuations. - Slightly jazzy or lo-fi financial vibes with subtle synths or muted piano chords. - Include a gentle bassline and soft percussion to create a sense of ticking time or rising tension, without being distracting. - Avoid dramatic buildups or intense beats — it should feel like background music in a high-end trading floor or data center. Tempo: - Medium (around 90–110 BPM) to support long play sessions. - Add occasional melodic accents or stingers to mark "price shifts" or wave transitions.
User prompt
still no timer visible on screen
User prompt
didn't work
User prompt
15 minutes timer isn't displayed at the top of the screen
User prompt
move Profit column a tiny bit to the right
User prompt
make it 15 minutes and display at the top center of the screen
User prompt
all looks good except Change and Holdings are a bit tangled, move Holdings column a bit to the right
User prompt
fix shifted table positionings
User prompt
add a thin "last price change" column between price and holdings, show the last price change by percentage and mark them green or red
User prompt
add a thin "last price change" column between price and holdings, show the last price change by percentage and mark them green or red, also fix Holdings label positioning by getting them directly on top of share amounts
User prompt
fix the spacings that are shifted a bit after adding this column
User prompt
add a thin "last price change" column between price and holdings, show the last price change and mark them green or red
User prompt
the game ends suddenly do you know why?
User prompt
move the news section a bit lower
User prompt
- Total portfolio profit should be the sum of the current unrealized gains or losses of all owned shares, **based on their average purchase price versus the current price**. - This value should continue updating every time stock prices change, as long as the shares are still held. - Selling shares should realize the profit and remove that holding from profit tracking.
User prompt
Currently, when a player buys new shares, the profit from those shares is added immediately, even though the price hasn't changed yet. - This is incorrect. Newly purchased shares should not generate any profit or loss at the moment of purchase. - Instead, profit/loss should only be calculated based on price changes that occur after the next price update.
User prompt
Fix the profit calculation logic for portfolio holdings. There are two main issues to address: 1. **Immediate Profit Inclusion Bug**: - Currently, when a player buys new shares, the profit from those shares is added immediately, even though the price hasn't changed yet. - This is incorrect. Newly purchased shares should not generate any profit or loss at the moment of purchase. - Instead, profit/loss should only be calculated based on price changes that occur **after the next price update**. Fix this by tracking the purchase price of each batch of shares, and only calculating profit based on changes in value after that point. 2. **Total Profit Calculation**: - Total portfolio profit should be the sum of the current unrealized gains or losses of all owned shares, **based on their average purchase price versus the current price**. - This value should continue updating every time stock prices change, as long as the shares are still held. - Selling shares should realize the profit and remove that holding from profit tracking. Ensure that: - Each share batch uses the correct reference price (purchase price). - Profits are not “double counted” at buy and again at price change. - Total profit updates only due to price changes **after purchase**. Once this is fixed, we’ll add realized vs unrealized profit breakdowns later.
User prompt
Please make the following improvements to the company detail section that shows the chart and news when a company is selected: 1. Layout Adjustments: - Move the entire detail section (chart and news) slightly further down the screen so it doesn't feel cramped under the stock list. - Increase the size of both the chart and the news section for better visibility. 2. Chart Fix: - The chart should be a proper connected line chart, not just floating individual dots. - Connect the data points with smooth lines to show the price trend clearly. - Add numerical values along the Y-axis (stock price) and X-axis (time or tick number), so the player can understand the trend. 3. News Classification and Behavior: - Classify news headlines into two categories: positive and negative. - When a company’s stock price increases, randomly select a headline from the positive news pool. - When a company’s stock price decreases, randomly select a headline from the negative news pool. - Only display news that matches the current trend of the company. - Optional: If price stays the same, you can show neutral or humorous “no change” headlines. Make sure the news feed updates whenever the stock price changes and that the headlines feel connected to the price movement.
User prompt
let's implement it directly
User prompt
still nothing happens when I click company names
User prompt
it doesn't seem to be working
User prompt
go ahead
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// StockRow: A row in the stock table, with price, holdings, buy/sell buttons
var StockRow = Container.expand(function () {
var self = Container.call(this);
// Properties
self.companyIndex = 0; // 0-9
self.companyName = '';
self.colorId = '';
self.price = 0;
self.holdings = 0;
self.lastPrice = 0;
// UI elements
var yPad = 0;
// Stock icon
var icon = self.attachAsset(self.colorId, {
anchorX: 0.5,
anchorY: 0.5,
x: 80,
y: 60 + yPad,
scaleX: 0.7,
scaleY: 0.7
});
// Company name button background
var nameBtnBg = self.attachAsset('buyBtn', {
anchorX: 0,
anchorY: 0,
x: 160,
y: 20 + yPad,
width: 320,
height: 60,
color: 0x34495e
});
nameBtnBg.alpha = 0.25;
// Company name
var nameTxt = new Text2(self.companyName, {
size: 48,
fill: 0xFFFFFF
});
nameTxt.x = 170;
nameTxt.y = 30 + yPad;
nameTxt.anchor.set(0, 0);
self.addChild(nameTxt);
// Make the button background clickable for company details
nameBtnBg.interactive = true;
nameBtnBg.buttonMode = true;
nameBtnBg.down = function (x, y, obj) {
if (typeof showCompanyDetail === "function") {
showCompanyDetail(self.companyIndex);
}
};
// Price text
var priceTxt = new Text2('', {
size: 48,
fill: 0xF1C40F
});
priceTxt.x = 500;
priceTxt.y = 30 + yPad;
priceTxt.anchor.set(0, 0);
self.addChild(priceTxt);
// Last price change percent text (new column)
var changeTxt = new Text2('', {
size: 44,
fill: 0xFFFFFF
});
changeTxt.x = 670;
changeTxt.y = 34 + yPad;
changeTxt.anchor.set(0, 0);
self.addChild(changeTxt);
// Holdings text
var holdingsTxt = new Text2('', {
size: 48,
fill: 0x2ECC71
});
holdingsTxt.x = 880;
holdingsTxt.y = 30 + yPad;
holdingsTxt.anchor.set(0, 0);
self.addChild(holdingsTxt);
// Profit text
var profitTxt = new Text2('', {
size: 48,
fill: 0xFFFFFF
});
profitTxt.x = 1130;
profitTxt.y = 30 + yPad;
profitTxt.anchor.set(0, 0);
self.addChild(profitTxt);
// Buy button
var buyBtn = self.attachAsset('buyBtn', {
anchorX: 0.5,
anchorY: 0.5,
x: 1450,
y: 60 + yPad
});
var buyTxt = new Text2('Buy', {
size: 36,
fill: 0xFFFFFF
});
buyTxt.anchor.set(0.5, 0.5);
buyTxt.x = buyBtn.x;
buyTxt.y = buyBtn.y;
self.addChild(buyTxt);
// Amount textbox (between buy and sell)
var amountBoxBg = self.attachAsset('buyBtn', {
anchorX: 0.5,
anchorY: 0.5,
x: 1575,
y: 60 + yPad,
scaleX: 0.7,
scaleY: 0.7
});
var amountTxt = new Text2(typeof selectedShareAmount !== "undefined" ? '' + selectedShareAmount : '1', {
size: 36,
fill: 0x222a36
});
amountTxt.anchor.set(0.5, 0.5);
amountTxt.x = amountBoxBg.x;
amountTxt.y = amountBoxBg.y;
self.addChild(amountTxt);
// Touch to edit amount (prompt for number)
amountBoxBg.down = function (x, y, obj) {
// On mobile, prompt is supported in LK sandbox for numeric input
var current = parseInt(amountTxt.text, 10);
if (isNaN(current) || current < 1) current = 1;
var entered = prompt("Enter number of shares:", current);
var val = parseInt(entered, 10);
if (isNaN(val) || val < 1) val = 1;
amountTxt.setText('' + val);
// Also update global selectedShareAmount and highlight if matches a selector
if (typeof selectedShareAmount !== "undefined") {
selectedShareAmount = val;
if (typeof shareAmountBtnBg !== "undefined" && typeof shareAmounts !== "undefined") {
for (var j = 0; j < shareAmountBtnBg.length; j++) {
shareAmountBtnBg[j].tint = shareAmounts[j] === selectedShareAmount ? 0xf1c40f : 0x2980b9;
}
}
// Update all amount boxes to match
if (typeof stockRows !== "undefined") {
for (var k = 0; k < stockRows.length; k++) {
if (stockRows[k].setAmountBoxValue) {
stockRows[k].setAmountBoxValue(selectedShareAmount);
}
}
}
}
};
// Method to allow global selector to update this textbox
self.setAmountBoxValue = function (val) {
if (typeof val === "number" && val > 0) {
amountTxt.setText('' + val);
}
};
// Sell button
var sellBtn = self.attachAsset('sellBtn', {
anchorX: 0.5,
anchorY: 0.5,
x: 1700,
y: 60 + yPad
});
var sellTxt = new Text2('Sell', {
size: 36,
fill: 0xFFFFFF
});
sellTxt.anchor.set(0.5, 0.5);
sellTxt.x = sellBtn.x;
sellTxt.y = sellBtn.y;
self.addChild(sellTxt);
// Update UI
self.updateUI = function () {
nameTxt.setText(self.companyName);
priceTxt.setText('₲' + self.price);
// Show last price change percent and color
var pctChange = 0;
if (self.lastPrice && self.lastPrice !== 0) {
pctChange = (self.price - self.lastPrice) / self.lastPrice * 100;
}
var pctStr = '';
var pctColor = "#BDC3C7";
if (pctChange > 0.01) {
pctStr = '+' + pctChange.toFixed(2) + '%';
pctColor = "#2ecc71";
} else if (pctChange < -0.01) {
pctStr = pctChange.toFixed(2) + '%';
pctColor = "#e74c3c";
} else {
pctStr = '+0.00%';
pctColor = "#BDC3C7";
}
changeTxt.setText(pctStr);
changeTxt.setStyle({
fill: pctColor
});
holdingsTxt.setText(self.holdings + ' shares');
// Calculate average cost for this stock
var lots = playerLots[self.companyIndex];
var totalShares = 0;
var totalCost = 0;
for (var i = 0; i < lots.length; i++) {
totalShares += lots[i].shares;
totalCost += lots[i].shares * lots[i].price;
}
var avgCost = totalShares > 0 ? totalCost / totalShares : 0;
// Profit is unrealized gain/loss: (current price - avgCost) * shares held
var profit = totalShares > 0 ? Math.round((self.price - avgCost) * totalShares) : 0;
var profitColor = "#ffffff";
if (profit > 0) profitColor = "#2ecc71";
if (profit < 0) profitColor = "#e74c3c";
profitTxt.setText((profit >= 0 ? '+' : '') + profit);
// Use setStyle to update fill color safely
profitTxt.setStyle({
fill: profitColor
});
};
// Buy handler
buyBtn.down = function (x, y, obj) {
// Use global selectedShareAmount for buy
var amount = typeof selectedShareAmount !== "undefined" && selectedShareAmount > 0 ? selectedShareAmount : 1;
var maxBuy = Math.floor(playerCash / self.price);
var buyCount = Math.min(amount, maxBuy);
if (buyCount > 0) {
playerCash -= self.price * buyCount;
self.holdings += buyCount;
// Add a new lot for these shares at current price
var lots = playerLots[self.companyIndex];
lots.push({
shares: buyCount,
price: self.price
});
// Clean up: merge lots with same price (optional, for tidiness)
var merged = [];
for (var i = 0; i < lots.length; i++) {
var found = false;
for (var j = 0; j < merged.length; j++) {
if (merged[j].price === lots[i].price) {
merged[j].shares += lots[i].shares;
found = true;
break;
}
}
if (!found) merged.push({
shares: lots[i].shares,
price: lots[i].price
});
}
playerLots[self.companyIndex] = merged;
self.updateUI();
updateCashUI();
}
};
// Sell handler
sellBtn.down = function (x, y, obj) {
// Use global selectedShareAmount for sell
var amount = typeof selectedShareAmount !== "undefined" && selectedShareAmount > 0 ? selectedShareAmount : 1;
var sellCount = Math.min(amount, self.holdings);
if (sellCount > 0) {
playerCash += self.price * sellCount;
self.holdings -= sellCount;
// Remove shares from lots (FIFO)
var lots = playerLots[self.companyIndex];
var toSell = sellCount;
var idx = 0;
while (toSell > 0 && idx < lots.length) {
if (lots[idx].shares <= toSell) {
toSell -= lots[idx].shares;
lots[idx].shares = 0;
idx++;
} else {
lots[idx].shares -= toSell;
toSell = 0;
}
}
// Remove empty lots
var newLots = [];
for (var i = 0; i < lots.length; i++) {
if (lots[i].shares > 0) newLots.push(lots[i]);
}
playerLots[self.companyIndex] = newLots;
self.updateUI();
updateCashUI();
}
};
// For updating price and profit
self.setPrice = function (newPrice) {
self.lastPrice = self.price;
self.price = newPrice;
// Update holdings from lots (in case lots changed externally)
var lots = playerLots[self.companyIndex];
var totalShares = 0;
for (var i = 0; i < lots.length; i++) {
totalShares += lots[i].shares;
}
self.holdings = totalShares;
self.updateUI();
};
// For updating holdings externally
self.setHoldings = function (newHoldings) {
self.holdings = newHoldings;
// Also reset lots to match (used for reset/game start)
var lots = playerLots[self.companyIndex];
lots.length = 0;
if (newHoldings > 0) {
lots.push({
shares: newHoldings,
price: self.price
});
}
self.updateUI();
};
// For updating profit display externally
self.updateProfit = function () {
self.updateUI();
};
// For setting up after construction
self.init = function (companyIndex, companyName, colorId, price) {
self.companyIndex = companyIndex;
self.companyName = companyName;
self.colorId = colorId;
self.price = price;
self.lastPrice = price;
icon.setAsset(colorId);
self.updateUI();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222a36
});
/****
* Game Code
****/
// Music: Lightly tense, smooth, jazzy/lo-fi, gentle bass, soft percussion, subtle synths
// --- Game Data ---
// 10 company colors for stocks (distinct, visually clear)
// Button shapes
var companyNames = ["AlphaTech", "BetaBank", "GammaFoods", "DeltaPharma", "EpsilonEnergy", "ZetaAuto", "EtaRetail", "ThetaMedia", "IotaLogix", "KappaSpace"];
var companyColors = ['stockA', 'stockB', 'stockC', 'stockD', 'stockE', 'stockF', 'stockG', 'stockH', 'stockI', 'stockJ'];
// Initial prices (randomized a bit for variety)
var initialPrices = [];
for (var i = 0; i < 10; i++) {
initialPrices[i] = 80 + Math.floor(Math.random() * 40) * 5; // 80-280
}
// Player state
var playerCash = 10000;
// playerHoldings: number of shares per stock (for UI/wealth only)
var playerHoldings = [];
for (var i = 0; i < 10; i++) playerHoldings[i] = 0;
// playerLots: for each stock, an array of {shares, price} objects representing purchase lots
var playerLots = [];
for (var i = 0; i < 10; i++) playerLots[i] = [];
// Stock state
var stockPrices = [];
var stockLastPrices = [];
for (var i = 0; i < 10; i++) {
stockPrices[i] = initialPrices[i];
stockLastPrices[i] = initialPrices[i];
}
// Timer
var gameDurationSec = 900; // 15 minutes
var timeLeft = gameDurationSec;
var timerInterval = null;
// UI elements
var cashTxt = null;
var timerTxt = null;
var wealthTxt = null;
var stockRows = [];
var tableContainer = null;
// --- UI Setup ---
// Cash display (top right, avoid top left 100x100)
cashTxt = new Text2('Cash: ₲' + playerCash, {
size: 72,
fill: 0xF1C40F
});
cashTxt.anchor.set(1, 0);
cashTxt.x = 0; // x/y are ignored for gui.topRight, but set to 0 for clarity
cashTxt.y = 0;
LK.gui.topRight.addChild(cashTxt);
// Timer display (top center)
timerTxt = new Text2('15:00', {
size: 72,
fill: 0xFFFFFF
});
timerTxt.anchor.set(0.5, 0);
// Do not set x/y directly, let LK.gui.top handle centering
LK.gui.top.addChild(timerTxt);
// Wealth display (below timer)
wealthTxt = new Text2('Wealth: ₲' + playerCash, {
size: 56,
fill: 0x2ECC71
});
wealthTxt.anchor.set(0.5, 0);
wealthTxt.x = 2048 / 2;
wealthTxt.y = 130;
LK.gui.top.addChild(wealthTxt);
// Table container (centered, scroll not needed for 10 rows)
tableContainer = new Container();
tableContainer.x = 0;
tableContainer.y = 300;
game.addChild(tableContainer);
// Table header
var headerY = 0;
// Table header columns for better alignment
var headerX = [160, 500, 670, 880, 1130]; // Company, Price, Change, Holdings, Profit (Profit moved from 1100 to 1130)
var headerTitles = ["Company", "Price", "Change", "Holdings", "Profit"];
for (var i = 0; i < headerTitles.length; i++) {
var colHeader = new Text2(headerTitles[i], {
size: 48,
fill: 0xBDC3C7
});
colHeader.anchor.set(0, 0);
colHeader.x = headerX[i];
colHeader.y = headerY;
tableContainer.addChild(colHeader);
}
// Total Profit/Loss label and display (at end of profit column)
var totalProfitLabel = new Text2('Total Profit:', {
size: 48,
fill: 0xBDC3C7
});
totalProfitLabel.anchor.set(1, 0);
totalProfitLabel.x = 1130;
totalProfitLabel.y = 80 + 10 * 120 + 10;
tableContainer.addChild(totalProfitLabel);
var totalProfitTxt = new Text2('', {
size: 48,
fill: 0xFFFFFF
});
totalProfitTxt.anchor.set(0, 0);
totalProfitTxt.x = 1150;
totalProfitTxt.y = 80 + 10 * 120 + 10;
tableContainer.addChild(totalProfitTxt);
// --- Global Share Amount Selector Buttons ---
var shareAmounts = [1, 5, 10, 25];
var selectedShareAmount = 1;
var shareAmountBtns = [];
var shareAmountBtnTxts = [];
var shareAmountBtnBg = [];
var shareAmountBtnY = 10; // y offset above first row
var shareAmountBtnX0 = 1575; // align with amount box column
var shareAmountBtnSpacing = 110;
var shareAmountBtnW = 90;
var shareAmountBtnH = 60;
var shareAmountBtnColor = 0x2980b9;
var shareAmountBtnColorSelected = 0xf1c40f;
// Container for selector buttons
var selectorBtnContainer = new Container();
selectorBtnContainer.x = shareAmountBtnX0 - shareAmountBtnSpacing * 1.5;
selectorBtnContainer.y = shareAmountBtnY;
tableContainer.addChild(selectorBtnContainer);
for (var i = 0; i < shareAmounts.length; i++) {
// Use buyBtn shape for button background
var btnBg = LK.getAsset('buyBtn', {
anchorX: 0.5,
anchorY: 0.5,
x: i * shareAmountBtnSpacing,
y: 0,
width: shareAmountBtnW,
height: shareAmountBtnH
});
btnBg.tint = shareAmounts[i] === selectedShareAmount ? shareAmountBtnColorSelected : shareAmountBtnColor;
selectorBtnContainer.addChild(btnBg);
shareAmountBtnBg.push(btnBg);
// Button label
var btnTxt = new Text2('' + shareAmounts[i], {
size: 36,
fill: 0xFFFFFF
});
btnTxt.anchor.set(0.5, 0.5);
btnTxt.x = btnBg.x;
btnTxt.y = btnBg.y;
selectorBtnContainer.addChild(btnTxt);
shareAmountBtnTxts.push(btnTxt);
// Button logic
(function (idx, amount) {
btnBg.down = function (x, y, obj) {
selectedShareAmount = amount;
// Update highlight
for (var j = 0; j < shareAmountBtnBg.length; j++) {
shareAmountBtnBg[j].tint = shareAmounts[j] === selectedShareAmount ? shareAmountBtnColorSelected : shareAmountBtnColor;
}
// Update all amount textboxes in all rows
for (var k = 0; k < stockRows.length; k++) {
if (stockRows[k].setAmountBoxValue) {
stockRows[k].setAmountBoxValue(selectedShareAmount);
}
}
};
})(i, shareAmounts[i]);
shareAmountBtns.push(btnBg);
}
// --- Stock Rows ---
for (var i = 0; i < 10; i++) {
var row = new StockRow();
row.init(i, companyNames[i], companyColors[i], stockPrices[i]);
row.y = 80 + i * 120;
tableContainer.addChild(row);
stockRows.push(row);
}
// --- Company Detail Section (Chart + News) ---
// Price history for each company (last 20 prices)
var companyPriceHistory = [];
for (var i = 0; i < 10; i++) {
companyPriceHistory[i] = [];
for (var j = 0; j < 20; j++) companyPriceHistory[i].push(stockPrices[i]);
}
// Fictional news headlines for each company, classified as positive/negative/neutral
var companyNews = [{
positive: ["AlphaTech launches new AI chip", "AlphaTech CEO: 'Innovation is our DNA'", "AlphaTech partners with BetaBank", "AlphaTech stock soars on strong earnings", "AlphaTech unveils breakthrough technology"],
negative: ["AlphaTech faces supply chain delays", "AlphaTech stock dips after market uncertainty", "AlphaTech recalls product line", "AlphaTech under investigation for patent dispute"],
neutral: ["AlphaTech holds annual shareholder meeting", "AlphaTech: No major news today"]
}, {
positive: ["BetaBank expands to new markets", "BetaBank reports record profits", "BetaBank launches mobile app", "BetaBank receives top customer service award"],
negative: ["BetaBank fined for compliance issues", "BetaBank stock falls after earnings miss", "BetaBank faces cyberattack"],
neutral: ["BetaBank: No significant changes reported"]
}, {
positive: ["GammaFoods unveils plant-based burger", "GammaFoods opens 100th store", "GammaFoods: 'Healthy eating for all'", "GammaFoods sales hit all-time high"],
negative: ["GammaFoods faces supply shortage", "GammaFoods stock drops after recall", "GammaFoods reports lower quarterly profits"],
neutral: ["GammaFoods: No major news today"]
}, {
positive: ["DeltaPharma vaccine approved", "DeltaPharma acquires Medix", "DeltaPharma Q2 profits soar", "DeltaPharma receives innovation award"],
negative: ["DeltaPharma faces regulatory setback", "DeltaPharma stock falls on trial results", "DeltaPharma issues product warning"],
neutral: ["DeltaPharma: No significant news"]
}, {
positive: ["EpsilonEnergy invests in solar", "EpsilonEnergy wins green award", "EpsilonEnergy: Oil prices stable", "EpsilonEnergy expands wind farm"],
negative: ["EpsilonEnergy stock drops on oil price fall", "EpsilonEnergy faces environmental protest", "EpsilonEnergy reports lower revenue"],
neutral: ["EpsilonEnergy: No major news today"]
}, {
positive: ["ZetaAuto reveals electric SUV", "ZetaAuto sales up 20%", "ZetaAuto opens new factory", "ZetaAuto receives safety award"],
negative: ["ZetaAuto recalls vehicles", "ZetaAuto stock dips after earnings", "ZetaAuto faces supply chain issues"],
neutral: ["ZetaAuto: No significant news"]
}, {
positive: ["EtaRetail launches online store", "EtaRetail Black Friday success", "EtaRetail expands to Europe", "EtaRetail reports record sales"],
negative: ["EtaRetail faces data breach", "EtaRetail stock falls on weak quarter", "EtaRetail closes underperforming stores"],
neutral: ["EtaRetail: No major news today"]
}, {
positive: ["ThetaMedia signs streaming deal", "ThetaMedia launches new channel", "ThetaMedia ad revenue climbs", "ThetaMedia wins industry award"],
negative: ["ThetaMedia stock drops after ratings slip", "ThetaMedia faces copyright lawsuit", "ThetaMedia cuts staff"],
neutral: ["ThetaMedia: No significant news"]
}, {
positive: ["IotaLogix automates warehouses", "IotaLogix wins logistics award", "IotaLogix: 'Efficiency first'", "IotaLogix expands robotics division"],
negative: ["IotaLogix stock falls on earnings miss", "IotaLogix faces labor strike", "IotaLogix recalls faulty robots"],
neutral: ["IotaLogix: No major news today"]
}, {
positive: ["KappaSpace launches satellite", "KappaSpace partners with NASA", "KappaSpace: 'Space for everyone'", "KappaSpace secures new contracts"],
negative: ["KappaSpace rocket launch fails", "KappaSpace stock dips after delay", "KappaSpace faces funding shortfall"],
neutral: ["KappaSpace: No significant news"]
}];
// Container for detail section
var companyDetailSection = new Container();
// Move detail section further down for more space
companyDetailSection.x = 0;
companyDetailSection.y = tableContainer.y + 80 + 10 * 120 + 120; // more padding below last row
game.addChild(companyDetailSection);
companyDetailSection.visible = false; // hidden by default
// Chart and news containers, both larger and spaced out
var chartContainer = new Container();
chartContainer.x = 120;
chartContainer.y = 0;
companyDetailSection.addChild(chartContainer);
var newsContainer = new Container();
newsContainer.x = 900; // move news further right for larger chart
newsContainer.y = 80; // move news section a bit lower
companyDetailSection.addChild(newsContainer);
// Helper to clear chart/news
function clearCompanyDetailSection() {
while (chartContainer.children.length > 0) chartContainer.removeChild(chartContainer.children[0]);
while (newsContainer.children.length > 0) newsContainer.removeChild(newsContainer.children[0]);
}
// Draw chart for companyIndex
function drawCompanyChart(companyIndex) {
clearCompanyDetailSection();
// Chart area: larger for better visibility
var chartW = 700,
chartH = 320;
var margin = 60;
var history = companyPriceHistory[companyIndex];
// Find min/max for scaling
var minP = history[0],
maxP = history[0];
for (var i = 1; i < history.length; i++) {
if (history[i] < minP) minP = history[i];
if (history[i] > maxP) maxP = history[i];
}
if (maxP === minP) maxP = minP + 1; // avoid div0
// Draw axes (Y and X)
var axisColor = 0xBDC3C7;
// Y axis
var yAxis = LK.getAsset('buyBtn', {
anchorX: 0,
anchorY: 0,
x: margin - 4,
y: margin,
width: 8,
height: chartH - 2 * margin,
color: axisColor
});
yAxis.alpha = 0.5;
chartContainer.addChild(yAxis);
// X axis
var xAxis = LK.getAsset('buyBtn', {
anchorX: 0,
anchorY: 0,
x: margin,
y: chartH - margin - 4,
width: chartW - 2 * margin,
height: 8,
color: axisColor
});
xAxis.alpha = 0.5;
chartContainer.addChild(xAxis);
// Draw Y-axis labels (min, mid, max)
var yLabels = [{
val: maxP,
y: margin - 20
}, {
val: Math.round((maxP + minP) / 2),
y: (chartH - 2 * margin) / 2 + margin - 20
}, {
val: minP,
y: chartH - margin - 20
}];
for (var i = 0; i < yLabels.length; i++) {
var yLabel = new Text2('₲' + yLabels[i].val, {
size: 28,
fill: 0xBDC3C7
});
yLabel.anchor.set(1, 0.5);
yLabel.x = margin - 10;
yLabel.y = yLabels[i].y + 16;
chartContainer.addChild(yLabel);
}
// Draw X-axis labels (first, mid, last tick)
var xLabels = [{
val: 1,
x: margin
}, {
val: Math.floor(history.length / 2) + 1,
x: margin + (chartW - 2 * margin) / 2
}, {
val: history.length,
x: chartW - margin
}];
for (var i = 0; i < xLabels.length; i++) {
var xLabel = new Text2('' + xLabels[i].val, {
size: 28,
fill: 0xBDC3C7
});
xLabel.anchor.set(0.5, 0);
xLabel.x = xLabels[i].x;
xLabel.y = chartH - margin + 12;
chartContainer.addChild(xLabel);
}
// Draw connected line (simulate with small rectangles between points)
for (var i = 1; i < history.length; i++) {
var px0 = margin + (chartW - 2 * margin) * ((i - 1) / (history.length - 1));
var py0 = margin + (chartH - 2 * margin) * (1 - (history[i - 1] - minP) / (maxP - minP));
var px1 = margin + (chartW - 2 * margin) * (i / (history.length - 1));
var py1 = margin + (chartH - 2 * margin) * (1 - (history[i] - minP) / (maxP - minP));
// Draw a thin rectangle between (px0,py0) and (px1,py1)
var dx = px1 - px0;
var dy = py1 - py0;
var len = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
var line = LK.getAsset('buyBtn', {
anchorX: 0.5,
anchorY: 0.5,
x: (px0 + px1) / 2,
y: (py0 + py1) / 2,
width: len,
height: 8,
color: 0xF1C40F
});
line.rotation = angle;
line.alpha = 0.8;
chartContainer.addChild(line);
}
// Draw points (dots) on top of the line
for (var i = 0; i < history.length; i++) {
var px = margin + (chartW - 2 * margin) * (i / (history.length - 1));
var py = margin + (chartH - 2 * margin) * (1 - (history[i] - minP) / (maxP - minP));
var dot = LK.getAsset(companyColors[companyIndex], {
anchorX: 0.5,
anchorY: 0.5,
x: px,
y: py,
width: 18,
height: 18
});
chartContainer.addChild(dot);
}
// Chart border
var border = LK.getAsset('buyBtn', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: chartW,
height: chartH,
color: 0x222a36
});
border.alpha = 0.3;
chartContainer.addChild(border);
// Chart title
var chartTitle = new Text2(companyNames[companyIndex] + " Price History", {
size: 44,
fill: 0xFFFFFF
});
chartTitle.anchor.set(0, 0);
chartTitle.x = 0;
chartTitle.y = chartH + 18;
chartContainer.addChild(chartTitle);
}
// Draw news for companyIndex
function drawCompanyNews(companyIndex) {
// Determine price trend: up, down, or neutral
var trend = "neutral";
if (stockPrices[companyIndex] > stockLastPrices[companyIndex]) trend = "positive";else if (stockPrices[companyIndex] < stockLastPrices[companyIndex]) trend = "negative";
// Pick news pool
var newsPool = companyNews[companyIndex][trend];
// If no news in pool, fallback to neutral
if (!newsPool || newsPool.length === 0) newsPool = companyNews[companyIndex].neutral;
// Pick up to 3 random headlines from the pool (no repeats)
var shown = [];
var used = {};
for (var i = 0; i < Math.min(3, newsPool.length); i++) {
var idx;
do {
idx = Math.floor(Math.random() * newsPool.length);
} while (used[idx] && Object.keys(used).length < newsPool.length);
used[idx] = true;
shown.push(newsPool[idx]);
}
// Show news
for (var i = 0; i < shown.length; i++) {
var fillColor = trend === "positive" ? 0x2ecc71 : trend === "negative" ? 0xe74c3c : 0xBDC3C7;
var newsTxt = new Text2("- " + shown[i], {
size: 44,
fill: fillColor
});
newsTxt.anchor.set(0, 0);
newsTxt.x = 0;
newsTxt.y = i * 64;
newsContainer.addChild(newsTxt);
}
// News title
var newsTitle = new Text2(trend === "positive" ? "Positive News" : trend === "negative" ? "Negative News" : "Recent News", {
size: 44,
fill: 0xFFFFFF
});
newsTitle.anchor.set(0, 0);
newsTitle.x = 0;
newsTitle.y = -60;
newsContainer.addChild(newsTitle);
}
// Show detail section for company
var currentDetailCompany = -1;
function showCompanyDetail(companyIndex) {
if (currentDetailCompany === companyIndex) return;
currentDetailCompany = companyIndex;
companyDetailSection.visible = true;
// Move detail section to top of display list so it's not hidden by table
if (companyDetailSection.parent && companyDetailSection.parent.children) {
var parent = companyDetailSection.parent;
var idx = parent.children.indexOf(companyDetailSection);
if (idx !== -1 && idx !== parent.children.length - 1) {
parent.removeChild(companyDetailSection);
parent.addChild(companyDetailSection);
}
}
drawCompanyChart(companyIndex);
drawCompanyNews(companyIndex);
}
// Hide detail section
function hideCompanyDetail() {
companyDetailSection.visible = false;
currentDetailCompany = -1;
clearCompanyDetailSection();
}
// Company name click handled by button background in StockRow
// Optionally, tap outside detail section to hide it (not required, but nice UX)
companyDetailSection.down = function (x, y, obj) {
hideCompanyDetail();
};
// Update total profit/loss
function updateTotalProfit() {
var totalProfit = 0;
for (var i = 0; i < 10; i++) {
var lots = playerLots[i];
var curPrice = stockRows[i].price;
for (var j = 0; j < lots.length; j++) {
totalProfit += Math.round((curPrice - lots[j].price) * lots[j].shares);
}
}
var profitColor = "#ffffff";
if (totalProfit > 0) profitColor = "#2ecc71";
if (totalProfit < 0) profitColor = "#e74c3c";
totalProfitTxt.setText((totalProfit >= 0 ? '+' : '') + totalProfit);
totalProfitTxt.setStyle({
fill: profitColor
});
}
// --- UI Update Functions ---
function updateCashUI() {
cashTxt.setText('Cash: ₲' + playerCash);
updateWealthUI();
updateTotalProfit();
}
function updateWealthUI() {
var wealth = playerCash;
for (var i = 0; i < 10; i++) {
wealth += playerHoldings[i] * stockPrices[i];
}
wealthTxt.setText('Wealth: ₲' + wealth);
}
function updateTimerUI() {
var min = Math.floor(timeLeft / 60);
var sec = timeLeft % 60;
var t = (min < 10 ? '0' : '') + min + ':' + (sec < 10 ? '0' : '') + sec;
timerTxt.setText(t);
}
// --- Stock Price Update Logic ---
// Simulate price changes every 10 seconds
function updateStockPrices() {
for (var i = 0; i < 10; i++) {
stockLastPrices[i] = stockPrices[i];
// Random walk: -8% to +8%
var pct = Math.random() * 0.16 - 0.08;
// Occasionally, a "news event" (1 in 8 chance): -25% to +25%
if (Math.random() < 0.125) {
pct = Math.random() * 0.5 - 0.25;
}
var newPrice = Math.max(10, Math.round(stockPrices[i] * (1 + pct)));
stockPrices[i] = newPrice;
// Update price history for chart
if (typeof companyPriceHistory !== "undefined" && companyPriceHistory[i]) {
companyPriceHistory[i].push(newPrice);
if (companyPriceHistory[i].length > 20) companyPriceHistory[i].shift();
// If this company is currently shown in detail, redraw chart and news
if (typeof currentDetailCompany !== "undefined" && currentDetailCompany === i && companyDetailSection.visible) {
drawCompanyChart(i);
drawCompanyNews(i);
}
}
// Update row
stockRows[i].setPrice(newPrice);
playerHoldings[i] = stockRows[i].holdings;
}
updateWealthUI();
updateTotalProfit();
}
// --- Timer Logic ---
function tickTimer() {
timeLeft -= 1;
if (timeLeft < 0) timeLeft = 0;
updateTimerUI();
// Only end game if timeLeft < 0 (so timer shows 00:00 for a full second)
if (timeLeft < 0) {
endGame();
}
}
// --- End Game ---
function endGame() {
// Calculate final wealth
var finalWealth = playerCash;
for (var i = 0; i < 10; i++) {
finalWealth += playerHoldings[i] * stockPrices[i];
}
// Show game over (handled by LK)
LK.showGameOver();
}
// --- Game Update Loop ---
game.update = function () {
// No per-frame logic needed for this MVP
};
// --- Start Game Logic ---
function startGame() {
// Reset state
playerCash = 10000;
for (var i = 0; i < 10; i++) {
stockPrices[i] = initialPrices[i];
stockLastPrices[i] = initialPrices[i];
playerHoldings[i] = 0;
playerLots[i] = [];
stockRows[i].setPrice(stockPrices[i]);
stockRows[i].setHoldings(0);
}
timeLeft = gameDurationSec;
updateCashUI();
updateTimerUI();
updateWealthUI();
hideCompanyDetail();
// Start price update interval (every 10s)
if (timerInterval) LK.clearInterval(timerInterval);
timerInterval = LK.setInterval(function () {
updateStockPrices();
}, 10000);
// Start timer (every 1s)
if (game._timerTick) LK.clearInterval(game._timerTick);
game._timerTick = LK.setInterval(function () {
tickTimer();
}, 1000);
}
// Play background music (looping, fade in for smoothness)
LK.playMusic('bg_stockfloor', {
fade: {
start: 0,
end: 0.7,
duration: 1200
}
});
// --- Start the game ---
startGame();
; ===================================================================
--- original.js
+++ change.js
@@ -338,8 +338,9 @@
/****
* Game Code
****/
+// Music: Lightly tense, smooth, jazzy/lo-fi, gentle bass, soft percussion, subtle synths
// --- Game Data ---
// 10 company colors for stocks (distinct, visually clear)
// Button shapes
var companyNames = ["AlphaTech", "BetaBank", "GammaFoods", "DeltaPharma", "EpsilonEnergy", "ZetaAuto", "EtaRetail", "ThetaMedia", "IotaLogix", "KappaSpace"];
@@ -908,6 +909,15 @@
game._timerTick = LK.setInterval(function () {
tickTimer();
}, 1000);
}
+// Play background music (looping, fade in for smoothness)
+LK.playMusic('bg_stockfloor', {
+ fade: {
+ start: 0,
+ end: 0.7,
+ duration: 1200
+ }
+});
// --- Start the game ---
-startGame();
\ No newline at end of file
+startGame();
+;
\ No newline at end of file