LiveCode Stock Trader: Part 1

Posted by Scott McDonald

Here is the first multi-player online by the LiveCode Game Developer. In this game your goal is to make money by buying and selling stocks in 5 companies at a stock exchange. Sounds a lot like Stock Market from a previous post. But this game is different. Not only is your goal to buy stocks when their values are going to rise, and sell them to make a virtual profit, but to become the top trader by beating other players. While the single player Stock Market game can be fun, competing against real people at the same stock exchange takes it to another level.

Multi-player online games are increasingly popular. The list of online games, or single player games with a multi-player aspect, increases every year. Clash of Clans, Star Wars: The Old Republic, EVE Online to name three. Sure Stock Trader is not a RPG with graphics, but EVE Online has been described as just a massive spreadsheet. So in Stock Trader could there be the seed of the next big MMO franchise?

The online version of Stock Market is called Stock Trader and you start with $10,000 in virtual play money and hope to not only increase your total value after each day of trading (buying and selling), but to rise to the top of the leader board.

This game is based on the Stock Market game in BASIC Computer Games from 1978, but has been altered to do all processing on a server and use a web browser for the interface.

In this game you will see:

  • LiveCode Server used to run the game
  • A MySQL table storing player logins
  • Persistent variables stored in a flat-file
  • A user interface based entirely on HTML
  • How an online multi-player game can make a simple idea more interesting

The existing code from Stock Market was used as much as possible. Re-using code saves time, and all the non-user interface handlers of Stock Trader are mostly the same as the single player program. But as a browser based online game, there are 2 major issues that require extra work.

  1. You need to figure out a way to store the values of the variables between each execution of the program.
  2. The user interface must be re-crafted to makes use of HTML elements.

A full explanation of why and how to work within these requirements, is way beyond the scope of this website, but there are many resources on the internet if you want to know more. If you like a good book, while not about LiveCode PHP and MySQL Web Development by Luke Welling and Laura Thomson is one way to sort out in your mind the concepts required for server-side programming.

In Stock Trader, a combination of MySQL, flat (plain text) files and hidden input tags solves the problem with variables. Ordinary HTML is used for the interface. Javascript could assist with both of these issues, but a decision was made to use LiveCode as much as possible, and other than the SQL and HTML use no other technologies. This is the LiveCode Game Developer, after all.

Having said that, Stock Trader does get some coding assistance from a library. Stock Trader runs at The LiveCode Lab, which includes a ready to go LiveCode Server and a small code library to help with the HTML output. But since The LiveCode Lab library is written in LiveCode, Stock Trader remains a multi-player online game with 100% LiveCode goodness.

Let's look at the Stock Trader code now. At around 600 lines, this game is larger than most other games examined here. So the decision was made to divide the game code into separate files.

With LiveCode Server you need to require the files that contain handlers you want to use.

require "trader-fns.lc"
require "trader-login.lc"

Next, validLogin is a called and this handler is in the trader-login.lc file. This handler checks to see if the player is already logged in. If the player is logged in this function returns a database ID for accessing the player data in the MySQL database and a userToken that keeps the login valid.

If the player is not logged in, the start page of the game is shown. Here commands from The LiveCode Lab are used: wasSetTemplate, wasSetActiveArea, wasPut and wasPutP. You can read about them in The LiveCode Lab Quick Start Guide which is a PDF, but in summary, they simplify the output of the HTML for the interface.

A table is used to layout the elements of the login form. There may be good reasons why this is not best practice, and CSS should be used instead, but here and elsewhere in Stock Trader tables are used because they work and are simple to code.

put empty into dbID
put empty into prompt
if not validLogin(dbID, prompt, userName, userToken) then
  wasSetTemplate "trader-template-login"
  wasSetActiveArea "rightcontent"
  
  if prompt is not empty then wasPutP prompt
  wasPut "<form action='^wasindex.lc' method='POST'>"
  wasPut "<table><tbody>"
  wasPut "<tr><td>Player Name:</td><td><input type='text' name='name' value='^1' /></td></tr>",$_POST["name"]
  wasPut "<tr><td>Password:</td><td><input type='password' name='password' value='^1' /></td></tr>",$_POST["password"]
  wasPut "<tr><td></td><td><input type='submit' name='butnLogin' value=' Login ' /></td></tr>"
  wasPut "<tr><td></td><td>Don\'t have a login?<br /><a href='trader-register.lc'>Register Here</a></td></tr>"
  wasPut "</tbody></table>"
  wasPut "</form>"
else

