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: 150, 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 = 160; 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 = 800; holdingsTxt.y = 30 + yPad; holdingsTxt.anchor.set(0, 0); self.addChild(holdingsTxt); // Profit text var profitTxt = new Text2('', { size: 48, fill: 0xFFFFFF }); profitTxt.x = 1100; 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 ****/ // --- 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 = 300; // 5 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('05:00', { size: 72, fill: 0xFFFFFF }); timerTxt.anchor.set(0.5, 0); timerTxt.x = 2048 / 2; timerTxt.y = 40; 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; var header = new Text2("Company Price Change Holdings Profit ", { size: 48, fill: 0xBDC3C7 }); header.anchor.set(0, 0); header.x = 160; header.y = headerY; tableContainer.addChild(header); // 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 = 1090; totalProfitLabel.y = 80 + 10 * 120 + 10; tableContainer.addChild(totalProfitLabel); var totalProfitTxt = new Text2('', { size: 48, fill: 0xFFFFFF }); totalProfitTxt.anchor.set(0, 0); totalProfitTxt.x = 1110; totalProfitTxt.y = 80 + 10 * 120 + 10; // below last row 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 = 1450; // align with buy/sell 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); } // --- Start the game --- startGame();
===================================================================
--- original.js
+++ change.js
@@ -63,25 +63,25 @@
priceTxt.x = 500;
priceTxt.y = 30 + yPad;
priceTxt.anchor.set(0, 0);
self.addChild(priceTxt);
- // Last price change % text (new column)
- var lastChangeTxt = new Text2('', {
+ // Last price change percent text (new column)
+ var changeTxt = new Text2('', {
size: 44,
- fill: 0xBDC3C7
+ fill: 0xFFFFFF
});
- lastChangeTxt.x = 670; // between price and holdings
- lastChangeTxt.y = 34 + yPad;
- lastChangeTxt.anchor.set(0, 0);
- self.addChild(lastChangeTxt);
+ 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.x = 800;
holdingsTxt.y = 30 + yPad;
- holdingsTxt.anchor.set(0.5, 0); // center text under Holdings label
+ holdingsTxt.anchor.set(0, 0);
self.addChild(holdingsTxt);
// Profit text
var profitTxt = new Text2('', {
size: 48,
@@ -174,9 +174,9 @@
// Update UI
self.updateUI = function () {
nameTxt.setText(self.companyName);
priceTxt.setText('₲' + self.price);
- // Show last price change % (compared to lastPrice)
+ // Show last price change percent and color
var pctChange = 0;
if (self.lastPrice && self.lastPrice !== 0) {
pctChange = (self.price - self.lastPrice) / self.lastPrice * 100;
}
@@ -188,16 +188,16 @@
} else if (pctChange < -0.01) {
pctStr = pctChange.toFixed(2) + '%';
pctColor = "#e74c3c";
} else {
- pctStr = '0.00%';
+ pctStr = '+0.00%';
pctColor = "#BDC3C7";
}
- lastChangeTxt.setText(pctStr);
- lastChangeTxt.setStyle({
+ changeTxt.setText(pctStr);
+ changeTxt.setStyle({
fill: pctColor
});
- holdingsTxt.setText(self.holdings);
+ holdingsTxt.setText(self.holdings + ' shares');
// Calculate average cost for this stock
var lots = playerLots[self.companyIndex];
var totalShares = 0;
var totalCost = 0;
@@ -286,12 +286,9 @@
}
};
// For updating price and profit
self.setPrice = function (newPrice) {
- // Update lastPrice before changing price, for correct % display
- if (typeof self.price !== "undefined") {
- self.lastPrice = self.price;
- }
+ self.lastPrice = self.price;
self.price = newPrice;
// Update holdings from lots (in case lots changed externally)
var lots = playerLots[self.companyIndex];
var totalShares = 0;
@@ -411,25 +408,16 @@
tableContainer.y = 300;
game.addChild(tableContainer);
// Table header
var headerY = 0;
-var header = new Text2("Company Price Last Change Holdings Profit ", {
+var header = new Text2("Company Price Change Holdings Profit ", {
size: 48,
fill: 0xBDC3C7
});
header.anchor.set(0, 0);
header.x = 160;
header.y = headerY;
tableContainer.addChild(header);
-// Add Holdings label directly above share amounts (centered above column)
-var holdingsLabel = new Text2("Holdings", {
- size: 36,
- fill: 0xBDC3C7
-});
-holdingsLabel.anchor.set(0.5, 0);
-holdingsLabel.x = 800 + 80; // center above holdings column (holdingsTxt.x + half width of column)
-holdingsLabel.y = headerY + 48;
-tableContainer.addChild(holdingsLabel);
// Total Profit/Loss label and display (at end of profit column)
var totalProfitLabel = new Text2('Total Profit:', {
size: 48,
fill: 0xBDC3C7