obsolete.computer/geekery/

Cashflow Master, An 80-column C128 Budgeting Program

Cashflow Master on a Commodore 1084D monitor

Do you have trouble determining if you're making enough money to cover your recurring expenses? Do you have a Commodore 128 and an 80-column monitor sitting around? Well then this program listing is exactly what you need.

Cashflow Master is a program that takes your recurring income sources and expenses, "normalizes" them all to a common time period, and shows you how much is left over at the end of that period. It's based on a spreadsheet that I've been using (and evolving) for nearly 20 years. The spreadsheet of course does a lot of extra things specific to my own needs, and maybe someday they'll make it into Cashflow Master, but for now I thought it would be cool to start with the basics... and while I was at it, learn some new BASIC 7 tricks.

Why the 80 column display? For two reasons: One, for practical reasons as there could potentially be a lot of info on the screen at once. And two, well I guess as a kid writing C64 games and programs, the C128 and its mysterious 80 column display was very intriguing to me. A few years back, I was fortunate enough to find a C128 at a local thrift shop in perfect condition...for $3.00!. It was sitting in a pile of PC keyboards, so as you can guess, it was most likely mistaken for one and priced accordingly. It did not have a power supply with it, but I grabbed one on eBay for $18 and I was in business. However, at the time I didn't have a way to use the 80-column screen. So, fast forward to this year (see also my recent post about accessing an ANSI BBS from a C128 using DesTerm), I picked up a Commodore 1084D monitor with the correct RGB connection for the 128's VDC output.

Using It

The main screen at startup presents you with a two-column table. As the on-screen instructions indicate, you can press either E or I to add an expense or income respectively. For each entry, you specify a description, an amount, and a period -- i.e., how often it recurs. Once you've entered some expenses and at least one income source, Cashflow Master will calculate your remaining cash flow per the current global period - which is indicated at the bottom of the screen. Press the G key to cycle through global period settings. The global periods available are the same ones you can specify for each income or expense -- currently: yearly, quarterly, monthly, bi-weekly, weekly, or daily. When you're done, you can press the Q key to quit and poke around the program listing if you like.

VICE screenshots - 1 of 4 VICE screenshots - 2 of 4 VICE screenshots - 3 of 4 VICE screenshots - 4 of 4

The Listing

The program is broken up into a few main parts, starting at each of the following line numbers:

  • 5 - 10: Jumps to initialization routines.
  • 100 - 199: The mainloop which waits for keypresses and jumps elsewhere in the program accordingly.
  • 200 - 299: The "create a new income" subroutine.
  • 300 - 399: The "create a new expense" subroutine.
  • 999: Jump here to tidy up and end the program.
  • 1000-1999: Draw the screen. (Jump to 1500, if only re-writing the tables rather than a full clear + redraw.)
  • 2000-2999: Program initialization subroutine. Sets up variables and screen colors.
  • 3000-3999: A popup-window drawing routine. This draws a frame in the center of the screen and uses the BASIC 7 WINDOW command to set up a dialog box of sorts.
  • 4000-4040: This subroutine does all the calculation -- it adjusts each income and expense to match the global period, storing the results in two new array variables. Then the total remaining (or deficit) is calulated by summing the values in the two arrays.

Download

  • Get the program here: PRG format
  • If you prefer, you can download the BASIC Listing and convert to a PRG using the command petcat -w70 -o cfmaster.prg -- cfmaster.bas. (petcat is included with the VICE emulator.)
  • The program listing is below, as rendered by petcat.