Assuming the player is logged in, the rest of the code executes. A different HTML template is made active, and some variables set. loadVars and loadStockHistory get values from 2 text files. Since these values only change once every 24 hours and are the same for all players, there is no need to put them into a SQL table, or anything like that. A plain text file is more than sufficient. LoadVars, loadStockHistory and convertLineToArray are declared in trader-fns.lc and will be covered in Part 2.

wasSetTemplate "trader-template"
wasSetActiveArea "leftcontent"

put "AAPL,GOOGL,IBM,INTC,MSFT" into sStockCodes
put "Apple,Google,IBM,Intel,Microsoft" into sCompanyNames
put 86400 into sTradeInterval -- number of seconds in day

-- get variables
loadVars sTrendLength,sTrendSlope,sTrendUpCounter,sTrendDownCounter,sTrendUpStock,sTrendDownStock
loadStockHistory sStockHistory
put convertLineToArray(item 2 to 6 of line 1 of sStockHistory,sStockCodes) into sValueArray

With all the variables set, a check is made to see if more than 24 hours have elapsed since the last login and if necessary the stock prices are updated. What may be interesting, is that there isn't a timer, or code actively updating the stock prices every 24 hours. This means is that if no players are logged into the game, then the stock history will be out of date. But since no-one is playing the game at that time this doesn't matter because no-on knows the data is out of date. Then when a player logs on the price history for the missing days is calculated.

This is what I call a lazy algorithm. Don't do anything until it is needed. Such a technique is useful in games (and other programs) as it can minimise processing and increase speed. Especially if the processing is complicated and you combine it with caching.

When the stock prices are updated the changes are saved and the total value of the assets for each player is updated. The call to updateUserAssets is necessary to ensure the leader board accurate reflects the new stock prices.

-- if necessary, update stock prices for new day
put false into sHistoryUpdated
repeat while item 1 of sStockHistory + sTradeInterval < the seconds
  updateStockHistory sStockCodes,sTradeInterval,sStockHistory,sValueArray,sTrendLength,sTrendSlope,sTrendUpCounter,sTrendDownCounter,sTrendUpStock,sTrendDownStock
  put true into sHistoryUpdated
end repeat
if sHistoryUpdated then
  storeStockHistory sStockHistory
  storeVars sTrendLength,sTrendSlope,sTrendUpCounter,sTrendDownCounter,sTrendUpStock,sTrendDownStock
  updateUserAssets dbID,sStockCodes,sValueArray
end if
put item 1 of sStockHistory into sLastUpdateTime

While the stock prices and the other variables are stored in plain text files, the player data is stored in a database. Any time you could have either a lot of data, need to ensure the player names are unique, or cannot control the timing of when information is written (and need to prevent loss of data through simultaneous writes) a database system, such as MySQL, is recommended.

While this requires learning about SQL (which is a different type of language from LiveCode) you only need to know a few SQL basics for a game like this. Below is a SQL statement that gets the user (player) data and stores it in a few variables and after a call to convertLineToArray in the sHoldingArray. (convertLineToArray is another function from trader-fns.lc that is discussed in Part 2.)

-- load user data
put "SELECT trade_time,cash,total_assets,holding_aapl,holding_googl,holding_ibm,holding_intc,holding_msft FROM stockuser WHERE user_name = '^1'" into sqlQuery
put subText(sqlQuery, userName) into sqlQuery
put revDataFromQuery(comma,cr,dbID,sqlQuery) into buffer
put item 1 of buffer into sPrevTradeTime
put item 2 of buffer into sCash
put item 3 of buffer into sTotalAssets
put convertLineToArray(item 4 to 8 of buffer,sStockCodes) into sHoldingArray

The sPrompt variable is for text that is shown as a highlighted yellow line, which is for instructions to the player and if a trade is attempted that cannot be done. For example, if you do not have enough money.

If $_POST["butnTrade"] is not empty this means that the Make Trades button has been clicked, and the stock holdings of the player must be updated. But first ValidTrades is called to ensure that the trades can be made. If the trades can be made, MakeTrades updates the values in sCash and sHoldingArray which are then stored in the stockuser database table.

put "You can now trade by dollar values. For example, entering <em>$2000</em> will buy or sell two thousand dollars worth of stock." into sPrompt
if $_POST["butnTrade"] is not empty then
  put ValidTrades(sStockCodes,sValueArray,sCash,sHoldingArray,sTradeAmount) into sPrompt
  if sPrompt is empty then
   MakeTrades sStockCodes,sValueArray,sTradeAmount,sCash,sHoldingArray
   put sCash + CalculateStockAssets(sStockCodes,sValueArray,sHoldingArray) into sTotalAssets
   put "UPDATE stockuser SET trade_time = ^1,cash = ^2,total_assets = ^3,holding_aapl = ^4,holding_googl = ^5,holding_ibm = ^6,holding_intc = ^7,holding_msft = ^8 WHERE user_name = '^9'" into sqlQuery
   put subText(sqlQuery,sLastUpdateTime,sCash,sTotalAssets,sHoldingArray["AAPL"],sHoldingArray["GOOGL"],sHoldingArray["IBM"],sHoldingArray["INTC"],sHoldingArray["MSFT"],userName) into sqlQuery
   revExecuteSQL dbID,sqlQuery
   put "Trades successful." into sPrompt
  end if
