Programming Tic-Tac-Toe in Java


<<Previous | ToC | Next >>
 

English Winner

Here is my first cut at the English version of the PlayO subroutine, which is supposed to play defensively:
"PlayO"
  print "Play" who
  let doing = 0
  let sofar = 0
  let best = 0
  let whom = "B"
  repeat 9
    let doing = doing+1
    if board[doing]>"A" do it again
    let score = 0
    let prms = 123 {1st row}
    CountMarks
    if rez=0 let score = score+1   {open}
    if Wsum=2 let score = score+70  {win}
    otherwise let score = score+Wsum*2
    if Zsum=2 let score = score+15  {block}
    otherwise let score = score+Zsum+3
    let prms = 456 {2nd row...}
    ...
    if score<sofar do it again
    let best = doing
    let sofar = score
    Next
  let play = best
  Done


I added four more lines to the end of CountMarks (which this code depends on) so that it is truly able to play either side, and so it does each direction only once for each square:

  let Wsum = Osum
  let Zsum = Osum
  if who = "X" let Wsum = Xsum {Wsum is the player's (who's) sum}
  if who = "O" let Zsum = Xsum {Zsum is the opponent's sum}
  Done


To convert this to English we need to replace the array notation with the appropriate Substring commands (see the corresponding code in ThreeInaRow). Oh wait, in English a subroutine call messes with otherwise, so I worked around it by presetting play=0 then testing for that:

  print "input for " who
  Repeat
    let play = 0
    if who="O" do PlayO
    if play=0 Input play -1
    ...
The trouble is, it didn't work. It added up the score for all eight rows, columns, and diagonals, instead of only those containing the proposed play. So I added code to CountMarks to identify which of the eight lines contain the candidate square, and to return 0. That failed, because zero (empty line) is positively playable, so I changed it to return -1. Here is the revised version of both PO and CM, as adapted for the English computer
"PlayO"
  print "Play" who
  let doing = 0
  let sofar = 0
  let best = 0
  let whom = "B"
  repeat 9
    let doing = doing+1
    Substring tmp = board,doing
    if tmp>"A" do it again
    let score = 0
    let all8 = "123,456,789,147,258,369,159,357"
    repeat 8
      Substring prms = all8,0,3
      Substring all8,4,44
      CountMarks
      if rez=0 let score = score+1   {open}
      if Wsum=2 let score = score+70  {win}
      otherwise let score = score+Wsum*2
      if Zsum=2 let score = score+15  {block}
      otherwise let score = score+Zsum*3
      Next
    if score>sofar repeat 1
      let best = doing
      let sofar = score
      print "so far " best " + " score
      Next
    Next
  let play = best
  Done

"CountMarks"
  let doit = false  {= true if this line includes doing}
  Substring tmp = prms,0
  Substring first = board,tmp
  if tmp=doing let doit = true
  Substring tmp = prms,1
  Substring mid = board,tmp
  if tmp=doing let doit = true
  Substring tmp = prms,2
  Substring last = board,tmp
  if tmp=doing let doit = true
  let rez = -1
  let Xsum = 0
  let Osum = 0
  if doit repeat 3
    let info = first
    if info="X" let Xsum = Xsum+1
    if info="O" let Osum = Osum+1
    let first = mid
    let mid = last
    Next
  if doit let rez = Xsum*4+Osum
  if turns>5 let doit = false  {only log first two rounds}
  if whom = "X" let rez = Xsum {rez is returned result}
  if whom = "O" let rez = Osum
  let Wsum = Osum
  if who = "X" let Wsum = Xsum {Wsum is the player's sum}
  let Zsum = Osum
  if who = "O" let Zsum = Xsum {Zsum is the opponent's sum}
  if doit print "CM " who " " prms " x=" Xsum " o=" Osum
  Done


I added a couple of diagnostic print lines so that I could see intermediate results of the calculation. A debugger is usually better, but the English computer doesn't have one, so we do this the way everybody did it in the Unix and pre-unix days: lots of "console" printout. Sometimes that is still a good way to debug Java -- especially in large programs where you don't know where to put a breakpoint, and if you did, it would be stopping there all the time -- and I do it often.

I mention this because finding the mistakes in our code is a large part of programming, even the best of us spend well over half our time doing it (and much more in languages less helpful than Java, which is most of them). So don't feel bad if you spend a lot of time debugging: it comes with the job.
 

Java from English

Converting this part of the program to Java requires first backing out of the English hacks, and then deciding which of the variables can be converted to parameters or locals, and which must be preserved as class variables because they return multiple values out of a subroutine. Another option is to pack multiple return results into a single integer (see "Packed Numbers" in the "Useful Tech Issues" page).

A really esoteric solution is to define a carrier class and pass an object of that type as a parameter; the called subroutine can insert the return values into the instance variables of the parameter object(s), which the caller can then extract. If you didn't understand that, don't worry about it, it's probably over the head of more than half of all experienced Java programmers.

Related to that, but more in line with what everybody else teaches Java programmers about OOPS, is to make the subroutine a method of some object class (other than the caller's parent class, see "OOPS Is Subroutines" elsewhere), then the called method can leave the multiple result values as instance variables in its own class, and provide accessor functions to extract them. Our non-OOPS English solution is essentially the same, except that both the caller and the callee are effectively methods in the same parent class, so the return values are visible to both, so no accessor functions are needed. This is the same as "class variables" referred to above.

Subroutine CountMarks evaluates three result values used by the caller PlayO. You probably have enough skill and information now to make a workable choice how to do that in a language like Java where functions can return at most a single value. (Hint: if you leave all the variables as class variables, then it works the same as in English, but it's not as elegant as a better choice).

Perhaps you noticed my variable all8 which holds the coordinates of all eight lines to be tested for 3-in-a-row, in 3-digit chunks. I got tired of copy-pasting, so my English code now picks off three digits at a time, then CountMarks picks those three apart into three square numbers to be tested together. This doesn't work so easily in a strongly typed language like Java, but you can do very nearly the same thing using a (constant) final int[] array which you access using the control variable in the for-loop that you use to translate the "repeat 8" to Java. That would mean that each value in the array is a (preferably hexadecimal) 3-digit number that you can take apart, either here or inside CountMarks, by shifting and ANDing, like this:

final int[] all8 = {0x123,0x456,0x789,0x147,0x258,0x369,0x159,0x357};
int Wsum, Zsum; // set inside CountMarks, but used in PlayO
...
int PlayO();
  ...
  for (int doing=1; doing<10; doing++) {
    if (board[doing] > '9') continue;
    score = 0;
    for (int nx=0; nx<8; nx++) {
      int prms = all8[nx];
      int rez = CountMarks('B',doing,prms>>8,(prms>>4)&15,prms&15);
      if (rez==0) score++;
      else if (Wsum==2) score = score+70;
      ...
      } // end for(nx)
    ...
The important thing is, if you understand what I did here, you can use the same ideas anywhere. If you are confused, ask a friend or the instructor. That's what we are here for. Even if you do understand, you are still free to write your Java code some other way. It's even better if you do, because then you would understand two different ways to do it.

Like you could put single digits in your all8 array -- either 2-dinensioned, or else using an offset +8 and +16 for the middle and last digits -- then use the array access directly in the CountMarks call. You could experiment with packing the returned values into a single integer (see "Packed Numbers") or else a carrier class for returning the three numbers used in PlayO.

I leave you to make your own choices. Assuming you already have the display subroutine working, when your PlayO works, you have your own Tic-Tac-Toe program to show off to family and friends.

After you have it working, you can stop here (perhaps browse some of the other chapters you nave not yet read), or else go on to read about (and try!) some different data structures (turn the page).

<<Previous | ToC | Next >>

Revised: 2021 July 7