Money in a game

I’d like to give my character some cash and coins, which they can use to buy things. That seems like a basic thing a character should be able to do, but could potentially become unnecessarily complicated.

[code]> look at money

You have 3 dollars and 50 cents.

give cashier 3 quarters

You only have 2 quarters.

give cashier 2.43 dollars

The cashier hands you 2 cents as change.[/code]

That seems too complicated. Is there an established method of doing this that is part of TADS 3? Or is there some way to simplify this so the people playing still feel they’re using money to buy things?

Getting Started in TADS 3 has a section on money.

I would follow that example and limit the currency options to bills, gold coins, or some other indivisible unit. You sacrifice some realism, but it’s easier to make change and describe the transaction in a readable way.

Here is how I handle money in Tads3. I use the typical RPG money system of gold, silver and copper.

To use this you need to include the bignum.h file (in gameMain usually):

#ifndef _BIGNUM_H_
#include <bignum.h>
#endif

Then in gameMain I put these:

  myBank = new BigNumber('0') // BigNumber('99123456') // the "master" bank variable for the player
    myCarriedMoney = new BigNumber('50') // 12345678') // '50') // temp variable mostly - recalculated "on the fly" based on carried gold, silver and copper
    myCarriedGold = new BigNumber('2550')    // actual
    myCarriedSilver = new BigNumber('320')  // actual
    myCarriedCopper = new BigNumber('50') // actual
   
 getPlayerBank(){
           // local gold = new BigNumber(myBank.getAbs() / 10000).getFloor();// floor is biggest int less than this number. i.e. 12.3456 = 12
           local gold = new BigNumber(gameMain.myBank.getAbs() / 10000).getFloor();
           local silver = new BigNumber(gameMain.myBank.getAbs() / 100).getFloor();
           silver = silver.divideBy(100)[2];// second list item is remainder
           local copper = new BigNumber(gameMain.myBank.getAbs()).getFloor();
           copper = copper.divideBy(100)[2];// second list item is remainder
           local s0 = 'In the bank you have: ' + 
                      gold + 'g ' + 
                      silver + 's ' + 
                      copper + 'c ' + '';
                      // '\bTotal (converting all to copper): ' + gameMain.myBank + 'c ';
           return s0;
    }

    getPlayerCarriedMoney(){
           //       --... // local gold = new BigNumber(myBank.getAbs() / 10000).getFloor();// floor is biggest int less than this number. i.e. 12.3456 = 12
           // local gold = new BigNumber(gameMain.myCarriedMoney.getAbs() / 10000).getFloor();
           // local silver = new BigNumber(gameMain.myCarriedMoney.getAbs() / 100).getFloor();
           // silver = silver.divideBy(100)[2];// second list item is remainder
           // local copper = new BigNumber(gameMain.myCarriedMoney.getAbs()).getFloor();
           // copper = copper.divideBy(100)[2];// second list item is remainder
           local gold = 0;
           if(gameMain.myCarriedGold > 0) gold = new BigNumber(gameMain.myCarriedGold.getAbs()).getFloor();
           local silver = 0;
           if(gameMain.myCarriedSilver > 0) silver = new BigNumber(gameMain.myCarriedSilver.getAbs()).getFloor();
           local copper = 0;
           if(gameMain.myCarriedCopper > 0) copper = new BigNumber(gameMain.myCarriedCopper.getAbs()).getFloor();           
           local s0 = 'Your carried money is: ' + 
                      gold + 'g ' + 
                      silver + 's ' + 
                      copper + 'c';
           return s0;
    }    

…and to actually interact with your bank money I’d have an NPC banker and some input screens with lists of things you can do:

  1. exit
  2. deposit
  3. withdraw

If you select “2” for withdraw, then ask how much gold, silver and copper. Then using some math, convert it and move it all over, subtracting from bank variable “myBank” and adding to variable “myCarriedMoney.”

Here is my banker NPC code (disregard the CromexxHuman and Male classes and go with Actor or Person here):