end if

Then a summary of your wealth is shown. This uses the wasPut command of The LiveCode Lab. This command is able to substitute parameters into the first string. For example, the first parameter after the text is put into the ^1 of the text.

-- summary table
wasPut "<h3>Total Wealth: $^1</h3>",insertDecimal(sTotalAssets)
wasPut "<table><tbody>"
wasPut "<tr><td>Cash:</td><td style='text-align:right;'>$^1</td><td></td></tr>",insertDecimal(sCash)
wasPut "<tr><td>Stock Assets:</td><td style='text-align:right;'>$^1</td><td><em>The stock prices update in about ^2</em>.</td></tr>",insertDecimal(sTotalAssets - sCash),nextTradePrompt(sLastUpdateTime,sTradeInterval)
wasPut "</tbody></table>"

Then the main part of the interface is constructed as HTML. It is a HTML form that contains:

  • Hidden text elements to maintain the login information
  • Radio buttons and text text elements and a submit button to allow trades
  • Information about the player's stocks

The last item does not need to be within the form tags, but based on the layout it is convenient and requires less coding to include it in the form.

Knowledge of HTML forms and tables is required to understand this form, but if you plan to make software that runs in a browser learning how forms work is essential. One part of this code that may be surprising is the line:

wasPut "<form action='^wasindex.lc' method='POST'>"

The form action is '^wasindex.lc'. The ^ is not standard HTML, and the ^ suggests that there may be a substitution to do, but there are no extra parameters in the call to wasPut. So what's going on? The ^wasindex.lc is substituted with the name of the file that contains the main LiveCode for the game. When the game is run it is replaced with index.lc, but the ^wasindex.lc is necessary to allow debugging of the game. You don't need to understand this, it is just a part of how The LiveCode Lab works.

-- trading table
wasPut "<h3>Trading Console</h3>"
wasPut "<form action='^wasindex.lc' method='POST'>"
wasPut "<input type='hidden' name='username' value='^1' />",username
wasPut "<input type='hidden' name='usertoken' value='^1' />",usertoken
wasPut "<table><tbody>"
wasPut "<tr>"
wasPut "<td><strong>Buy</strong></td><td><strong>Sell</strong></td><td style='text-align: left;'><strong>Amount</strong></td><td><strong>Company</strong></td><td><strong>Price</strong></td><td><strong>Holding</strong></td><td><strong>Value</strong></td><td><strong>Change</strong></td>",loopStockCode
wasPut "</tr>"

put convertLineToArray(item 2 to 6 of line 2 of sStockHistory,sStockCodes) into sPrevValueArray

put 1 into loopIndex
repeat for each item loopStockCode in sStockCodes
  wasPut "<tr>"

  wasPut "<td><input type='radio' name='radiAction^1' value='buy' ^2 /></td>",loopStockCode,condText($_POST["radiAction" & loopStockCode] = "buy","checked","")
  wasPut "<td><input type='radio' name='radiAction^1' value='sell' ^2 /></td>",loopStockCode,condText($_POST["radiAction" & loopStockCode] = "sell","checked","")
  wasPut "<td><input type='text' name='amount^1' value='^2' style='width: 8em;' /></td>",loopStockCode,defaultText($_POST["amount" & loopStockCode],0)
  wasPut "<td class='lefttext'>^1</td>",item loopIndex of sCompanyNames
  wasPut "<td>$^1</td>",insertDecimal(sValueArray[loopStockCode])
  wasPut "<td>^1</td>",sHoldingArray[loopStockCode]
  wasPut "<td>$^1</td>",insertDecimal(sValueArray[loopStockCode] * sHoldingArray[loopStockCode])
  wasPut "<td>^1</td>",insertDecimal(sValueArray[loopStockCode] - sPrevValueArray[loopStockCode])

  wasPut "</tr>"
  add 1 to loopIndex
end repeat
wasPut "<tr>"
wasPut "<td colspan='2'></td>"

wasPut "<td class='lefttext'><input type='submit' name='butnTrade' value=' Make Trades ' style='width: 8em;' /></td>"

