I have to say, COBOL was more fun than I was expecting. I’m sure working on ancient, multi-KLOC mainframe apps is not as fun. But for my purposes, explicitly laying out my working memory and writing keywords in all caps is entertaining. More impressions:

COBOL is very bureaucratic. This is not a surprise, in itself, but it is surprising just how bureaucratic it is. Rigid divisions and sections for required metadata, working data, the equivalent of procedure parameters, the procedures themselves. Lots of verbiage and boilerplate. From a modern perspective, I think of high level languages as abstracting away much of the nitty gritty of memory management, function call mechanics, etc. COBOL strikes me, instead, as a way of supporting and enforcing organizational best practices of the day, while leaving a fair bit of the nitty gritty up to the programmer.

The punch card legacy is alive and well. Top-level lines must begin in column 8, and any text after column 80 is simply ignored. (If vim’s syntax highlighting had not warned me, I’m sure I’d have lost many hours to this.) There is a “free” layout mode available in more recent COBOL, but what fun is that?

You don’t really have typed variables. In fact, everything is, more or less, a string as far as I can tell. Instead, you lay out sections of memory, give them labels, and show the compiler a “picture” of how the memory is structured. The only type information you do have is that you can specify whether a given memory region should contain alphabetic, numeric, or alphanumeric characters. Alphabetic is represented by “A”, numeric by “9” and alphanumeric by “X”. So, for example “XXX9A” represents a 5-byte region that should contain three characters, a number, and a letter. You can also define totally different overlays on top of the same region of memory — which you would routinely do in order to, say, define an array with initial values. You can see this below where I used the “REDEFINES” keyword.

The wild-west memory layout and overlays, together with call by reference, let me do some magic tricks. For instance, I can check for a tie by just asking if the whole region where I keep the board placements is numeric. Or, I lay out the whole, formatted board at once, and overwrite sections of it as I go, rather than reformatting each time. I assume that this is the sort of “cleverness” that modern languages hope to prevent. 🙂

The syntax bears a strong resemblance to SQL. I presume this is not accidental.

Here’s the code:

IDENTIFICATION DIVISION. PROGRAM-ID. TicTacToe. DATA DIVISION. WORKING-STORAGE SECTION. 01 CurrentPlayer PIC A VALUE "X". 01 CurrentMove PIC 9(10). 01 RowSeparator PIC X(11) VALUE "---+---+---". * The board, for calculation purposes 01 CurrentBoard. 02 CurrentBoardValues PIC X(9) VALUE "123456789". 02 CurrentBoardTable REDEFINES CurrentBoardValues. 03 Cell OCCURS 9 TIMES PIC X. * The board, for display purposes 01 BoardForDisplay. 02 BoardValuesForDisplay. 03 RowOne PIC X(11) VALUE "(1)|(2)|(3)". 03 FILLER PIC X. 03 RowTwo PIC X(11) VALUE "(4)|(5)|(6)". 03 FILLER PIC X. 03 RowThree PIC X(11) VALUE "(7)|(8)|(9)". 03 FILLER PIC X. 02 FILLER REDEFINES BoardValuesForDisplay. 03 DisplayCell OCCURS 9 TIMES PIC X(4). 01 GameOver PIC X VALUE 'F'. PROCEDURE DIVISION. Begin. PERFORM WITH TEST AFTER UNTIL GameOver EQUAL 'T' PERFORM DisplayBoard DISPLAY "Select a square, " CurrentPlayer ": " WITH NO ADVANCING ACCEPT CurrentMove IF CurrentMove > 0 AND CurrentMove < 10 AND Cell(CurrentMove) NUMERIC MOVE CurrentPlayer TO Cell(CurrentMove) CALL "FormatCell" USING BY CONTENT CurrentPlayer BY REFERENCE DisplayCell(CurrentMove) PERFORM CheckForWin PERFORM CheckForDraw PERFORM SwitchPlayer END-IF END-PERFORM. STOP RUN. DisplayBoard. DISPLAY "" DISPLAY RowOne DISPLAY RowSeparator DISPLAY RowTwo DISPLAY RowSeparator DISPLAY RowThree DISPLAY "". CheckForWin. IF Cell(1) EQUAL Cell(2) AND Cell(2) EQUAL Cell(3) OR Cell(4) EQUAL Cell(5) AND Cell(5) EQUAL Cell(6) OR Cell(7) EQUAL Cell(8) AND Cell(8) EQUAL Cell(9) OR Cell(1) EQUAL Cell(4) AND Cell(4) EQUAL Cell(7) OR Cell(2) EQUAL Cell(5) AND Cell(5) EQUAL Cell(8) OR Cell(3) EQUAL Cell(6) AND Cell(6) EQUAL Cell(9) OR Cell(1) EQUAL Cell(5) AND Cell(5) EQUAL Cell(9) OR Cell(3) EQUAL Cell(5) AND Cell(5) EQUAL Cell(7) PERFORM DisplayBoard DISPLAY CurrentPlayer " Wins!" SET GameOver TO 'T' END-IF. CheckForDraw. IF CurrentBoard ALPHABETIC AND GameOver NOT EQUAL 'T' PERFORM DisplayBoard DISPLAY "It's a Draw!" SET GameOver TO 'T' END-IF. SwitchPlayer. IF CurrentPlayer EQUAL "X" THEN SET CurrentPlayer TO "O" ELSE SET CurrentPlayer TO "X" END-IF. IDENTIFICATION DIVISION. PROGRAM-ID. FormatCell DATA DIVISION. LINKAGE SECTION. 01 CellValue PIC X. 01 CellRepresentation. 02 LeftPad PIC X. 02 ContentSpace PIC X. 02 RightPad PIC X. 02 FILLER PIC X. PROCEDURE DIVISION USING CellValue, CellRepresentation. Begin. MOVE CellValue to ContentSpace IF CellValue NUMERIC MOVE "(" TO LeftPad MOVE ")" TO RightPad ELSE MOVE " " TO LeftPad MOVE " " TO RightPad END-IF. EXIT PROGRAM. END PROGRAM FormatCell. END PROGRAM TicTacToe.