forked from gutomaia/inventwithpython
-
Notifications
You must be signed in to change notification settings - Fork 0
/
chapter15.html
1359 lines (1116 loc) · 123 KB
/
chapter15.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("UA-5459430-3");
pageTracker._trackPageview();
} catch(err) {}</script>
<meta http-equiv="Content-Type" content="text/html;charset=us-ascii" />
<title>IYOCGwP, Chapter 15 - Reversi</title>
<link rel="stylesheet" href="inventbook.css" type="text/css" media="all" />
</head>
<body class='chapter15body'>
<table border='0' width='100%'><tr><td><a href='chapter14.html'>Go to Chapter 14 - Caesar Cipher</a></td><td align='right'><a href='chapter16.html'>Go to Chapter 16 - AI Simulation</a></td></tr></table>
<div style='height: 310px;'><a href='http://www.amazon.com/Invent-Your-Computer-Games-Python/dp/0982106017/'><img src='images/buyad.png' align='right'></a></div>
<div style='height: 350px;'><img src='images/chap15.png'></div>
<div class='inthischapter'><h3 id="TopicsCoveredInThisChapter">Topics Covered In This Chapter:</h3>
<ul>
<li>The <span class='m'>bool()</span> Function</li>
<li>Evaluating Non-Boolean Values as Booleans</li>
</ul></div>
<h2 id="HowtoPlayReversi">How to Play Reversi</h2>
<p>In this chapter we will make a game called Reversi. Reversi (also called Othello) is a board game that is played on a grid (so we will use a Cartesian coordinate system with XY coordinates, like we did with Sonar.) It is a game played with two players. Our version of the game will have a computer AI that is more advanced than the AI we made for Tic Tac Toe. In fact, this AI is so good that it will probably beat you almost every time you play. (I know I lose whenever I play against it!)</p>
<p>If you would like to see a video of Reversi being played, there is a demonstration on this book's website. Go to the URL <a href='http://inventwithpython.com/videos'>http://inventwithpython.com/videos</a> and find the "Reversi Demo Video" video.</p>
<p>Reversi has an 8 x 8 board with tiles that are black on one side and white on the other (our game will use O's and X's though). The starting board looks like Figure 15-1. Each player takes turn placing down a new tile of their color. Any of the opponent's tiles that are between the new tile and the other tiles of that color is flipped. The goal of the game is to have as many of the tiles with your color as possible. For example, Figure 15-2 is what it looks like if the white player places a new white tile on space 5, 6.</p>
<table border='0' class='centeredImageP'>
<tr><td style='width: 280px;'><img src='images/15-1.png' alt='' class='centeredImage' /></td><td style='width: 280px;'><img src='images/15-2.png' alt='' class='centeredImage' /></td></tr>
<tr><td>Figure 15-1: The starting Reversi board<br />has two white tiles and two black tiles.</td><td>Figure 15-2: White places a new tile.</td></tr>
</table>
<p>The black tile at 5, 5 is in between the new white tile and the existing white tile at 5, 4. That black tile is flipped over and becomes a new white tile, making the board look like Figure 15-3. Black makes a similar move next, placing a black tile on 4, 6 which flips the white tile at 4, 5. This results in a board that looks like Figure 15-4.</p>
<table border='0' class='centeredImageP'>
<tr><td style='width: 280px;'><img src='images/15-3.png' alt='' class='centeredImage' /></td><td style='width: 280px;'><img src='images/15-4.png' alt='' class='centeredImage' /></td></tr>
<tr><td>Figure 15-3: White's move will<br />flip over one of black's tiles.</td><td>Figure 15-4: Black places a new tile,<br />which flips over one of white's tiles.</td></tr>
</table>
<p>Tiles in all directions are flipped as long as they are in between the player's new tile and existing tile. In Figure 15-5, the white player places a tile at 3, 6 and flips black tiles in both directions (marked by the lines.) The result is in Figure 15-6.</p>
<table border='0' class='centeredImageP'>
<tr><td style='width: 280px;'><img src='images/15-5.png' alt='' class='centeredImage' /></td><td style='width: 280px;'><img src='images/15-6.png' alt='' class='centeredImage' /></td></tr>
<tr><td>Figure 15-5: White's second move<br />at 3, 6 will flip two of black's tiles.</td><td>Figure 15-6: The board after white's second move.</td></tr>
</table>
<p>As you can see, each player can quickly grab a majority of the tiles on the board in just one or two moves. Players must always make a move that captures at least one tile. The game ends when a player either cannot make a move, or the board is completely full. The player with the most tiles of their color wins.</p>
<p>The basic strategy of Reversi is to look at which move would turn over the most tiles. But you should also consider taking a move that will not let your opponent recapture many tiles after your move. Placing a tile on the sides or, even better, the corners is good because there is less chance that those tiles will end up between your opponent's tiles. The AI we make for this game will simply look for any corner moves they can take. If there are no corner moves available, then the computer will select the move that claims the most tiles.</p>
<p>You can learn more about Reversi from Wikipedia: <a href='http://en.wikipedia.org/wiki/Reversi'>http://en.wikipedia.org/wiki/Reversi</a></p>
<div class='createspace'><br /><br /></div>
<h2 id="SampleRun">Sample Run</h2>
<p>Notice that our version of Reversi doesn't use black and white tiles because the text that our program creates will always be the same color. Instead, we will use X's and O's to represent the human and computer players.</p>
<div class='samplerun'>
Welcome to Reversi!<br />
Do you want to be X or O?<br />
<span class='sampleruninput'>x</span><br />
The player will go first.<br />
1 2 3 4 5 6 7 8<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
1 | | | | | | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
2 | | | | | | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
3 | | | | | | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
4 | | | | X | O | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
5 | | | | O | X | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
6 | | | | | | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
7 | | | | | | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
8 | | | | | | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
You have 2 points. The computer has 2 points.<br />
Enter your move, or type quit to end the game, or hints to turn off/on hints.<br />
<span class='sampleruninput'>53</span><br />
1 2 3 4 5 6 7 8<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
1 | | | | | | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
2 | | | | | | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
3 | | | | | X | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
4 | | | | X | X | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
5 | | | | O | X | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
6 | | | | | | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
7 | | | | | | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
8 | | | | | | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
You have 4 points. The computer has 1 points.<br />
Press Enter to see the computer's move.<br />
<br />
<br />
<i>...skipped for brevity...</i><br />
<br />
<br />
1 2 3 4 5 6 7 8<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
1 | O | O | O | O | O | O | O | O |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
2 | O | O | O | O | O | O | O | O |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
3 | O | O | O | O | O | O | O | O |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
4 | O | O | X | O | O | O | O | O |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
5 | O | O | O | X | O | X | O | X |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
6 | O | X | O | X | X | O | O | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
7 | O | X | X | O | O | O | O | O |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
8 | O | X | X | O | | | X | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
You have 12 points. The computer has 48 points.<br />
Enter your move, or type quit to end the game, or hints to turn off/on hints.<br />
<span class='sampleruninput'>86</span><br />
X scored 15 points. O scored 46 points.<br />
You lost. The computer beat you by 31 points.<br />
Do you want to play again? (yes or no)<br />
<span class='sampleruninput'>no</span><br />
</div>
<p>As you can see, the AI was pretty good at beating me. To help the player out, we'll program our game to provide hints. If the player types <span class='m'>'hints'</span> as their move, they can toggle the hints mode on and off. When hints mode is on, all the possible moves the player can make will show up on the board as <span class='m'>'.'</span> characters, like this:</p>
<div class='sourceblurb' style='font-size: small;'>
1 2 3 4 5 6 7 8<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
1 | | | | | | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
2 | | | | . | | . | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
3 | | | | O | O | O | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
4 | | | . | O | O | X | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
5 | | | . | O | O | O | X | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
6 | | | | . | | . | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
7 | | | | | | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
8 | | | | | | | | |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
</div>
<h2 id="ReversisSourceCode">Reversi's Source Code</h2>
<p>Reversi is a mammoth program compared to our previous games. It comes in over 300 lines long! (But don't worry, many of these lines are just comments or blank lines to space out the code and make it more readable.) As always, you don't have to type in the program before reading this chapter. And you can also download the program by going to this book's website at the URL, <a href='http://inventwithpython.com/chapter15'>http://inventwithpython.com/chapter15</a> and following the instructions online.</p>
<p>As with our other programs, we will first create several functions to carry out Reversi-related tasks that the main section of our program will call. Roughly the first 250 lines of code are for these helper functions, and the last 50 lines of code implement the Reversi game itself.</p>
<div class='sourcecode'><span class='sourcecodeHeader'>reversi.py</span><br /><span class='sourcecodeSubHeader'>This code can be downloaded from <a href='http://inventwithpython.com/reversi.py'>http://inventwithpython.com/reversi.py</a><br />If you get errors after typing this code in, compare it to the book's code with the online diff tool at <a href='http://inventwithpython.com/diff'>http://inventwithpython.com/diff</a> or email the author at <a href="mailto:[email protected]">[email protected]</a></span><br /><ol start='1'>
<li># Reversi</li>
<li></li>
<li>import random</li>
<li>import sys</li>
<li></li>
<li>def drawBoard(board):</li>
<li> # This function prints out the board that it was passed. Returns None.</li>
<li> HLINE = ' +---+---+---+---+---+---+---+---+'</li>
<li> VLINE = ' | | | | | | | | |'</li>
<li></li>
<li> print(' 1 2 3 4 5 6 7 8')</li>
<li> print(HLINE)</li>
<li> for y in range(8):</li>
<li> print(VLINE)</li>
<li> print(y+1, end=' ')</li>
<li> for x in range(8):</li>
<li> print('| %s' % (board[x][y]), end=' ')</li>
<li> print('|')</li>
<li> print(VLINE)</li>
<li> print(HLINE)</li>
<li></li>
<li></li>
<li>def resetBoard(board):</li>
<li> # Blanks out the board it is passed, except for the original starting position.</li>
<li> for x in range(8):</li>
<li> for y in range(8):</li>
<li> board[x][y] = ' '</li>
<li></li>
<li> # Starting pieces:</li>
<li> board[3][3] = 'X'</li>
<li> board[3][4] = 'O'</li>
<li> board[4][3] = 'O'</li>
<li> board[4][4] = 'X'</li>
<li></li>
<li></li>
<li>def getNewBoard():</li>
<li> # Creates a brand new, blank board data structure.</li>
<li> board = []</li>
<li> for i in range(8):</li>
<li> board.append([' '] * 8)</li>
<li></li>
<li> return board</li>
<li></li>
<li></li>
<li>def isValidMove(board, tile, xstart, ystart):</li>
<li> # Returns False if the player's move on space xstart, ystart is invalid.</li>
<li> # If it is a valid move, returns a list of spaces that would become the player's if they made a move here.</li>
<li> if board[xstart][ystart] != ' ' or not isOnBoard(xstart, ystart):</li>
<li> return False</li>
<li></li>
<li> board[xstart][ystart] = tile # temporarily set the tile on the board.</li>
<li></li>
<li> if tile == 'X':</li>
<li> otherTile = 'O'</li>
<li> else:</li>
<li> otherTile = 'X'</li>
<li></li>
<li> tilesToFlip = []</li>
<li> for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]:</li>
<li> x, y = xstart, ystart</li>
<li> x += xdirection # first step in the direction</li>
<li> y += ydirection # first step in the direction</li>
<li> if isOnBoard(x, y) and board[x][y] == otherTile:</li>
<li> # There is a piece belonging to the other player next to our piece.</li>
<li> x += xdirection</li>
<li> y += ydirection</li>
<li> if not isOnBoard(x, y):</li>
<li> continue</li>
<li> while board[x][y] == otherTile:</li>
<li> x += xdirection</li>
<li> y += ydirection</li>
<li> if not isOnBoard(x, y): # break out of while loop, then continue in for loop</li>
<li> break</li>
<li> if not isOnBoard(x, y):</li>
<li> continue</li>
<li> if board[x][y] == tile:</li>
<li> # There are pieces to flip over. Go in the reverse direction until we reach the original space, noting all the tiles along the way.</li>
<li> while True:</li>
<li> x -= xdirection</li>
<li> y -= ydirection</li>
<li> if x == xstart and y == ystart:</li>
<li> break</li>
<li> tilesToFlip.append([x, y])</li>
<li></li>
<li> board[xstart][ystart] = ' ' # restore the empty space</li>
<li> if len(tilesToFlip) == 0: # If no tiles were flipped, this is not a valid move.</li>
<li> return False</li>
<li> return tilesToFlip</li>
<li></li>
<li></li>
<li>def isOnBoard(x, y):</li>
<li> # Returns True if the coordinates are located on the board.</li>
<li> return x >= 0 and x <= 7 and y >= 0 and y <=7</li>
<li></li>
<li></li>
<li>def getBoardWithValidMoves(board, tile):</li>
<li> # Returns a new board with . marking the valid moves the given player can make.</li>
<li> dupeBoard = getBoardCopy(board)</li>
<li></li>
<li> for x, y in getValidMoves(dupeBoard, tile):</li>
<li> dupeBoard[x][y] = '.'</li>
<li> return dupeBoard</li>
<li></li>
<li></li>
<li>def getValidMoves(board, tile):</li>
<li> # Returns a list of [x,y] lists of valid moves for the given player on the given board.</li>
<li> validMoves = []</li>
<li></li>
<li> for x in range(8):</li>
<li> for y in range(8):</li>
<li> if isValidMove(board, tile, x, y) != False:</li>
<li> validMoves.append([x, y])</li>
<li> return validMoves</li>
<li></li>
<li></li>
<li>def getScoreOfBoard(board):</li>
<li> # Determine the score by counting the tiles. Returns a dictionary with keys 'X' and 'O'.</li>
<li> xscore = 0</li>
<li> oscore = 0</li>
<li> for x in range(8):</li>
<li> for y in range(8):</li>
<li> if board[x][y] == 'X':</li>
<li> xscore += 1</li>
<li> if board[x][y] == 'O':</li>
<li> oscore += 1</li>
<li> return {'X':xscore, 'O':oscore}</li>
<li></li>
<li></li>
<li>def enterPlayerTile():</li>
<li> # Let's the player type which tile they want to be.</li>
<li> # Returns a list with the player's tile as the first item, and the computer's tile as the second.</li>
<li> tile = ''</li>
<li> while not (tile == 'X' or tile == 'O'):</li>
<li> print('Do you want to be X or O?')</li>
<li> tile = input().upper()</li>
<li></li>
<li> # the first element in the tuple is the player's tile, the second is the computer's tile.</li>
<li> if tile == 'X':</li>
<li> return ['X', 'O']</li>
<li> else:</li>
<li> return ['O', 'X']</li>
<li></li>
<li></li>
<li>def whoGoesFirst():</li>
<li> # Randomly choose the player who goes first.</li>
<li> if random.randint(0, 1) == 0:</li>
<li> return 'computer'</li>
<li> else:</li>
<li> return 'player'</li>
<li></li>
<li></li>
<li>def playAgain():</li>
<li> # This function returns True if the player wants to play again, otherwise it returns False.</li>
<li> print('Do you want to play again? (yes or no)')</li>
<li> return input().lower().startswith('y')</li>
<li></li>
<li></li>
<li>def makeMove(board, tile, xstart, ystart):</li>
<li> # Place the tile on the board at xstart, ystart, and flip any of the opponent's pieces.</li>
<li> # Returns False if this is an invalid move, True if it is valid.</li>
<li> tilesToFlip = isValidMove(board, tile, xstart, ystart)</li>
<li></li>
<li> if tilesToFlip == False:</li>
<li> return False</li>
<li></li>
<li> board[xstart][ystart] = tile</li>
<li> for x, y in tilesToFlip:</li>
<li> board[x][y] = tile</li>
<li> return True</li>
<li></li>
<li></li>
<li>def getBoardCopy(board):</li>
<li> # Make a duplicate of the board list and return the duplicate.</li>
<li> dupeBoard = getNewBoard()</li>
<li></li>
<li> for x in range(8):</li>
<li> for y in range(8):</li>
<li> dupeBoard[x][y] = board[x][y]</li>
<li></li>
<li> return dupeBoard</li>
<li></li>
<li></li>
<li>def isOnCorner(x, y):</li>
<li> # Returns True if the position is in one of the four corners.</li>
<li> return (x == 0 and y == 0) or (x == 7 and y == 0) or (x == 0 and y == 7) or (x == 7 and y == 7)</li>
<li></li>
<li></li>
<li>def getPlayerMove(board, playerTile):</li>
<li> # Let the player type in their move.</li>
<li> # Returns the move as [x, y] (or returns the strings 'hints' or 'quit')</li>
<li> DIGITS1TO8 = '1 2 3 4 5 6 7 8'.split()</li>
<li> while True:</li>
<li> print('Enter your move, or type quit to end the game, or hints to turn off/on hints.')</li>
<li> move = input().lower()</li>
<li> if move == 'quit':</li>
<li> return 'quit'</li>
<li> if move == 'hints':</li>
<li> return 'hints'</li>
<li></li>
<li> if len(move) == 2 and move[0] in DIGITS1TO8 and move[1] in DIGITS1TO8:</li>
<li> x = int(move[0]) - 1</li>
<li> y = int(move[1]) - 1</li>
<li> if isValidMove(board, playerTile, x, y) == False:</li>
<li> continue</li>
<li> else:</li>
<li> break</li>
<li> else:</li>
<li> print('That is not a valid move. Type the x digit (1-8), then the y digit (1-8).')</li>
<li> print('For example, 81 will be the top-right corner.')</li>
<li></li>
<li> return [x, y]</li>
<li></li>
<li></li>
<li>def getComputerMove(board, computerTile):</li>
<li> # Given a board and the computer's tile, determine where to</li>
<li> # move and return that move as a [x, y] list.</li>
<li> possibleMoves = getValidMoves(board, computerTile)</li>
<li></li>
<li> # randomize the order of the possible moves</li>
<li> random.shuffle(possibleMoves)</li>
<li></li>
<li> # always go for a corner if available.</li>
<li> for x, y in possibleMoves:</li>
<li> if isOnCorner(x, y):</li>
<li> return [x, y]</li>
<li></li>
<li> # Go through all the possible moves and remember the best scoring move</li>
<li> bestScore = -1</li>
<li> for x, y in possibleMoves:</li>
<li> dupeBoard = getBoardCopy(board)</li>
<li> makeMove(dupeBoard, computerTile, x, y)</li>
<li> score = getScoreOfBoard(dupeBoard)[computerTile]</li>
<li> if score > bestScore:</li>
<li> bestMove = [x, y]</li>
<li> bestScore = score</li>
<li> return bestMove</li>
<li></li>
<li></li>
<li>def showPoints(playerTile, computerTile):</li>
<li> # Prints out the current score.</li>
<li> scores = getScoreOfBoard(mainBoard)</li>
<li> print('You have %s points. The computer has %s points.' % (scores[playerTile], scores[computerTile]))</li>
<li></li>
<li></li>
<li></li>
<li>print('Welcome to Reversi!')</li>
<li></li>
<li>while True:</li>
<li> # Reset the board and game.</li>
<li> mainBoard = getNewBoard()</li>
<li> resetBoard(mainBoard)</li>
<li> playerTile, computerTile = enterPlayerTile()</li>
<li> showHints = False</li>
<li> turn = whoGoesFirst()</li>
<li> print('The ' + turn + ' will go first.')</li>
<li></li>
<li> while True:</li>
<li> if turn == 'player':</li>
<li> # Player's turn.</li>
<li> if showHints:</li>
<li> validMovesBoard = getBoardWithValidMoves(mainBoard, playerTile)</li>
<li> drawBoard(validMovesBoard)</li>
<li> else:</li>
<li> drawBoard(mainBoard)</li>
<li> showPoints(playerTile, computerTile)</li>
<li> move = getPlayerMove(mainBoard, playerTile)</li>
<li> if move == 'quit':</li>
<li> print('Thanks for playing!')</li>
<li> sys.exit() # terminate the program</li>
<li> elif move == 'hints':</li>
<li> showHints = not showHints</li>
<li> continue</li>
<li> else:</li>
<li> makeMove(mainBoard, playerTile, move[0], move[1])</li>
<li></li>
<li> if getValidMoves(mainBoard, computerTile) == []:</li>
<li> break</li>
<li> else:</li>
<li> turn = 'computer'</li>
<li></li>
<li> else:</li>
<li> # Computer's turn.</li>
<li> drawBoard(mainBoard)</li>
<li> showPoints(playerTile, computerTile)</li>
<li> input('Press Enter to see the computer\'s move.')</li>
<li> x, y = getComputerMove(mainBoard, computerTile)</li>
<li> makeMove(mainBoard, computerTile, x, y)</li>
<li></li>
<li> if getValidMoves(mainBoard, playerTile) == []:</li>
<li> break</li>
<li> else:</li>
<li> turn = 'player'</li>
<li></li>
<li> # Display the final score.</li>
<li> drawBoard(mainBoard)</li>
<li> scores = getScoreOfBoard(mainBoard)</li>
<li> print('X scored %s points. O scored %s points.' % (scores['X'], scores['O']))</li>
<li> if scores[playerTile] > scores[computerTile]:</li>
<li> print('You beat the computer by %s points! Congratulations!' % (scores[playerTile] - scores[computerTile]))</li>
<li> elif scores[playerTile] < scores[computerTile]:</li>
<li> print('You lost. The computer beat you by %s points.' % (scores[computerTile] - scores[playerTile]))</li>
<li> else:</li>
<li> print('The game was a tie!')</li>
<li></li>
<li> if not playAgain():</li>
<li> break</li>
</ol></div>
<h2 id="HowtheCodeWorks">How the Code Works</h2>
<h3 id="TheGameBoardDataStructure">The Game Board Data Structure</h3>
<p>Before we get into the code, we should talk about the board data structure. This data structure is a list of lists, just like the one in our previous Sonar game. The list is created so that <span class='m'>board[x][y]</span> will represent the character on space located at position <span class='m'>x</span> on the X-axis (going left/right) and position <span class='m'>y</span> on the Y-axis (going up/down). This character can either be a <span class='m'>' '</span> space character (to represent a blank space), a <span class='m'>'.'</span> period character (to represent a possible move in hint mode), or an <span class='m'>'X'</span> or <span class='m'>'O'</span> (to represent a player's tile). Whenever you see a parameter named board, that parameter variable is meant to be this list of lists <span class='m'>board</span> data structure.</p>
<h3 id="ImportingOtherModules">Importing Other Modules</h3>
<div class='sourcecode'><ol start='1'>
<li># Reversi</li>
<li></li>
<li>import random</li>
<li>import sys</li>
</ol></div>
<p>We import the <span class='m'>random</span> module for its <span class='m'>randint()</span> and <span class='m'>choice()</span> functions and the <span class='m'>sys</span> module for its <span class='m'>exit()</span> function.</p>
<h3 id="DrawingtheBoardDataStructureontheScreen">Drawing the Board Data Structure on the Screen</h3>
<div class='sourcecode'><ol start='6'>
<li>def drawBoard(board):</li>
<li> # This function prints out the board that it was passed. Returns None.</li>
<li> HLINE = ' +---+---+---+---+---+---+---+---+'</li>
<li> VLINE = ' | | | | | | | | |'</li>
<li></li>
<li> print(' 1 2 3 4 5 6 7 8')</li>
<li> print(HLINE)</li>
</ol></div>
<p>The <span class='m'>drawBoard()</span> function will print out the current game <span class='m'>board</span> based on the data structure in board. Notice that each square of the board looks like this:</p>
<div class='sourceblurb'>
+---+<br />
| |<br />
| X |<br />
| |<br />
+---+<br />
</div>
<p>Since we are going to print the string with the horizontal line (and plus signs at the intersections) over and over again, we will store that in a constant variable named <span class='m'>HLINE</span>. There are also lines above and below the very center of X or O tile that are nothing but '|' characters (called "pipe" characters) with three spaces in between. We will store this string in a constant named <span class='m'>VLINE</span>.</p>
<p>Line 11 is the first <span class='m'>print()</span> function call executed, and it prints out the labels for the X-axis along the top of the board. Line 12 prints the top horizontal line of the board.</p>
<div class='sourcecode'><ol start='13'>
<li> for y in range(8):</li>
<li> print(VLINE)</li>
<li> print(y+1, end=' ')</li>
<li> for x in range(8):</li>
<li> print('| %s' % (board[x][y]), end=' ')</li>
<li> print('|')</li>
<li> print(VLINE)</li>
<li> print(HLINE)</li>
</ol></div>
<p>Printing each row of spaces on the board is fairly repetitive, so we can use a loop here. We will loop eight times, once for each row. Line 15 prints the label for the Y-axis on the left side of the board, and has an <span class='m'>end=' '</span> keyword argument at the end of it to print a single space instead of a new line. This is so we can have another loop (which again loops eight times, once for each space) print out each space (along with the <span class='m'>'X'</span>, <span class='m'>'O'</span>, or <span class='m'>' '</span> character for that space depending on what is stored in <span class='m'>board</span>.)</p>
<p>The <span class='m'>print()</span> function call inside the inner loop also has an <span class='m'>end=' '</span> keyword argument at the end of it, meaning a space character is printed instead of a newline character. This produces the second space in the pipe-space-tile-space string that we print out, over and over for eight times. That will produce a single line on the screen that looks like <span class='m'>'| X | X | X | X | X | X | X | X '</span> (that is, if each of the <span class='m'>board[x][y]</span> values were <span class='m'>'X'</span>). After the inner loop is done, the <span class='m'>print()</span> function call on line 18 prints out the final <span class='m'>'|'</span> character along with a newline (since it does not end with an <span class='m'>end</span> keyword argument).</p>
<p>(The <span class='m'>print()</span>call forces us to always print a newline character or a space at the end of everything we print. If we do not want this last character, then we can always use the <span class='m'>sys.stdout.write()</span> function, which has a single string parameter that it prints out. Be sure to <span class='m'>import sys</span> first before calling this function.)</p>
<p>The code inside the outer <span class='m'>for</span> loop from line 14 to line 20 prints out an entire row of the board like this:</p>
<div class='sourceblurb' style='font-size: small;'>
| | | | | | | | |<br />
| X | X | X | X | X | X | X | X |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
</div>
<p>When the <span class='m'>for</span> loop on line 13 prints the row eight times, it forms the entire board (of course, some of the spaces on the board will have <span class='m'>'O'</span> or <span class='m'>' '</span> instead of <span class='m'>'X'</span>):</p>
<div class='sourceblurb' style='font-size: small;'>
| | | | | | | | |<br />
| X | X | X | X | X | X | X | X |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
| X | X | X | X | X | X | X | X |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
| X | X | X | X | X | X | X | X |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
| X | X | X | X | X | X | X | X |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
| X | X | X | X | X | X | X | X |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
| X | X | X | X | X | X | X | X |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
| X | X | X | X | X | X | X | X |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
| | | | | | | | |<br />
| X | X | X | X | X | X | X | X |<br />
| | | | | | | | |<br />
+---+---+---+---+---+---+---+---+<br />
</div>
<h3 id="ResettingtheGameBoard">Resetting the Game Board</h3>
<p>An important thing to remember is that the coordinates that we print out to the player are from 1 to 8, but the indexes in the <span class='m'>board</span> data structure are from 0 to 7.</p>
<div class='sourcecode'><ol start='23'>
<li>def resetBoard(board):</li>
<li> # Blanks out the board it is passed, except for the original starting position.</li>
<li> for x in range(8):</li>
<li> for y in range(8):</li>
<li> board[x][y] = ' '</li>
</ol></div>
<p>Here we use a loop inside a loop to set the <span class='m'>board</span> data structure to be all single-space strings to make a blank Reversi board. We will call the <span class='m'>resetBoard()</span> function whenever we start a new game and want to remove the tiles from a previous game.</p>
<h3 id="SettingUptheStartingPieces">Setting Up the Starting Pieces</h3>
<div class='sourcecode'><ol start='29'>
<li> # Starting pieces:</li>
<li> board[3][3] = 'X'</li>
<li> board[3][4] = 'O'</li>
<li> board[4][3] = 'O'</li>
<li> board[4][4] = 'X'</li>
</ol></div>
<p>When we start a new game of Reversi, it isn't enough to have a completely blank board. At the very beginning, each player has two tiles already laid down in the very center, so we will also have to set those.</p>
<p>We do not have to return the <span class='m'>board</span> variable, because <span class='m'>board</span> is a reference to a list. Even when we make changes inside the local function's scope, these changes happen to the original list that was passed as an argument. (Remember, this is one way list variables are different from non-list variables.)</p>
<h3 id="CreatingaNewGameBoardDataStructure">Creating a New Game Board Data Structure</h3>
<div class='sourcecode'><ol start='36'>
<li>def getNewBoard():</li>
<li> # Creates a brand new, blank board data structure.</li>
<li> board = []</li>
<li> for i in range(8):</li>
<li> board.append([' '] * 8)</li>
<li></li>
<li> return board</li>
</ol></div>
<p>The <span class='m'>getNewBoard()</span> function creates a new board data structure and returns it. Line 38 creates the outer list and stores a reference to this list in <span class='m'>board</span>. Line 40 creates the inner lists using list replication. (<span class='m'>[' '] * 8</span> evaluates to be the same as <span class='m'>[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']</span> but with less typing.) The <span class='m'>for</span> loop here runs line 40 eight times to create the eight inner lists. The spaces represent a completely empty game board.</p>
<p>What <span class='m'>board</span> ends up being is a list of eight lists, and each of those eight lists themselves has eight strings. The result is sixty four (8 x 8 = 64) strings. Each string is (right now) a single space character.</p>
<h3 id="CheckingifaMoveisValid">Checking if a Move is Valid</h3>
<div class='sourcecode'><ol start='45'>
<li>def isValidMove(board, tile, xstart, ystart):</li>
<li> # Returns False if the player's move on space xstart, ystart is invalid.</li>
<li> # If it is a valid move, returns a list of spaces that would become the player's if they made a move here.</li>
<li> if board[xstart][ystart] != ' ' or not isOnBoard(xstart, ystart):</li>
<li> return False</li>
<li></li>
<li> board[xstart][ystart] = tile # temporarily set the tile on the board.</li>
<li></li>
<li> if tile == 'X':</li>
<li> otherTile = 'O'</li>
<li> else:</li>
<li> otherTile = 'X'</li>
<li></li>
<li> tilesToFlip = []</li>
</ol></div>
<p><span class='m'>isValidMove()</span> is one of the more complicated functions. Given a board data structure, the player's tile, and the XY coordinates for player's move, this function should return <span class='m'>True</span> if the Reversi game rules allow a move to those coordinates and <span class='m'>False</span> if they don't.</p>
<p>The easiest check we can do to disqualify a move is to see if the XY coordinates are on the game board or if the space at XY is not empty. This is what the <span class='m'>if</span> statement on line 48 checks for. <span class='m'>isOnBoard()</span> is a function we will write that makes sure both the X and Y coordinates are between <span class='m'>0</span> and <span class='m'>7</span>. We do this on line 48 and 49.</p>
<p>For the purposes of this function, we will go ahead and copy the XY coordinate pointed to by <span class='m'>xstart</span> and <span class='m'>ystart</span> with the player's tile. We set this place on the board back to a space before we leave this function.</p>
<p>The player's tile (either the human player or the computer player) has been passed to us, but we will need to be able to identify the other player's tile. If the player's tile is <span class='m'>'X'</span> then obviously the other player's tile is <span class='m'>'O'</span>, and vice versa.</p>
<p>Finally, if the given XY coordinate ends up as a valid position, we will return a list of all the opponent's tiles that would be flipped by this move.</p>
<div class='sourcecode'><ol start='59'>
<li> for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]:</li>
</ol></div>
<p>The <span class='m'>for</span> loop iterates through a list of lists which represent directions you can move on the game board. The game board is a Cartesian coordinate system with an X and Y direction. There are eight directions you can move: up, down, left, right, and the four diagonal directions. Each of the eight 2-item lists in the list on line 59 represents one of these directions. We will move around the board in a direction by adding the first value in the two-item list to our X coordinate, and the second value to our Y coordinate.</p>
<p>Because the X coordinates increase as you go to the right, you can "move" to the right by adding <span class='m'>1</span> to the X coordinate. Moving to the left is the opposite: you would subtract <span class='m'>1</span> (or add <span class='m'>-1</span>) from the X coordinate. We can move up, down, left, and right by adding or subtracting to only one coordinate at a time. But to move diagonally, we need to add or subtract to both coordinates. For example, adding <span class='m'>1</span> to the X coordinate to move right and adding <span class='m'>-1</span> to the Y coordinate to move up would result in moving to the up-right diagonal direction.</p>
<h3 id="CheckingEachoftheEightDirections">Checking Each of the Eight Directions</h3>
<p>Here is a diagram to make it easier to remember which two-item list represents which direction:</p>
<p class='centeredImageP'><img src='images/15-7.png' alt='' class='centeredImage' /><br />Figure 15-7: Each two-item list represents one of the eight directions.
</p>
<div class='sourcecode'><ol start='59'>
<li> for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]:</li>
<li> x, y = xstart, ystart</li>
<li> x += xdirection # first step in the direction</li>
<li> y += ydirection # first step in the direction</li>
</ol></div>
<p>Line 60 sets an <span class='m'>x</span> and <span class='m'>y</span> variable to be the same value as <span class='m'>xstart</span> and <span class='m'>ystart</span>, respectively. We will change <span class='m'>x</span> and <span class='m'>y</span> to "move" in the direction that <span class='m'>xdirection</span> and <span class='m'>ydirection</span> dictate. <span class='m'>xstart</span> and <span class='m'>ystart</span> will stay the same so we can remember which space we originally intended to check. (Remember, we need to set this place back to a space character, so we shouldn't overwrite the values in them.)</p>
<p>We make the first step in the direction as the first part of our algorithm.</p>
<div class='sourcecode'><ol start='63'>
<li> if isOnBoard(x, y) and board[x][y] == otherTile:</li>
<li> # There is a piece belonging to the other player next to our piece.</li>
<li> x += xdirection</li>
<li> y += ydirection</li>
<li> if not isOnBoard(x, y):</li>
<li> continue</li>
</ol></div>
<p>Remember, in order for this to be a valid move, the first step in this direction must be 1) on the board and 2) must be occupied by the other player's tile. Otherwise there is no chance to flip over any of the opponent's tiles. In that case, the <span class='m'>if</span> statement on line 63 is not <span class='m'>True</span> and execution goes back to the <span class='m'>for</span> statement for the next direction.</p>
<p>But if the first space does have the other player's tile, then we should keep proceeding in that direction until we reach on of our own tiles. If we move off of the board, then we should continue back to the <span class='m'>for</span> statement to try the next direction.</p>
<div class='sourcecode'><ol start='69'>
<li> while board[x][y] == otherTile:</li>
<li> x += xdirection</li>
<li> y += ydirection</li>
<li> if not isOnBoard(x, y): # break out of while loop, then continue in for loop</li>
<li> break</li>
<li> if not isOnBoard(x, y):</li>
<li> continue</li>
</ol></div>
<p>The <span class='m'>while</span> loop on line 69 ensures that <span class='m'>x</span> and <span class='m'>y</span> keep going in the current direction as long as we keep seeing a trail of the other player's tiles. If <span class='m'>x</span> and <span class='m'>y</span> move off of the board, we break out of the <span class='m'>for</span> loop and the flow of execution moves to line 74. What we really want to do is break out of the <span class='m'>while</span> loop but continue in the <span class='m'>for</span> loop. But if we put a <span class='m'>continue</span> statement on line 73, that would only continue to the <span class='m'>while</span> loop on line 69.</p>
<p>Instead, we recheck <span class='m'>not isOnBoard(x, y)</span> on line 74 and then continue from there, which goes to the next direction in the <span class='m'>for</span> statement on line 59. It is important to know that <span class='m'>break</span> and <span class='m'>continue</span> will only break or continue in the loop they are called from, and not an outer loop that contain the loop they are called from.</p>
<h3 id="FindingOutifTherearePiecestoFlipOver">Finding Out if There are Pieces to Flip Over</h3>
<div class='sourcecode'><ol start='76'>
<li> if board[x][y] == tile:</li>
<li> # There are pieces to flip over. Go in the reverse direction until we reach the original space, noting all the tiles along the way.</li>
<li> while True:</li>
<li> x -= xdirection</li>
<li> y -= ydirection</li>
<li> if x == xstart and y == ystart:</li>
<li> break</li>
<li> tilesToFlip.append([x, y])</li>
</ol></div>
<p>If the <span class='m'>while</span> loop on line 69 stopped looping because the condition was <span class='m'>False</span>, then we have found a space on the board that holds our own tile or a blank space. Line 76 checks if this space on the board holds one of our tiles. If it does, then we have found a valid move. We will then start a new <span class='m'>while</span> loop, this time subtracting <span class='m'>x</span> and <span class='m'>y</span> to move in the opposite direction we were originally going. We note each space between our tiles on the board by appending the space to the <span class='m'>tilesToFlip</span> list.</p>
<p>We break out of the <span class='m'>while</span> loop once <span class='m'>x</span> and <span class='m'>y</span> have returned to the original position (which was still stored in <span class='m'>xstart</span> and <span class='m'>ystart</span>).</p>
<div class='sourcecode'><ol start='85'>
<li> board[xstart][ystart] = ' ' # restore the empty space</li>
<li> if len(tilesToFlip) == 0: # If no tiles were flipped, this is not a valid move.</li>
<li> return False</li>
<li> return tilesToFlip</li>
</ol></div>
<p>We perform this check in all eight directions, and afterwards the <span class='m'>tilesToFlip</span> list will contain the XY coordinates all of our opponent's tiles that would be flipped if the player moved on <span class='m'>xstart</span>, <span class='m'>ystart</span>. Remember, the <span class='m'>isValidMove()</span> function is only checking to see if the original move was valid, it does not actually change the data structure of the game board.</p>
<p>If none of the eight directions ended up flipping at least one of the opponent's tiles, then <span class='m'>tilesToFlip</span> would be an empty list and this move would not be valid. In that case, <span class='m'>isValidMove()</span> should return <span class='m'>False</span>. Otherwise, we should return <span class='m'>tilesToFlip</span>.</p>
<h3 id="CheckingforValidCoordinates">Checking for Valid Coordinates</h3>
<div class='sourcecode'><ol start='91'>
<li>def isOnBoard(x, y):</li>
<li> # Returns True if the coordinates are located on the board.</li>
<li> return x >= 0 and x <= 7 and y >= 0 and y <=7</li>
</ol></div>
<p><span class='m'>isOnBoard()</span> is a function called from <span class='m'>isValidMove()</span>, and is just shorthand for the rather complicated Boolean expression that returns <span class='m'>True</span> if both <span class='m'>x</span> and <span class='m'>y</span> are in between <span class='m'>0</span> and <span class='m'>7</span>. This function lets us make sure that the coordinates are actually on the game board.</p>
<h3 id="GettingaListwithAllValidMoves">Getting a List with All Valid Moves</h3>
<div class='sourcecode'><ol start='96'>
<li>def getBoardWithValidMoves(board, tile):</li>
<li> # Returns a new board with . marking the valid moves the given player can make.</li>
<li> dupeBoard = getBoardCopy(board)</li>
<li></li>
<li> for x, y in getValidMoves(dupeBoard, tile):</li>
<li> dupeBoard[x][y] = '.'</li>
<li> return dupeBoard</li>
</ol></div>
<p><span class='m'>getBoardWithValidMoves()</span> is used to return a game board data structure that has <span class='m'>'.'</span> characters for all valid moves on the board. This is used by the hints mode to display to the player a board with all possible moves marked on it.</p>
<p>Notice that this function creates a duplicate game board data structure instead of modifying the one passed to it in the <span class='m'>board</span> parameter. Line 100 calls <span class='m'>getValidMoves()</span>, which returns a list of XY coordinates with all the legal moves the player could make. The board copy is then marked with a period in those spaces. How <span class='m'>getValidMoves()</span> works is described next.</p>
<div class='sourcecode'><ol start='105'>
<li>def getValidMoves(board, tile):</li>
<li> # Returns a list of [x,y] lists of valid moves for the given player on the given board.</li>
<li> validMoves = []</li>
<li></li>
<li> for x in range(8):</li>
<li> for y in range(8):</li>
<li> if isValidMove(board, tile, x, y) != False:</li>
<li> validMoves.append([x, y])</li>
<li> return validMoves</li>
</ol></div>
<p>The <span class='m'>getValidMoves()</span> function returns a list of two-item lists that hold the XY coordinates for all valid moves for tile's player, given a particular game board data structure in <span class='m'>board</span>.</p>
<p>This function uses two loops to check every single XY coordinate (all sixty four of them) by calling <span class='m'>isValidMove()</span> on that space and checking if it returns <span class='m'>False</span> or a list of possible moves (in which case it is a valid move). Each valid XY coordinate is appended to the list, <span class='m'>validMoves</span>.</p>
<h2 id="TheboolFunction">The <span class='m'>bool()</span> Function</h2>
<p>Remember how you could use the <span class='m'>int()</span> and <span class='m'>str()</span> functions to get the integer and string value of other data types? For example, <span class='m'>str(42)</span> would return the string <span class='m'>'42'</span>, and <span class='m'>int('100')</span> would return the integer <span class='m'>100</span>.</p>
<p>There is a similar function for the Boolean data type, <span class='m'>bool()</span>. Most other data types have one value that is considered the <span class='m'>False</span> value for that data type, and every other value is consider <span class='m'>True</span>. The integer <span class='m'>0</span>, the floating point number <span class='m'>0.0</span>, the empty string, the empty list, and the empty dictionary are all considered to be <span class='m'>False</span> when used as the condition for an <span class='m'>if</span> or loop statement. All other values are <span class='m'>True</span>. Try entering the following into the interactive shell:</p>
<div class='sourceblurb'>
>>> bool(0)<br />
False<br />
>>> bool(0.0)<br />
False<br />
>>> bool('')<br />
False<br />
>>> bool([])<br />
False<br />
>>> bool({})<br />
False<br />
>>> bool(1)<br />
True<br />
>>> bool('Hello')<br />
True<br />
>>> bool([1, 2, 3, 4, 5])<br />
True<br />
>>> bool({'spam':'cheese', 'fizz':'buzz'})<br />
True<br />
>>><br />
</div>
<p>Whenever you have a condition, imagine that the entire condition is placed inside a call to <span class='m'>bool()</span> as the parameter. Conditions are automatically interpreted as Boolean values. This is similar to how <span class='m'>print()</span> can be passed non-string values and will automatically interpret them as strings when they print.</p>
<p>This is why the condition on line 111 works correctly. The call to the <span class='m'>isValidMove()</span> function either returns the Boolean value <span class='m'>False</span> or a non-empty list. If you imagine that the entire condition is placed inside a call to <span class='m'>bool()</span>, then the condition <span class='m'>False</span> becomes <span class='m'>bool(False)</span> (which, of course, evalutes to <span class='m'>False</span>). And a condition of a non-empty list placed as the parameter to <span class='m'>bool()</span> will return <span class='m'>True</span>. This is why the return value of <span class='m'>isValidMove()</span> can be used as a condition.</p>
<h3 id="GettingtheScoreoftheGameBoard">Getting the Score of the Game Board</h3>
<div class='sourcecode'><ol start='116'>
<li>def getScoreOfBoard(board):</li>
<li> # Determine the score by counting the tiles. Returns a dictionary with keys 'X' and 'O'.</li>
<li> xscore = 0</li>
<li> oscore = 0</li>
<li> for x in range(8):</li>
<li> for y in range(8):</li>
<li> if board[x][y] == 'X':</li>
<li> xscore += 1</li>
<li> if board[x][y] == 'O':</li>
<li> oscore += 1</li>
<li> return {'X':xscore, 'O':oscore}</li>
</ol></div>
<p>The <span class='m'>getScoreOfBoard()</span> function uses nested <span class='m'>for</span> loops to check all 64 spaces on the board (8 rows times 8 columns per row is 64 spaces) and see which tile (if any) is on them. For each <span class='m'>'X'</span> tile, the code increments <span class='m'>xscore</span>. For each <span class='m'>'O'</span> tile, the code increments <span class='m'>oscore</span>.</p>
<p>Notice that this function does not return a two-item list of the scores. A two-item list might be a bit confusing, because you may forget which item is for X and which item is for O. Instead the function returns a dictionary with keys <span class='m'>'X'</span> and <span class='m'>'O'</span> whose values are the scores.</p>
<h3 id="GettingthePlayersTileChoice">Getting the Player's Tile Choice</h3>
<div class='sourcecode'><ol start='129'>
<li>def enterPlayerTile():</li>
<li> # Let's the player type which tile they want to be.</li>
<li> # Returns a list with the player's tile as the first item, and the computer's tile as the second.</li>
<li> tile = ''</li>
<li> while not (tile == 'X' or tile == 'O'):</li>
<li> print('Do you want to be X or O?')</li>
<li> tile = input().upper()</li>
</ol></div>
<p>This function asks the player which tile they want to be, either <span class='m'>'X'</span> or <span class='m'>'O'</span>. The <span class='m'>for</span> loop will keep looping until the player types in <span class='m'>'X'</span> or <span class='m'>'O'</span>.</p>
<div class='sourcecode'><ol start='137'>
<li> # the first element in the tuple is the player's tile, the second is the computer's tile.</li>
<li> if tile == 'X':</li>
<li> return ['X', 'O']</li>
<li> else:</li>
<li> return ['O', 'X']</li>
</ol></div>
<p>The <span class='m'>enterPlayerTile()</span> function then returns a two-item list, where the player's tile choice is the first item and the computer's tile is the second. We use a list here instead of a dictionary so that the assignment statement calling this function can use the multiple assignment trick. (See line 252.)</p>
<h3 id="DeterminingWhoGoesFirst">Determining Who Goes First</h3>
<div class='sourcecode'><ol start='144'>
<li>def whoGoesFirst():</li>
<li> # Randomly choose the player who goes first.</li>
<li> if random.randint(0, 1) == 0:</li>
<li> return 'computer'</li>
<li> else:</li>
<li> return 'player'</li>
</ol></div>
<p>The <span class='m'>whoGoesFirst()</span> function randomly selects who goes first, and returns either the string <span class='m'>'computer'</span> or the string <span class='m'>'player'</span>.</p>
<h3 class='pagebreaker' id="AskingthePlayertoPlayAgain">Asking the Player to Play Again</h3>
<div class='sourcecode'><ol start='152'>
<li>def playAgain():</li>
<li> # This function returns True if the player wants to play again, otherwise it returns False.</li>
<li> print('Do you want to play again? (yes or no)')</li>
<li> return input().lower().startswith('y')</li>
</ol></div>
<p>We have used the <span class='m'>playAgain()</span> in our previous games. If the player types in something that begins with <span class='m'>'y'</span>, then the function returns <span class='m'>True</span>. Otherwise the function returns <span class='m'>False</span>.</p>
<h3 id="PlacingDownaTileontheGameBoard">Placing Down a Tile on the Game Board</h3>
<div class='sourcecode'><ol start='158'>
<li>def makeMove(board, tile, xstart, ystart):</li>
<li> # Place the tile on the board at xstart, ystart, and flip any of the opponent's pieces.</li>
<li> # Returns False if this is an invalid move, True if it is valid.</li>
<li> tilesToFlip = isValidMove(board, tile, xstart, ystart)</li>
</ol></div>
<p><span class='m'>makeMove()</span> is the function we call when we want to place a tile on the board and flip the other tiles according to the rules of Reversi. This function modifies the <span class='m'>board</span> data structure that is passed as a parameter directly. Changes made to the <span class='m'>board</span> variable (because it is a list) will be made to the global scope as well. Most of the work is done by <span class='m'>isValidMove()</span>, which returns a list of XY coordinates (in a two-item list) of tiles that need to be flipped. (Remember, if the the <span class='m'>xstart</span> and <span class='m'>ystart</span> arguments point to an invalid move, then <span class='m'>isValidMove()</span> will return the Boolean value <span class='m'>False</span>.)</p>
<div class='sourcecode'><ol start='163'>
<li> if tilesToFlip == False:</li>
<li> return False</li>
<li></li>
<li> board[xstart][ystart] = tile</li>
<li> for x, y in tilesToFlip:</li>
<li> board[x][y] = tile</li>
<li> return True</li>
</ol></div>
<p>On lines 163 and 164, if the return value of <span class='m'>isValidMove()</span> was <span class='m'>False</span>, then <span class='m'>makeMove()</span> will also return <span class='m'>False</span>.</p>
<p>Otherwise, <span class='m'>isValidMove()</span> would have returned a list of spaces on the board to put down our tiles (the <span class='m'>'X'</span> or <span class='m'>'O'</span> string in tile). Line 166 sets the space that the player has moved on, and the <span class='m'>for</span> loop after that sets all the tiles that are in <span class='m'>tilesToFlip</span>.</p>
<h3 id="CopyingtheBoardDataStructure">Copying the Board Data Structure</h3>
<div class='sourcecode'><ol start='172'>
<li>def getBoardCopy(board):</li>
<li> # Make a duplicate of the board list and return the duplicate.</li>
<li> dupeBoard = getNewBoard()</li>
<li></li>
<li> for x in range(8):</li>
<li> for y in range(8):</li>
<li> dupeBoard[x][y] = board[x][y]</li>
<li></li>
<li> return dupeBoard</li>
</ol></div>
<p><span class='m'>getBoardCopy()</span> is different from <span class='m'>getNewBoard()</span>. <span class='m'>getNewBoad()</span> will create a new game board data structure which has only empty spaces and the four starting tiles. <span class='m'>getBoardCopy()</span> will create a new game board data structure, but then copy all of the pieces in the <span class='m'>board</span> parameter. This function is used by our AI to have a game board that it can change around so that it doesn't have to change the real game board. This is like how you may imagine making moves on a copy of the board in your mind, but not actually put pieces down on the real board.</p>
<p>A call to <span class='m'>getNewBoard()</span> handles getting a fresh game board data structure. Then the two <span class='m'>for</span> loops copy each of the 64 tiles from <span class='m'>board</span> to our duplicate board data structure named <span class='m'>dupeBoard</span>.</p>