wasPut "<td colspan='5' style='text-align:left;'><small>Note: A brokerage fee of 1% is charged on all transactions,<br />so plan your buying and selling carefully.</small></td>"
wasPut "</tr>"
wasPut "</tbody></table>"

if sPrompt is not empty then
  wasPut "<p style='background-color: #EEE11E;margin: 0.25em 2em 0 0'> ^1</p>",sPrompt
end if

wasPut "</form>"

After the form, there is yet another HTML table that loops through the lines of the sStockHistory variable and formats the values with colour to show if a stock is going up or down.

-- price table
wasPut "<h3>Price History</h3>"
wasPut "<table class='pricetable'><tbody>"
wasPut "<tr class='priceheading'>"
wasPut "<td>Date</td><td>Apple</td><td>Google</td><td>IBM</td><td>Intel</td><td>Microsoft</td>"
wasPut "</tr>"

repeat with loopIndex = 1 to 7
  put line loopIndex of sStockHistory into stockValues
  put line loopIndex+1 of sStockHistory into prevStockValues
  wasPut "<tr>"

  put item 1 of stockValues into buffer
  convert buffer from seconds to short date

  wasPut "<td>^1</td>",buffer
  repeat with loopInner = 2 to 6
   wasPut "<td style='color:#^1'>$^2</td>",condText(item loopInner of stockValues < item loopInner of prevStockValues,"FF0000","007F0E"),insertDecimal(item loopInner of stockValues)
  end repeat

  wasPut "</tr>"
end repeat
wasPut "</tbody></table>"

The leader board of Top Traders is on a different area of the page, so a call to wasSetActiveArea sets a different part of the HTML template to be the destination of the calls to wasPut. Another SQL query is used. If the game had thousands of players (I wish!) a LIMIT could be added to the query so only the top 20 players are returned. But since that is not the case the entire table is returned. This has the benefit that if the player is not in the top twenty, the rank of the player can easily be calculated. The foundUser variable keeps track of whether the player needs to be listed separately after the top twenty traders.

-- top trader table
wasSetActiveArea "rightcontent"
put "SELECT user_name,total_assets FROM stockuser ORDER BY total_assets DESC" into sqlQuery
put revDataFromQuery(comma,cr,dbID,sqlQuery) into buffer
if item 1 of buffer <> "revdberr" then
  wasPut "<h3>Top Traders</h3>"
  wasPut "<table><tbody>"
  -- table heading
  wasPut "<tr class='topheading'>"
  wasPut "<td style='color:#000000;'>Rank</td><td style='color:#000000;'>Assets</td><td style='color:#000000;'>Trader</td>"
  wasPut "</tr>"
  put 1 into index
  put false into foundUser
  repeat for each line loopLine in buffer
   put item 1 of loopLine into loopUserName
   if index < 21 then
    if loopUserName = userName then
     wasPut "<tr style='color:#12EAF3;'><td>^1</td><td>$^2</td><td>^3</td></tr>",index,insertCommas(item 2 of loopLine),loopUserName
     put true into foundUser
    else
     wasPut "<tr><td>^1</td><td>$^2</td><td>^3</td></tr>",index,insertCommas(item 2 of loopLine),loopUserName
    end if
   else
    if not foundUser then
     if loopUserName = userName then
      if index > 21 then wasPut "<tr><td>…</td><td>…</td><td>…</td></tr>"
      wasPut "<tr style='color:#12EAF3;'><td>^1</td><td>$^2</td><td>^3</td></tr>",index,insertCommas(item 2 of loopLine),loopUserName
      exit repeat
     end if  
    end if
   end if
   add 1 to index
  end repeat
  wasPut "</tbody></table>"
  wasPutP "Number of players: " & the number of lines in buffer
end if

Lastly, the link to the database is closed.

if dbID is not empty then revCloseDatabase dbID

That completes a multi-player game in LiveCode. It is longer than my normal goal of less than 500 lines, but except for the SQL and HTML it is still pure LiveCode code. You can play it here: Play Stock Trader. You do need to register to play, but this is easy and doesn't require any personal details or an email address.

Examine the complete source to LiveCode Stock Trader made in LiveCode Server 6.5.2 from here: LiveCode Stock Trader Source

Part 2 of the Stock Trader that looks at the other code will be posted later.

Happy LiveCoding.

Tagged: advanced multiplayer online simulation

Friday, August 1, 2014

0 Responses

Be the first to make a comment.


COMMENTS ARE CLOSED

 

Legal Stuff

All blog posts are copyright and cannot be re-used without permission. But all the code and scripts are dedicated to the public domain. Use such code and scripts in any way you want, but I am not responsible if they don't work for you.

Contact

Further comments or feedback? You can contact me by email at: