Upload app.py
Browse files
app.py
ADDED
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import math
|
3 |
+
import random
|
4 |
+
|
5 |
+
class TicTacToe:
|
6 |
+
def __init__(self):
|
7 |
+
"""Initialize the game board"""
|
8 |
+
self.board = [[' ' for _ in range(3)] for _ in range(3)]
|
9 |
+
self.current_winner = None
|
10 |
+
|
11 |
+
def make_move(self, row, col, player):
|
12 |
+
"""Make a move on the board"""
|
13 |
+
if self.board[row][col] == ' ':
|
14 |
+
self.board[row][col] = player
|
15 |
+
return True
|
16 |
+
return False
|
17 |
+
|
18 |
+
def check_winner(self):
|
19 |
+
"""Check for a winner"""
|
20 |
+
# Check rows
|
21 |
+
for row in self.board:
|
22 |
+
if row[0] == row[1] == row[2] != ' ':
|
23 |
+
return row[0]
|
24 |
+
|
25 |
+
# Check columns
|
26 |
+
for col in range(3):
|
27 |
+
if self.board[0][col] == self.board[1][col] == self.board[2][col] != ' ':
|
28 |
+
return self.board[0][col]
|
29 |
+
|
30 |
+
# Check diagonals
|
31 |
+
if self.board[0][0] == self.board[1][1] == self.board[2][2] != ' ':
|
32 |
+
return self.board[0][0]
|
33 |
+
if self.board[0][2] == self.board[1][1] == self.board[2][0] != ' ':
|
34 |
+
return self.board[0][2]
|
35 |
+
|
36 |
+
return None
|
37 |
+
|
38 |
+
def is_board_full(self):
|
39 |
+
"""Check if the board is full"""
|
40 |
+
return all(cell != ' ' for row in self.board for cell in row)
|
41 |
+
|
42 |
+
def minimax(self, depth, is_maximizing, alpha, beta):
|
43 |
+
"""Minimax algorithm with Alpha-Beta pruning"""
|
44 |
+
winner = self.check_winner()
|
45 |
+
|
46 |
+
# Terminal states
|
47 |
+
if winner == 'X':
|
48 |
+
return 1
|
49 |
+
elif winner == 'O':
|
50 |
+
return -1
|
51 |
+
elif self.is_board_full():
|
52 |
+
return 0
|
53 |
+
|
54 |
+
if is_maximizing:
|
55 |
+
max_eval = -math.inf
|
56 |
+
for i in range(3):
|
57 |
+
for j in range(3):
|
58 |
+
if self.board[i][j] == ' ':
|
59 |
+
self.board[i][j] = 'X'
|
60 |
+
eval = self.minimax(depth + 1, False, alpha, beta)
|
61 |
+
self.board[i][j] = ' '
|
62 |
+
max_eval = max(max_eval, eval)
|
63 |
+
alpha = max(alpha, eval)
|
64 |
+
if beta <= alpha:
|
65 |
+
break
|
66 |
+
return max_eval
|
67 |
+
else:
|
68 |
+
min_eval = math.inf
|
69 |
+
for i in range(3):
|
70 |
+
for j in range(3):
|
71 |
+
if self.board[i][j] == ' ':
|
72 |
+
self.board[i][j] = 'O'
|
73 |
+
eval = self.minimax(depth + 1, True, alpha, beta)
|
74 |
+
self.board[i][j] = ' '
|
75 |
+
min_eval = min(min_eval, eval)
|
76 |
+
beta = min(beta, eval)
|
77 |
+
if beta <= alpha:
|
78 |
+
break
|
79 |
+
return min_eval
|
80 |
+
|
81 |
+
def find_best_move(self):
|
82 |
+
"""Find the best move for the AI"""
|
83 |
+
best_val = -math.inf
|
84 |
+
best_move = None
|
85 |
+
|
86 |
+
for i in range(3):
|
87 |
+
for j in range(3):
|
88 |
+
if self.board[i][j] == ' ':
|
89 |
+
self.board[i][j] = 'X'
|
90 |
+
move_val = self.minimax(0, False, -math.inf, math.inf)
|
91 |
+
self.board[i][j] = ' '
|
92 |
+
|
93 |
+
if move_val > best_val:
|
94 |
+
best_move = (i, j)
|
95 |
+
best_val = move_val
|
96 |
+
|
97 |
+
return best_move
|
98 |
+
|
99 |
+
def main():
|
100 |
+
# Set page configuration
|
101 |
+
st.set_page_config(
|
102 |
+
page_title="Tic-Tac-Toe AI",
|
103 |
+
page_icon=":game_die:",
|
104 |
+
layout="centered"
|
105 |
+
)
|
106 |
+
|
107 |
+
# Custom CSS for styling
|
108 |
+
st.markdown("""
|
109 |
+
<style>
|
110 |
+
.stButton>button {
|
111 |
+
width: 100px;
|
112 |
+
height: 100px;
|
113 |
+
font-size: 48px;
|
114 |
+
margin: 5px;
|
115 |
+
}
|
116 |
+
.title {
|
117 |
+
text-align: center;
|
118 |
+
color: #4a4a4a;
|
119 |
+
}
|
120 |
+
.subtitle {
|
121 |
+
text-align: center;
|
122 |
+
color: #6a6a6a;
|
123 |
+
}
|
124 |
+
</style>
|
125 |
+
""", unsafe_allow_html=True)
|
126 |
+
|
127 |
+
# Title and introduction
|
128 |
+
st.markdown("<h1 class='title'>π² Tic-Tac-Toe AI π€</h1>", unsafe_allow_html=True)
|
129 |
+
st.markdown("<h3 class='subtitle'>Can you beat the AI?</h3>", unsafe_allow_html=True)
|
130 |
+
|
131 |
+
# Initialize game state
|
132 |
+
if 'game' not in st.session_state:
|
133 |
+
st.session_state.game = TicTacToe()
|
134 |
+
st.session_state.game_over = False
|
135 |
+
st.session_state.winner = None
|
136 |
+
|
137 |
+
# Function to handle button clicks
|
138 |
+
def button_click(row, col):
|
139 |
+
# Check if game is not over and the cell is empty
|
140 |
+
if not st.session_state.game_over and st.session_state.game.board[row][col] == ' ':
|
141 |
+
# Player's move
|
142 |
+
st.session_state.game.make_move(row, col, 'O')
|
143 |
+
|
144 |
+
# Check for player win
|
145 |
+
winner = st.session_state.game.check_winner()
|
146 |
+
if winner:
|
147 |
+
st.session_state.game_over = True
|
148 |
+
st.session_state.winner = winner
|
149 |
+
return
|
150 |
+
|
151 |
+
# Check for draw
|
152 |
+
if st.session_state.game.is_board_full():
|
153 |
+
st.session_state.game_over = True
|
154 |
+
return
|
155 |
+
|
156 |
+
# AI's move
|
157 |
+
ai_move = st.session_state.game.find_best_move()
|
158 |
+
if ai_move:
|
159 |
+
st.session_state.game.make_move(ai_move[0], ai_move[1], 'X')
|
160 |
+
|
161 |
+
# Check for AI win
|
162 |
+
winner = st.session_state.game.check_winner()
|
163 |
+
if winner:
|
164 |
+
st.session_state.game_over = True
|
165 |
+
st.session_state.winner = winner
|
166 |
+
|
167 |
+
# Render the game board
|
168 |
+
game_board = st.columns(3)
|
169 |
+
for row in range(3):
|
170 |
+
with game_board[row % 3]:
|
171 |
+
for col in range(3):
|
172 |
+
# Create a button for each cell
|
173 |
+
button_label = st.session_state.game.board[row][col]
|
174 |
+
button_key = f"button_{row}_{col}"
|
175 |
+
|
176 |
+
# Style buttons based on their content
|
177 |
+
if button_label == 'O':
|
178 |
+
button_style = "background-color: #FF6B6B; color: white;"
|
179 |
+
elif button_label == 'X':
|
180 |
+
button_style = "background-color: #4ECDC4; color: white;"
|
181 |
+
else:
|
182 |
+
button_style = ""
|
183 |
+
|
184 |
+
# Create the button
|
185 |
+
if st.button(button_label, key=button_key,
|
186 |
+
on_click=button_click,
|
187 |
+
args=(row, col),
|
188 |
+
disabled=st.session_state.game_over or button_label != ' ',
|
189 |
+
help="Click to make your move"):
|
190 |
+
pass
|
191 |
+
|
192 |
+
# Display game result
|
193 |
+
if st.session_state.game_over:
|
194 |
+
if st.session_state.winner == 'O':
|
195 |
+
st.success("π Congratulations! You won!")
|
196 |
+
elif st.session_state.winner == 'X':
|
197 |
+
st.error("π€ AI wins! Better luck next time.")
|
198 |
+
else:
|
199 |
+
st.warning("π€ It's a draw!")
|
200 |
+
|
201 |
+
# Restart game button
|
202 |
+
if st.button("New Game", help="Start a new game"):
|
203 |
+
st.session_state.game = TicTacToe()
|
204 |
+
st.session_state.game_over = False
|
205 |
+
st.session_state.winner = None
|
206 |
+
st.rerun()
|
207 |
+
|
208 |
+
if __name__ == "__main__":
|
209 |
+
main()
|