|
import streamlit as st
|
|
import math
|
|
import random
|
|
|
|
class TicTacToe:
|
|
def __init__(self):
|
|
"""Initialize the game board"""
|
|
self.board = [[' ' for _ in range(3)] for _ in range(3)]
|
|
self.current_winner = None
|
|
|
|
def make_move(self, row, col, player):
|
|
"""Make a move on the board"""
|
|
if self.board[row][col] == ' ':
|
|
self.board[row][col] = player
|
|
return True
|
|
return False
|
|
|
|
def check_winner(self):
|
|
"""Check for a winner"""
|
|
|
|
for row in self.board:
|
|
if row[0] == row[1] == row[2] != ' ':
|
|
return row[0]
|
|
|
|
|
|
for col in range(3):
|
|
if self.board[0][col] == self.board[1][col] == self.board[2][col] != ' ':
|
|
return self.board[0][col]
|
|
|
|
|
|
if self.board[0][0] == self.board[1][1] == self.board[2][2] != ' ':
|
|
return self.board[0][0]
|
|
if self.board[0][2] == self.board[1][1] == self.board[2][0] != ' ':
|
|
return self.board[0][2]
|
|
|
|
return None
|
|
|
|
def is_board_full(self):
|
|
"""Check if the board is full"""
|
|
return all(cell != ' ' for row in self.board for cell in row)
|
|
|
|
def minimax(self, depth, is_maximizing, alpha, beta):
|
|
"""Minimax algorithm with Alpha-Beta pruning"""
|
|
winner = self.check_winner()
|
|
|
|
|
|
if winner == 'X':
|
|
return 1
|
|
elif winner == 'O':
|
|
return -1
|
|
elif self.is_board_full():
|
|
return 0
|
|
|
|
if is_maximizing:
|
|
max_eval = -math.inf
|
|
for i in range(3):
|
|
for j in range(3):
|
|
if self.board[i][j] == ' ':
|
|
self.board[i][j] = 'X'
|
|
eval = self.minimax(depth + 1, False, alpha, beta)
|
|
self.board[i][j] = ' '
|
|
max_eval = max(max_eval, eval)
|
|
alpha = max(alpha, eval)
|
|
if beta <= alpha:
|
|
break
|
|
return max_eval
|
|
else:
|
|
min_eval = math.inf
|
|
for i in range(3):
|
|
for j in range(3):
|
|
if self.board[i][j] == ' ':
|
|
self.board[i][j] = 'O'
|
|
eval = self.minimax(depth + 1, True, alpha, beta)
|
|
self.board[i][j] = ' '
|
|
min_eval = min(min_eval, eval)
|
|
beta = min(beta, eval)
|
|
if beta <= alpha:
|
|
break
|
|
return min_eval
|
|
|
|
def find_best_move(self):
|
|
"""Find the best move for the AI"""
|
|
best_val = -math.inf
|
|
best_move = None
|
|
|
|
for i in range(3):
|
|
for j in range(3):
|
|
if self.board[i][j] == ' ':
|
|
self.board[i][j] = 'X'
|
|
move_val = self.minimax(0, False, -math.inf, math.inf)
|
|
self.board[i][j] = ' '
|
|
|
|
if move_val > best_val:
|
|
best_move = (i, j)
|
|
best_val = move_val
|
|
|
|
return best_move
|
|
|
|
def main():
|
|
|
|
st.set_page_config(
|
|
page_title="Tic-Tac-Toe AI",
|
|
page_icon=":game_die:",
|
|
layout="centered"
|
|
)
|
|
|
|
|
|
st.markdown("""
|
|
<style>
|
|
.stButton>button {
|
|
width: 100px;
|
|
height: 100px;
|
|
font-size: 48px;
|
|
margin: 5px;
|
|
}
|
|
.title {
|
|
text-align: center;
|
|
color: #4a4a4a;
|
|
}
|
|
.subtitle {
|
|
text-align: center;
|
|
color: #6a6a6a;
|
|
}
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
st.markdown("<h1 class='title'>π² Tic-Tac-Toe AI π€</h1>", unsafe_allow_html=True)
|
|
st.markdown("<h3 class='subtitle'>Can you beat the AI?</h3>", unsafe_allow_html=True)
|
|
|
|
|
|
if 'game' not in st.session_state:
|
|
st.session_state.game = TicTacToe()
|
|
st.session_state.game_over = False
|
|
st.session_state.winner = None
|
|
|
|
|
|
def button_click(row, col):
|
|
|
|
if not st.session_state.game_over and st.session_state.game.board[row][col] == ' ':
|
|
|
|
st.session_state.game.make_move(row, col, 'O')
|
|
|
|
|
|
winner = st.session_state.game.check_winner()
|
|
if winner:
|
|
st.session_state.game_over = True
|
|
st.session_state.winner = winner
|
|
return
|
|
|
|
|
|
if st.session_state.game.is_board_full():
|
|
st.session_state.game_over = True
|
|
return
|
|
|
|
|
|
ai_move = st.session_state.game.find_best_move()
|
|
if ai_move:
|
|
st.session_state.game.make_move(ai_move[0], ai_move[1], 'X')
|
|
|
|
|
|
winner = st.session_state.game.check_winner()
|
|
if winner:
|
|
st.session_state.game_over = True
|
|
st.session_state.winner = winner
|
|
|
|
|
|
game_board = st.columns(3)
|
|
for row in range(3):
|
|
with game_board[row % 3]:
|
|
for col in range(3):
|
|
|
|
button_label = st.session_state.game.board[row][col]
|
|
button_key = f"button_{row}_{col}"
|
|
|
|
|
|
if button_label == 'O':
|
|
button_style = "background-color: #FF6B6B; color: white;"
|
|
elif button_label == 'X':
|
|
button_style = "background-color: #4ECDC4; color: white;"
|
|
else:
|
|
button_style = ""
|
|
|
|
|
|
if st.button(button_label, key=button_key,
|
|
on_click=button_click,
|
|
args=(row, col),
|
|
disabled=st.session_state.game_over or button_label != ' ',
|
|
help="Click to make your move"):
|
|
pass
|
|
|
|
|
|
if st.session_state.game_over:
|
|
if st.session_state.winner == 'O':
|
|
st.success("π Congratulations! You won!")
|
|
elif st.session_state.winner == 'X':
|
|
st.error("π€ AI wins! Better luck next time.")
|
|
else:
|
|
st.warning("π€ It's a draw!")
|
|
|
|
|
|
if st.button("New Game", help="Start a new game"):
|
|
st.session_state.game = TicTacToe()
|
|
st.session_state.game_over = False
|
|
st.session_state.winner = None
|
|
st.rerun()
|
|
|
|
if __name__ == "__main__":
|
|
main() |