;./CFMASTER ==1c01==
    5 gosub 2000 : rem init
   10 gosub 1000 : rem draw screen
  100 getkey k$ : rem mainloop
  105 if k$="q" then goto 999
  110 if k$="i" then gosub 200
  120 if k$="e" then gosub 300
  130 if k$="g" then begin
  131 gp=gp+1
  132 if gp>5 then gp=0
  133 gosub 1000
  134 bend
  199 goto 100
  200 gosub3000:print"create new income"
  205 ifni=mithenprint"max incomes (";mi;") already created.":print"press a key":getkeyk$:return
  210 print"income description";:inputd$
  220 print"income period (yqmbwd)";:inputp$
  225 ok=0:fori=0to5:ifp$(i)=p$thenip(ni)=i:ok=1
  226 nexti:ifok=0thenprint"invalid period selected.":goto220
  230 print"income amount";:inputa
  235 ifa<=0thenprint"incomes must be greater than zero.":goto230
  240 id$(ni)=d$
  250 ia(ni)=a:ni=ni+1
  299 print"{home}{home}":gosub1000:return
  300 gosub3000:print"create new expense"
  305 ifne=methenprint"max expenses (";me;") already created.":print"press a key":getkeyk$:return
  310 print"expense description";:inputd$
  320 print"expense period (yqmbwd)";:inputp$
  325 ok=0:fori=0to5:ifp$(i)=p$thenep(ne)=i:ok=1
  326 nexti:ifok=0thenprint"invalid period selected.":goto320
  330 print"expense amount";:inputa
  335 ifa<=0thenprint"expenses must be greater than zero.":goto330
  340 ed$(ne)=d$
  350 ea(ne)=a:ne=ne+1
  399 print"{home}{home}":gosub1000:return
  999 scnclr:slow:end
 1000 scnclr : rem full redraw
 1010 printh$;left$(x$,40-len(tt$)/2);r$;tt$
 1020 fori=0to78:char1,i,1,"C":char1,i,22,"C":nexti:char1,39,1,"{CBM-R}"
 1030 fori=2to21:char1,39,i,"B":nexti:char1,39,22,"{CBM-E}"
 1500 printh$;left$(y$,2);left$(x$,20-len(et$)/2);et$
 1510 if ne>0 then begin
 1520 fori=0tone-1
 1530 printleft$(ed$(i),md);left$(x$,md-len(ed$(i)));
 1540 print using cf$;ea(i);:print" ";r$;p$(ep(i))
 1550 nexti
 1560 bend:else:print"press {rvon}e{rvof} to add an expense"
 1570 printh$;left$(y$,2);left$(x$,60-len(it$)/2);it$
 1580 if ni>0 then begin
 1590 fori=0toni-1
 1600 printleft$(x$,40);left$(id$(i),md);left$(x$,md-len(id$(i)));
 1610 print using cf$;ia(i);:print" ";r$;p$(ip(i))
 1620 nexti
 1630 bend:else:printleft$(x$,40);"press {rvon}i{rvof} to add an income"
 1640 gosub 4000:rem calculate totals
 1650 printh$;left$(y$,23);"total expenses:";left$(x$,md-15);
 1655 print using cf$;et;
 1660 printh$;left$(y$,23);left$(x$,40);"total incomes:";left$(x$,md-14);
 1665 print using cf$;it;
 1670 printh$;left$(y$,24);"{rvon}g{rvof}lobal period: ";left$(x$,md-5-len(pd$(gp)));pd$(gp);
 1680 printh$;left$(y$,24);left$(x$,40);
 1690 ifre>=0 then print"{grn}remaining:";left$(x$,md-10);
 1700 ifre<0 then print"{red}deficit:";left$(x$,md-8);
 1710 print using cf$;re;:print"{wht}";
 1999 return
 2000 color5,2:color6,1
 2010 ifrgr(0)<>5thenprint"this program must be run in 80-column mode.":end:else fast
 2020 mi=20:me=20:ma=5:ni=0:ne=0:na=0:se=na:md=27:it=0:et=0:re=0
 2030 dim id$(mi),ip(mi),ia(mi),ai(mi)
 2040 dim ed$(me),ep(me),ea(me),ae(me)
 2050 dim ad$(ma),ap(ma),aa(ma)
 2060 y=5:q=4:m=3:b=2:w=1:d=0
 2061 dim p(6):p(y)=365.25:p(q)=p(y)/4:p(m)=p(y)/12:p(b)=14:p(w)=7:p(d)=1
 2062 dim p$(6):p$(y)="y":p$(q)="q":p$(m)="m":p$(b)="b":p$(w)="w":p$(d)="d"
 2063 dim pd$(6):pd$(y)="yearly":pd$(q)="quarterly":pd$(m)="monthly":pd$(b)="bi-weekly":pd$(w)="weekly":pd$(d)="daily"
 2065 h$="{home}":r$="{rvon}":x$="{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}":y$="{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}"
 2070 tt$=" c a s h f l o w  m a s t e r "
 2080 et$="expenses":it$="incomes":at$="accounts"
 2090 wt=229:wb=228:wl=230:wr=231:dt=5:db=20:dl=20:dr=60
 2100 gp=m : rem default to monthly
 2200 cf$="#######.##"
 2999 return
 3000 char1,dl-1,dt-1,"U":char1,dl-1,db+1,"J"
 3010 fori=dltodr:char1,i,dt-1,"C":char1,i,db+1,"C":nexti
 3020 char1,dr+1,dt-1,"I":char1,dr+1,db+1,"K"
 3030 fori=dttodb:char1,dl-1,i,"B":char1,dr+1,i,"B":nexti
 3040 windowdl,dt,dr,db,1:return
 4000 it=0:et=0:rem calculate totals
 4010 fori=0toni:ai(i)=ia(i)/p(ip(i))*p(gp):it=it+ai(i):nexti
 4020 fori=0tone:ae(i)=ea(i)/p(ep(i))*p(gp):et=et+ae(i):nexti
 4030 re=it-et
 4040 return

Modified Friday, November 07, 2025