JSFiddle - React, Tailwind, and code Playground

by Gwyn Milcote

HTML

<h2>Stock Market Simulator</h2>
<p>Demo made for Ittermat's game. Heavily based on the Neopets stock market minigame. You begin with a random portfolio and ten shops to browse. Buy and sell stocks each day to make a profit.</p>

Day <span id='sm-status-day'>7</span>. 
<button id='sm-advance-btn'>Advance 1 day</button>
<button id='sm-reset-btn'>Reset simulation</button>
<br>
You have <span id='sm-status-coins'>500</span> coins, and have bought <span id='sm-status-stocksToday'>0</span>/100 stocks today.
<br>
In total, you have spent <span id='sm-status-totalSpent'>0</span> and earned <span id='sm-status-totalEarned'>0</span> coins. That's a profit of <span id='sm-status-profitCoins'>0</span> coins, or <span id='sm-status-profitPercent'>0%</span>.

<hr>
<span id='sm-message'>[Messages will show here]</span>
<hr>

<b>Current Shop Status</b>
<p>Click on a shop name to see its stock history for the last 7 days.</p>
<table id='sm-shops'></table>

<ul id='sm-history-price'></ul>
<ul id='sm-history-buysell'></ul>

<hr>
<b>My Portfolio</b>
<p>To sell stocks, click a row. You purchased the stocks in batches at a time, so can sell stocks from each batch.</p>
<table id='sm-portfolio'></table>
<table id='sm-sell-batches'></table>
<button id='sm-sell-btn'>Sell</button>

<hr>
<b>My History</b>
<p>Everything you've bought and sold in the last 14 days. (First 7 days are randomly generated, you begin with those shares)</p>
<ul id='sm-user-history'></ul>

CSS

table {
    border-collapse: collapse;
    width: 100%;
    max-width: 900px;
    margin: 20px auto;
}

table th {
    background-color: silver;
}

table td {
    padding: 5px;
    border: 1px solid silver;
}

table tr:nth-child(even) {
    background-color: #eee;
}

table input {
    width: 50px;
}

#sm-shops td:nth-child(3) {
    width: 50px;
}
#sm-shops td:nth-child(4) {
    width: 50px;
}
#sm-shops td:nth-child(5) {
    width: 70px;
}

/* Reveal later, when data-filled */
#sm-sell-btn, #sm-sell-batches, #sm-history-price, #sm-history-buysell {
    display: none;
}


.sm-shop-name:hover {
    cursor: pointer;
    background-color: lightblue;
}

.sm-text-positive {
    color: green;
}
.sm-text-negative {
    color: red;
}

.sm-bg-positive {
    background-color: lightgreen;
}
.sm-bg-negative {
    background-color: lightpink;
}

JavaScript

let db = {

    // Virtual companies. Will have randomised prices.
    shops: [
        {id: 1, name: "Brie the Blacksmith", stockPrice: 0},
        {id: 2, name: "Shining Armoury", stockPrice: 0},
        {id: 3, name: "The Fruit Bowl", stockPrice: 0},
        {id: 4, name: "Onyx and Pearl", stockPrice: 0},
        {id: 5, name: "Everything Equine", stockPrice: 0},
        {id: 6, name: "Sky Emporium", stockPrice: 0},
        {id: 7, name: "Dragon's Dungeon", stockPrice: 0},
        {id: 8, name: "Saddles and Shoes", stockPrice: 0},
        {id: 9, name: "The Beautiful Boys", stockPrice: 0},
        {id: 10, name: "The Sweaty Smelter", stockPrice: 0}
    ],
    
    // Which shares are owned by which users.
    // They're grouped at the time of purchase, so
    // price-paid-at-the-time can be saved for later.
    stocksOwned: [
        {id: 1, userId: 1, shopId: 2, qty: 6, price: 0, day: 1},
        {id: 2, userId: 1, shopId: 2, qty: 25, price: 0, day: 3}
    ],
    
    // Just keeping track.
    stocksSold: [
        {id: 1, userId: 1, shopId: 5, qty: 17, price: 53, day: 4}
    ],
    
    // Track how % and prices changed each day.
    // Old records are deleted after 7 days.
    stockHistory: [
        {shopId: 1, percentChange: 2, newPrice: 5, day: 0},
        {shopId: 2, percentChange: -3, newPrice: 7, day: 0}
    ],
    
    // Simulate currency, buying limits.
    users: [
        {id: 1, coins: 500, stocksToday: 0, totalSpent: 0, totalEarned: 0}
    ]
};

function randomNumber(n1, n2){
    if(n1 == n2){ return n1; }
    let min = n1, max = n2;
    if(n1 > n2){ min = n2; max = n1; }
    return Math.floor(Math.random() * (max - min + 1) + min);
}
function randomArray(array){
    return array[Math.floor(Math.random() * array.length)];
}

class StockMarketDemo {

    constructor(){
        this.limitPerShop = 300;
        this.limitPerDay = 100;
        this.minimumQty = 5;
    
        // Fill tables with random starting data.
        // Generate 7 days...