Remember how an AS/400 terminal looks like?
One of the interesting things about the platform is how programs actually interact with this. As in... sure, it's a "screen-oriented" terminal, so the contents of the screen will be downloaded to it in one go and then fields edited locally... but... you'd assume that at some point, your program will be printing these lines, right?
So then... look at the source code of that Battleship app! (This one is actually GPL!)
ctl-opt copyright('This program is free software, you can redistribute +
it and/or modify it under the terms of the GNU General Public License +
as published by the Free Software Foundation. See GNU General Public +
License for detail. Craig Rutledge < www.jcrcmds.com > ');
// JCRGMBTL - BattleShip
dcl-f JCRGMBTLD workstn infds(Infds);
InfdsFkey char(1) pos(369);
dcl-s col uns(3);
dcl-s ForCount uns(3);
dcl-s HashCol uns(3) dim(51);
dcl-s HashRow uns(3) dim(51);
dcl-s row uns(3);
dcl-s TimesHit2 uns(3);
dcl-s TimesHit3 uns(3);
dcl-s TimesHit4 uns(3);
... so... yeah. It's written in RPG. This language started out being used on punch cards, so the fact that you don't have to put certain parts of lines on certain fixed columns is already an improvement. What you're looking at here is... um... OK, we'll figure out that one later. We can find the main loop of the game though!
scDow = f_GetDayName(); exsr srSetupUserShips; 1b dou 1 = 2; exfmt screen2; // get cursor Row and Column QsnGetCsrAdr(QsnCursorRow: QsnCursorCol: 0: ApiErrDS); csrRow = QsnCursorRow; cSrCol = QsnCursorCol; // F5 = Restart 2b if InfdsFkey = f05; exsr srSetupUserShips; 1i iter; 2e endif; 2b if InfdsFkey = f03 or InfdsFkey = f12; 1v leave; 2e endif; // Process users attack, then let computer have shot at it! exsr srUserAttack; // Check and see if ALL enemy ships are sunk 2b if UserxHit2 = 9 and UserxHit3 = 9 and UserxHit4 = 9 and UserxHit5 = 9; GameOver = 'CONGRATULATIONS! YOU WIN!'; aGameover = %bitor(Green: RI); 2x else; exsr srComputerAttack; 2e endif; 1e enddo; *inlr = *on; return;
First of all: there is no asynchronous web weirdness here. Your game is multiple rounds of ships shooting at each other? Well, you'll write a loop then! Just as I described in that article about Web Push article, there is an actual server process paying attention to this terminal; even though communication is screen by screen, you don't have to save everything in a session and return, just you would need to do with HTTP.
But then... where are all the print statements? And what the hell is that "exfmt screen2;" doing?
Well... as it turns out, we do have something called "screen2":
A R SCREEN2 CSRLOC(CSRROW CSRCOL)
A CSRROW 3S 0H
A CSRCOL 3S 0H
A ATR0101 1A P
A ATR0102 1A P
A ATR0103 1A P
A ATR0104 1A P
A ATR0105 1A P
A ATR0106 1A P
A ATR0107 1A P
A ATR0108 1A P
A ATR0109 1A P
A UDSPATR5 1A P
A AGAMEOVER 1A P
A 1 3'JCRGMBTL' COLOR(BLU)
A 1 14'BATTLE SHIP!' COLOR(BLU)
A SCDOW 9A O 1 62COLOR(BLU)
A 1 72DATE EDTCDE(Y) COLOR(BLU)
A 2 8'ATTACK '
A DSPATR(HI UL)
A 2 42'DEFEND '
A DSPATR(HI UL)
A 2 72SYSNAME COLOR(BLU)
A 3 8'1'
A 3 10'2'
A 3 12'3'
A 3 14'4'
A 3 16'5'
A 3 18'6'
A 3 20'7'
A 3 22'8'
A 3 24'9'
A 3 26'0'
A 3 42'1'
A 3 44'2'
A 3 46'3'
A 3 48'4'
A 3 50'5'
A 3 52'6'
A 3 54'7'
A 3 56'8'
A 3 58'9'
A 3 60'0'
A R01C01 1A B 5 8DSPATR(&ATR0101)
A R01C02 1A B 5 10DSPATR(&ATR0102)
A R01C03 1A B 5 12DSPATR(&ATR0103)
A B10C08 1A O 14 56DSPATR(&BTR1008)
A B10C09 1A O 14 58DSPATR(&BTR1009)
A B10C10 1A O 14 60DSPATR(&BTR1010)
A 14 64'0'
A 16 8'Enemy Ship Status' DSPATR(UL HI)
A 16 42'Your Ship Status' DSPATR(UL HI)
A 17 8'Cruiser 2' DSPATR(&EDSPATR2)
A 17 42'Cruiser 2' DSPATR(&UDSPATR2)
A 18 8'Destroyer 3' DSPATR(&EDSPATR3)
A 18 42'Destroyer 3' DSPATR(&UDSPATR3)
A 19 8'BattleShip 4' DSPATR(&EDSPATR4)
A 19 42'BattleShip 4' DSPATR(&UDSPATR4)
A 20 8'Aircraft Carrier 5'
A 20 42'Aircraft Carrier 5'
A GAMEOVER 27A 21 20DSPATR(&AGAMEOVER)
A 23 7'Key X, press Enter to Fire!'
A 24 7'F3=Exit' COLOR(BLU)
A 24 41'F5=Restart' COLOR(BLU)
I did skip a lot of repetitive parts. But... yes, this is a screen. A template.
It has row and column numbers for each line! and actual text. Plus attributes (e.g. making some of the text blue). Now, there is this part of the code above:
dcl-ds GridDS qualified template;
col char(1) dim(10);
dcl-ds Deployed dim(10) likeds(GridDS);
dcl-ds Attack dim(10) likeds(GridDS) based(ptr); // enemy screen fields
dcl-ds AttackA dim(10) likeds(GridDS) based(ptr2); // enemy attrib array
dcl-ds Defend dim(10) likeds(GridDS) based(ptr3); // defend screen fields
dcl-ds DefendA dim(10) likeds(GridDS) based(ptr4); // defend attrib array
dcl-ds DefendSave dim(10) likeds(GridDS);
dcl-s ptr pointer inz(%addr(r01c01));
dcl-s ptr2 pointer inz(%addr(atr0101));
dcl-s ptr3 pointer inz(%addr(b01c01));
dcl-s ptr4 pointer inz(%addr(btr0101));
// map screen fields into DS so arrays can manipulate values
r01c01 etc. are just fields of a data structure, which... we then also map to an array of arrays, so that we can manipulate their contents with an actual loop in the program. But... essentially, what's happening is:
... we have a record type. With a bunch of fields in them.
By calling exmft screen2;, we print this data structure onto the screen, in a nicely formatted way, described by our screen template. Then we wait for user input (... the user editing the fields at this point), we read contents back into the variables, and we continue.
We also get to query what function key led to the submission of the form (... not entirely unlike HTTP forms). So... that's why there are no print statements: essentially, our global namespace gets plotted onto the screen, with everything being taken care of!
Punch cards and record formats
It kinda makes more sense if you imagine where all this is coming from. It was all punch cards!
Imagine a computer with a punch card reader and a printer attached. A program could be written that:
- reads a punch card that contains some info (e.g. an order from a customer)
- processes it somehow (e.g. adjusts purchase price and accumulates total orders)
- prints a report of the results (e.g. a list of parts to manufacture, with amounts)
Actually, "RPG" used to stand for "Report Program Generator"; its main purpose was doing something like this.
Given how everything was record-oriented, it made sense not to embed the formatting of the printed records or the input punch cards into the program logic itself (... these might change, after all); thus, these were defined separately, so that the actual program could focus on the data processing part itself. In fact, in early versions of RPG, it might even have been impossible to even express something like "record parsing / formatting"; in any case, you wouldn't want to perform such a complex operation in a high-level language anyway (vs. assembly code operating on templates).
Eventually, punch cards got upgraded into magnetic tapes... and, eventually, fancy terminals! But then... just the way UNIX still treats screens as teletypes with a serial input, a bell and a carriage that can be "returned", AS/400 and RPG are still thinking in terms of records that can come either from a database... or from a user typing them in one by one.
As with many things, it's a tradeoff. If you want to implement Battleship... it's nontrivial to make it fit into this "records" model, but, apparently, it can be done! On the other hand, if you do have records to work with, the resulting code is probably a lot simpler than an equivalent UNIX tool, busily parsing arguments and formatting output line by line. It's also a lot easier to modify how the output looks like.