banker: CromexxHuman, Male
  name = 'banker'
  vocabWords = '(the) (bank) banker Roy Dribbik male/man/dude/guy/fellow/teller/tellar'
  properName = 'Roy Dribbik'
  isProperName = true
  isawake = true
  isQualifiedName = (isProperName)
  location = erifAveBankInterior 
  firstPlayerMeetingNPCObject = nil // player object
  firstPlayerMeetingNPCGender = nil // sex 'male' or 'female'
  desc {
     "Roy Dribbik, the banker, is dressed in a forest green suit with black pinstripes.
     He\'s wearing a matching tie and thin little reading glasses and even has a banker\'s
     visor on his head. He\'s got grey eyes and white tufts of hair around the ears but is
     otherwise bald. He looks like he\'s in his fifties or sixties. ";
  } 
  showBank(){
     local sa = gameMain.getPlayerBank();
     local sb = gameMain.getPlayerCarriedMoney();     
     "<table border=1 width=350 bgcolor=#000000>
        <tr><td><font color=#000000>\b<<sa>></font></td></tr>
        <tr><td><font color=#000000>\b<<sb>></font></td></tr>
        <tr><td><font color=#000000>\b(1) Deposit\b(2) Withraw\b(3) Goodbye</font></td></tr>
      </table>";
    local iDone = 0;
    local s0 = '';
    do{
              "<br> >";
              s0 = inputManager.getInputLine(nil, nil);
              s0 = s0.toLower();
              if((s0 == 'deposit') || 
                (s0 == 'withdraw') ||
                (s0 == 'goodbye') ||
                (s0 == 'bye') ||
                (s0 == '1') ||
                (s0 == '2') ||
                (s0 == '3')){ 
                   iDone++;
              }else{
                "Please select <b>1</b> - <b>3</b><br>";
              } 
              "\n";
    }while(iDone==0);
    if((s0=='3') || (s0=='goodbye') || (s0 =='bye')){
             if(banker.agendaList != nil){
                 foreach(local obj in banker.agendaList){
                     banker.removeFromAgenda(obj);// clear the agenda list
                 }
             }
            // banker.setCurState(bankerBye);
            // gTranscript.showReports(true);// Per Mike Roberts "...'true' turns off the transcript for the remainder of the turn, so if you're doing a series of timeDelay() pauses, you only need to do this once, before the first pause."
            // nestedActorAction(gPlayerChar,Goodbye);//  exit;
            // if(banker.curState != nil){
            //    "....bye!";
             banker.curState.endConversation(gPlayerChar,endConvBye);// endConvBye);// see: endConvxxx in adv3.h - see: actor.t
             // gPlayerChar.curState.endConversation(banker,endConvBye);
             // gPlayerChar.endConversation();
             // banker.setCurState(bankerWorking);
            // }
           // gPlayerChar.sayGoodbye(banker);// actor);
        //    libGlobal.totalTurns--;
        //    local tokList = Tokenizer.tokenize('goodbye');
        //    executeCommand(gPlayerChar,gPlayerChar,tokList,nil);// gActor, gActor      
    }else if((s0=='2') || (s0=='withdraw')){
           banker.withdraw();       
    }else{
           banker.deposit();
    }
  }

  deposit(){
           local gold = new BigNumber(gameMain.myCarriedGold.getAbs()).getFloor();
           local silver = new BigNumber(gameMain.myCarriedSilver.getAbs()).getFloor();
           local copper = new BigNumber(gameMain.myCarriedCopper.getAbs()).getFloor();           
           if(gold + silver + copper == 0){
               "You have no money in your posession to deposit!\b";
               inputManager.pauseForMore(true);  
               banker.showBank();              
           }else{
               if(copper != 0){
                     banker.depositAmount(copper,'copper');
               }
               if(silver != 0){
                     banker.depositAmount(silver,'silver');
               }               
               if(gold != 0){
                     banker.depositAmount(gold,'gold');
               }
               inputManager.pauseForMore(true);  
               banker.showBank();              
           }
  }

  /*
   *   where oMoneyObject = gameMain.myCarriedGold, ...silver, ... copper
   *                sName = 'gold', 'silver' or 'copper'
   */
  depositAmount(oMoneyObject,sName){
          oMoneyObject = new BigNumber(oMoneyObject.getAbs()).getFloor();
          local iDone = 0;
          local s0 = '';
          local oBigNumborf;
          local iFailDigitTest = 0;
          local digitPat = new RexPattern('<digit>');// matches a single digit in a string
          do{
              "<br>Please enter amount of <<sName>> to deposit >";
              s0 = inputManager.getInputLine(nil, nil);
              if(s0 == nil) s0 = '0';
              if(s0 == '') s0 = '0';
              s0 = s0.toLower();
              // rex search for <digit> ... this stops cheaters from trying to trick the parser with math tokens, etc. 
              iFailDigitTest = 0;// reset flag 
              for(local i = 0; i <= s0.length;i++){
                if(! rexMatch(digitPat,s0,i)) iFailDigitTest++;
              }            
              oBigNumborf = new BigNumber(s0);
              // getFraction() returns everything to the right of the decimal point, if this is > 0 it is a fraction
              if(oBigNumborf.getFraction() > 0){
                 "Please enter whole numbers.<br>";
              }else if(iFailDigitTest > 0){
                  "Please enter a whole number without any alphabetical or special characters (including the avoidance of decimal points). ";
              }else if(toInteger(s0) > oMoneyObject){
                 "That\'s more <<sName>> than you have available.<br>";
              }else if(toInteger(s0) < 0){
                 "You can\'t deposit negative amounts of <<sName>>.<br>";
              }else if(toInteger(s0) >= 0){
                 "The banker says,\"Depositing <<s0>> <<sName>> to your account.\"<br>";                 
                 if(sName == 'copper'){
                    gameMain.myBank += toInteger(s0);// copper gets directly added
                    gameMain.myCarriedCopper -= toInteger(s0);        
                 }else if(sName == 'silver'){
                    gameMain.myBank += (toInteger(s0) * 100); // silver converted to copper is * 100
                    gameMain.myCarriedSilver -= toInteger(s0);
                 }else if(sName == 'gold'){
                    gameMain.myBank += (toInteger(s0) * 10000);// gold converted to copper is * 10000
                    gameMain.myCarriedGold -= toInteger(s0);
                 }
                 iDone++;
              }else{
                 "You must enter a numeric amount from 0 to <<toInteger(s0)>>.<br>";
              }
              "\n";
          }while(iDone==0);
  }

  withdraw(){
          local gold = new BigNumber(gameMain.myBank.getAbs() / 10000).getFloor();
          local silver = new BigNumber(gameMain.myBank.getAbs() / 100).getFloor();
          silver = silver.divideBy(100)[2];// second list item is remainder
          local copper = new BigNumber(gameMain.myBank.getAbs()).getFloor();
          copper = copper.divideBy(100)[2];// second list item is remainder
          if(gold + silver + copper == 0){
               "You have no money in the bank to withdraw!\b";
               inputManager.pauseForMore(true);  
               banker.showBank();              
           }else{
               if(copper != 0){
                     banker.withdrawAmount(copper,'copper');
               }
               if(silver != 0){
                     banker.withdrawAmount(silver,'silver');
               }               
               if(gold != 0){
                     banker.withdrawAmount(gold,'gold');
               }
               inputManager.pauseForMore(true);  
               banker.showBank();              
           }  
  } 

  /*
   *   where oMoneyObject = gameMain.myCarriedGold, ...silver, ... copper
   *                sName = 'gold', 'silver' or 'copper'
   */
  withdrawAmount(oMoneyObject,sName){
          local iDone = 0;
          local s0 = '';
          local oBigNum;
          local iFailDigitTest = 0;
          local digitPat = new RexPattern('<digit>');// matches a single digit in a string
          do{
              "<br>Please enter amount of <<sName>> to withdraw >";
              s0 = inputManager.getInputLine(nil, nil);
              if(s0 == nil) s0 = '0';
              if(s0 == '') s0 = '0';
              s0 = s0.toLower();
              // rex search for <digit> ... this stops cheaters from trying to trick the parser with math tokens, etc. 
              iFailDigitTest = 0;// reset flag 
              for(local i = 0; i <= s0.length;i++){
                if(! rexMatch(digitPat,s0,i)) iFailDigitTest++;
              }
              oBigNum = new BigNumber(s0);
              // getFraction() returns everything to the right of the decimal point, if this is > 0 it is a fraction
              if(oBigNum.getFraction() > 0){
                  "Please enter whole numbers.<br>";
              }else if(iFailDigitTest > 0){
                  "Please enter a whole number without any alphabetical or special characters (including the avoidance of decimal points). ";
              }else if(toInteger(s0) > oMoneyObject){
                 "That\'s more <<sName>> than you have in the bank.<br>";
              }else if(toInteger(s0) < 0){
                 "You can\'t withdraw negative amounts of <<sName>>.<br>";
              }else if(toInteger(s0) >= 0){
                 "The banker says,\"Withdrawing <<s0>> <<sName>> from your account.\"<br>";                 
                 if(sName == 'copper'){
                    gameMain.myBank -= toInteger(s0);
                    gameMain.myCarriedCopper += toInteger(s0);        
                 }else if(sName == 'silver'){
                    gameMain.myBank -= (toInteger(s0) * 100); // silver converted to copper is * 100
                    gameMain.myCarriedSilver += toInteger(s0);
                 }else if(sName == 'gold'){
                    gameMain.myBank -= (toInteger(s0) * 10000);// gold converted to copper is * 10000
                    gameMain.myCarriedGold += toInteger(s0);
                 }
                 iDone++;
              }else{
                 "You must enter a numeric amount from 0 to <<toInteger(s0)>>.<br>";
              }
              "\n";
          }while(iDone==0);
  }
