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.
COBOL predates SQL, and it’s likely that the SQL syntax was made similar to COBOL’s because of COBOL’s once-upon-a-time position as the dominant development language for businesses.
Thank you for pointing out the devilishly obvious.
Which implementation of Cobol did you use?
Just curious and wishing to try your code out.
Could you present at the end of each week the compiler/interpreter you used?
Sure: In this case, I used OpenCobol which as far as I know is the only open source implementation.
I’ll start including the environment details with posts from now on.
Ruby, then COBOL? Rather a violent transition, don’t you think?
Just wait for the hybrid: ROBOL.