Programming Tic-Tac-Toe in Java


<<Previous | ToC | Next >>
 

TTT Game Board Array Specification

What is it that subroutine "ThreeInaRow" does in the game board? It counts the number of times the current player's mark occurs in the three cells it has been given to look at, the three cells representing one of the eight rows, columns or diagonals of the game board. You can do that by comparing each of the three cells for equality to a reference variable (in the English code that would be variable who), and if equal, add one to a counter. Or you can do it by testing for a combined condition of all three matching (that's the way I did it in my English code). Maybe you thought of a different way to do it -- if you did, I would like to know 

Since we are looking for only one of two marks (X or O) we can do the counting by making those marks to be +1 and -1 (with unplayed locations =0), then just adding the three cells and return true if the sum is +3 or -3. That would be very efficient, but not very perspicuous.

The most obvious implementation, which is favored by Unix programmers because of that, is to make everything text (as I did in English) and then compare the characters in those positions. If you want to do that, then your game board should be an array of type char (not int). Then your Java code will look very much like my (non-English) English.
 

Parameters, Not Globals

The other place where (my) English does array-like things with text strings is in passing parameters to subroutine "ThreeInaRow". What you really want (and can do in Java, but not English) is to parameterize the calls to ThreeInaRow like this:
if (ThreeInaRow(who,3,5,7)) won = true;
else if ...
or maybe:
won = ThreeInaRow(who,3,5,7);
if (won) return true;
or  even:
won = (board[3]==who) && (board[5]==who) && (board[7]==who);
if (won) return true;
and eliminate ThreeInaRow as a subroutine ("method" in Java) entirely. But if you keep the method, it needs to return a boolean result (true if it found three in a row) and take the mark you are looking (who) for as a parameter of type char, as well as the numbers indexing the three cells in the row, column or diagonal being tested.

It is not obvious to you now in the small programs you are working on this month, but using parameters instead of global variables is like strong typing: it reduces the chances of undetected errors, because the parameter is set in the call and used only within the method, and cannot be accidentally altered elsewhere. Classes in Java do the same thing by eliminating global variables entirely, although "instance variables" (declared out at the class level) are like globals within the class. You always want to restrict data access to those parts of the code with (as they say in the military) "a need to know." Less unnecessary access means more robust (less easily broken) code. You want that.

Some variables need to be persistent across subroutine calls: the game board, who is playing, stuff like that (basically everything that is initialized in the Initialize method) you need to move them out to be class variables (declared and possibly initialized before the first subroutine).
 

One-Dimensional vs 2D

When we first introduced the question  back in the design phase we considered whether to use a 2-dimensional array to reflect the board as we see it, or else a 1-dimentional array to reflect the linear numbering scheme assigned to the nine squares. Now is the time to make that decision. You already know my opinion, but I think you should make the decision for yourself.

If you choose a 2D array, you can index the board in the display routine using the for-loop index variables for the rows and columns directly, but it won't help you at all in ThreeInaRow, you'd need to pass it both a row and column for each cell to be considered in each test. Initializing either a character or integer array with constant values can be done in the declaration with no extra code in Java (not even 2D), as we saw in the Seaman (ASCII Graphics) program.

No matter which internal representation you choose, you may need to convert between the two representations, one way or the other, because both formats are considered at one stage or the other during the program. The display routine needs to access the data by rows and columns. We linearized that with a secondary index variable step in "Iterating the Board" by carefully stepping through the board in numerical order. Otherwise we would need to do the conversion

index = row*3+column+k
where k is some constant that depends on the starting index values of your row, column, and final index ranges. If all the ranges start at 0 (like Java and C arrays), then k=0.

The conversion the other direction is the inverse of this, but (again, you wouldn't know this) much more expensive in processing time, because the hardware people have not figured out how to do integer division as fast as addition or subtraction; multiplication can be "pipelined" which overlaps the slower multiplication steps with other instructions in the hardware for effective (but not always) single-cycle operation:

row = (index-ki)/3+kr;
column = index-row*3+kc;

TTT Scoring in Java

OK, you have enough information to do the rest yourself. Make it run in Java. I suggest you start a new class, perhaps named "TTT" or "TTTscore". Obviously you need to translate the subroutine names to something Java accepts, and each of the five program primitives in the English code, you need to respell them in Java, but you already know how. If you get stuck, don't hesitate to ask. That's what we are here for.

After you have it working with ASCII graphics, you can stop here (perhaps browse some of the other chapters you nave not yet read), or else go on to making the same program run in the GameEngine (turn the page) or skip the GameEngine part and program the computer to play "O" intelligently.

<<Previous | ToC | Next >>

Revised: 2021 August 30