;
+ bankerTalking: InConversationState
  stateDesc = nil
  attentionSpan = 4 // after 4 turns he goes back to wandering
  nextState = bankerWorking
  specialDesc = "The banker, Mr. Dribbik, stands behind his teller window talking to you. "
;
++ bankerWorking: ConversationReadyState
  specialDesc = "A banker is here. "
  // stateDesc = "A banker is here. "
  isInitState = true  
;
+++ bankerHello: HelloTopic
  topicResponse {
     "\"Hello there,\" you say to the banker.\b\"Welcome to my bank,\" he says.
     (<a href='ask about money'>ask about your money</a>)";
  } 
// <a plain href='<<libMessages.commandFullScore>>'>";
;
+++ bankerBye: ByeTopic
  topicResponse {
     "\"Bye,\" you say to the banker.\b\"Thank you for coming to my bank. See you again,\" he says. ";
   //  gTranscript.showReports(true);// Per Mike Roberts "...'true' turns off the transcript for the remainder of the turn, so if you're doing a series of timeDelay() pauses, you only need to do this once, before the first pause."
   //  banker.setCurState(bankerWorking);
  }
  isActive = bankerHello.isActive
;
+++ bankerImpBye: ImpByeTopic
   topicResponse {
           "The banker shrugs and waits for the next customer. ";
   }
;
++ bankerDAT: DefaultAskTellTopic
   topicResponse {
     "<q>What do you think about <<gTopic.getTopicText>>?</q> you ask.<.p>";
     "<q>This is a place of business, not a public inquiry office,\" the banker states rather coldly. He\'s expecting you to maybe <a href='ask about money'>ask about your money</a>.<.p> ";
   }
;
++ AskTopic @playerMoney
   name = 'your money'
   topicResponse {
        banker.showBank();// open up the banker transaction "screen"     
   }
;

I have a working system using this method, plus an experience point system, RPG character screen (with inventory slots), items with statistic modifiers, etc. in my latest ongoing project “Villain” over on this site (as programmer Cleo Kraft):
http://www.tfgamessite.com/
See this post once you log into the board (you may need game creator status to see this thread though - not sure): http://www.tfgamessite.com/phpbb3/viewtopic.php?f=6&t=2221