<<Previous | ToC
| Next >>
"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 " whoThe 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
Repeat
let play = 0
if who="O" do PlayO
if play=0 Input play -1
...
"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.
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};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.
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)
...
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