1+ # tictactoe.py
2+ # From Classic Computer Science Problems in Python Chapter 8
3+ # Copyright 2018 David Kopec
4+ #
5+ # Licensed under the Apache License, Version 2.0 (the "License");
6+ # you may not use this file except in compliance with the License.
7+ # You may obtain a copy of the License at
8+ #
9+ # http://www.apache.org/licenses/LICENSE-2.0
10+ #
11+ # Unless required by applicable law or agreed to in writing, software
12+ # distributed under the License is distributed on an "AS IS" BASIS,
13+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+ # See the License for the specific language governing permissions and
15+ # limitations under the License.
16+ from __future__ import annotations
17+ from typing import List
18+ from dataclasses import dataclass
19+ from enum import Enum
20+ from board import Piece , Board , Move
21+
22+
23+ class TTTPiece (Piece , Enum ):
24+ X = "X"
25+ O = "O"
26+ E = "E" # stand-in for empty
27+
28+ @property
29+ def opposite (self ) -> Piece :
30+ if self == TTTPiece .X :
31+ return TTTPiece .O
32+ elif self == TTTPiece .O :
33+ return TTTPiece .X
34+ else :
35+ return TTTPiece .E
36+
37+
38+ @dataclass (frozen = True )
39+ class TTTBoard (Board ):
40+ position : List [TTTPiece ]
41+ my_turn : TTTPiece
42+
43+ @property
44+ def turn (self ) -> Piece :
45+ return self .my_turn
46+
47+ def move (self , location : Move ) -> Board :
48+ temp_position : List [TTTPiece ] = self .position .copy ()
49+ temp_position [location ] = self .turn
50+ return TTTBoard (temp_position , self .turn .opposite )
51+
52+ @property
53+ def legal_moves (self ) -> List [Move ]:
54+ return [Move (l ) for l in range (len (self .position )) if self .position [l ] == TTTPiece .E ]
55+
56+ @property
57+ def is_win (self ) -> bool :
58+ # three row, three column, and then two diagonal checks
59+ return self .position [0 ] == self .position [1 ] and self .position [0 ] == self .position [2 ] and self .position [0 ] != TTTPiece .E or \
60+ self .position [3 ] == self .position [4 ] and self .position [3 ] == self .position [5 ] and self .position [3 ] != TTTPiece .E or \
61+ self .position [6 ] == self .position [7 ] and self .position [6 ] == self .position [8 ] and self .position [6 ] != TTTPiece .E or \
62+ self .position [0 ] == self .position [3 ] and self .position [0 ] == self .position [6 ] and self .position [0 ] != TTTPiece .E or \
63+ self .position [1 ] == self .position [4 ] and self .position [1 ] == self .position [7 ] and self .position [1 ] != TTTPiece .E or \
64+ self .position [2 ] == self .position [5 ] and self .position [2 ] == self .position [8 ] and self .position [2 ] != TTTPiece .E or \
65+ self .position [0 ] == self .position [4 ] and self .position [0 ] == self .position [8 ] and self .position [0 ] != TTTPiece .E or \
66+ self .position [2 ] == self .position [4 ] and self .position [2 ] == self .position [6 ] and self .position [2 ] != TTTPiece .E
67+
68+ def evaluate (self , player : Piece ) -> float :
69+ if self .is_win and self .turn == player :
70+ return - 1
71+ elif self .is_win and self .turn != player :
72+ return 1
73+ else :
74+ return 0
75+
76+
77+ if __name__ == "__main__" :
78+ from minimax import find_best_move
79+ # win in 1 move
80+ to_win_easy_position : List [TTTPiece ] = [TTTPiece .X , TTTPiece .O , TTTPiece .X ,
81+ TTTPiece .X , TTTPiece .E , TTTPiece .O ,
82+ TTTPiece .E , TTTPiece .E , TTTPiece .O ]
83+ test_board1 : TTTBoard = TTTBoard (to_win_easy_position , TTTPiece .X )
84+ answer1 : Move = find_best_move (test_board1 )
85+ print (answer1 )
86+
87+ # must block O's win
88+ to_block_position : List [TTTPiece ] = [TTTPiece .X , TTTPiece .E , TTTPiece .E ,
89+ TTTPiece .E , TTTPiece .E , TTTPiece .O ,
90+ TTTPiece .E , TTTPiece .X , TTTPiece .O ]
91+ test_board2 : TTTBoard = TTTBoard (to_block_position , TTTPiece .X )
92+ answer2 : Move = find_best_move (test_board2 )
93+ print (answer2 )
94+
95+ # find the best move to win 2 moves
96+ to_win_hard_position : List [TTTPiece ] = [TTTPiece .X , TTTPiece .E , TTTPiece .E ,
97+ TTTPiece .E , TTTPiece .E , TTTPiece .O ,
98+ TTTPiece .O , TTTPiece .X , TTTPiece .E ]
99+ test_board3 : TTTBoard = TTTBoard (to_win_hard_position , TTTPiece .X )
100+ answer3 : Move = find_best_move (test_board3 )
101+ print (answer3 )
0 commit comments