import random
import sys
import math
from typing import List, Optional, Tuple
Board = List[str] # 9 elements: 'X', 'O' or ' '
WIN_COMBINATIONS = [
(0,1,2), (3,4,5), (6,7,8), # rows
(0,3,6), (1,4,7), (2,5,8), # cols
(0,4,8), (2,4,6) # diagonals
]
def new_board() -> Board:
return [' '] * 9
def print_board(board: Board) -> None:
print()
print(f" {board[0]} | {board[1]} | {board[2]} ")
print("---+---+---")
print(f" {board[3]} | {board[4]} | {board[5]} ")
print("---+---+---")
print(f" {board[6]} | {board[7]} | {board[8]} ")
print()
def board_full(board: Board) -> bool:
return all(s != ' ' for s in board)
def check_winner(board: Board) -> Optional[str]:
for a,b,c in WIN_COMBINATIONS:
if board[a] == board[b] == board[c] and board[a] != ' ':
return board[a]
return None
def available_moves(board: Board) -> List[int]:
return [i for i, v in enumerate(board) if v == ' ']
def human_move(board: Board, symbol: str) -> None:
while True:
try:
pos = input(f"Player {symbol}, enter move (1-9): ").strip()
if [Link]() in ('q', 'quit', 'exit'):
print("Quitting game. Goodbye!")
[Link](0)
idx = int(pos) - 1
if idx not in range(9):
print("Invalid position. Choose 1 through 9.")
continue
if board[idx] != ' ':
print("That square is already taken. Choose another.")
continue
board[idx] = symbol
break
except ValueError:
print("Please enter a number 1-9 (or 'q' to quit).")
def random_ai_move(board: Board, symbol: str) -> None:
move = [Link](available_moves(board))
board[move] = symbol
print(f"Computer ({symbol}) plays at {move+1} (random).")
def minimax(board: Board, depth: int, is_maximizing: bool, ai_sym: str, human_sym: str) -> Tuple[int, Optional[int]]:
"""
Return tuple(score, move_index)
Score: +1 if ai wins, -1 if human wins, 0 draw.
Minimax returns best achievable score from this state.
"""
winner = check_winner(board)
if winner == ai_sym:
return (1, None)
elif winner == human_sym:
return (-1, None)
elif board_full(board):
return (0, None)
if is_maximizing:
best_score = -[Link]
best_move = None
for m in available_moves(board):
board[m] = ai_sym
score, _ = minimax(board, depth+1, False, ai_sym, human_sym)
board[m] = ' '
if score > best_score:
best_score = score
best_move = m
# perfect win found
if best_score == 1:
break
return (best_score, best_move)
else:
best_score = [Link]
best_move = None
for m in available_moves(board):
board[m] = human_sym
score, _ = minimax(board, depth+1, True, ai_sym, human_sym)
board[m] = ' '
if score < best_score:
best_score = score
best_move = m
if best_score == -1:
break
return (best_score, best_move)
def perfect_ai_move(board: Board, ai_sym: str, human_sym: str) -> None:
# If center free, it's usually a good move if first; but minimax will handle it.
_, move = minimax(board, 0, True, ai_sym, human_sym)
if move is None:
move = [Link](available_moves(board))
board[move] = ai_sym
print(f"Computer ({ai_sym}) plays at {move+1} (minimax).")
def select_mode() -> Tuple[str, Optional[str]]:
print("Select mode:")
print("1) Two players (local)")
print("2) Play vs Computer (Easy - random)")
print("3) Play vs Computer (Hard - unbeatable)")
while True:
choice = input("Enter 1, 2 or 3: ").strip()
if choice in ('1','2','3'):
return choice, None
print("Please enter 1, 2 or 3.")
def choose_symbols() -> Tuple[str, str]:
while True:
first = input("Who goes first? Enter 'X' or 'O' (X goes first): ").strip().upper()
if first in ('X','O'):
human_first = first
break
print("Enter X or O.")
if human_first == 'X':
return ('X','O')
else:
return ('O','X')
def play_game():
print("Welcome to Tic-Tac-Toe!")
mode, _ = select_mode()
board = new_board()
if mode == '1':
p1, p2 = 'X', 'O'
current = 'X'
while True:
print_board(board)
if current == 'X':
human_move(board, 'X')
else:
human_move(board, 'O')
winner = check_winner(board)
if winner or board_full(board):
print_board(board)
if winner:
print(f"Player {winner} wins! 🎉")
else:
print("It's a draw.")
break
current = 'O' if current == 'X' else 'X'
else:
# vs computer
# Let human choose symbol and who goes first
print("Choose your symbol and who goes first.")
while True:
human_sym = input("Pick your symbol (X or O): ").strip().upper()
if human_sym in ('X','O'):
break
print("Enter X or O.")
ai_sym = 'O' if human_sym == 'X' else 'X'
while True:
who = input("Who goes first? (me / computer): ").strip().lower()
if who in ('me','computer','c','i','human','h'):
human_turn_first = [Link]('m') or [Link]('h') or [Link]('i')
break
print("Enter 'me' or 'computer'.")
hard = (mode == '3')
current_is_human = human_turn_first
while True:
print_board(board)
if current_is_human:
human_move(board, human_sym)
else:
if hard:
perfect_ai_move(board, ai_sym, human_sym)
else:
random_ai_move(board, ai_sym)
winner = check_winner(board)
if winner or board_full(board):
print_board(board)
if winner:
if winner == human_sym:
print("You win! Congratulations 🎉")
else:
print("Computer wins. Better luck next time.")
else:
print("It's a draw.")
break
current_is_human = not current_is_human
def main():
while True:
play_game()
again = input("Play again? (y/n): ").strip().lower()
if again not in ('y','yes'):
print("Thanks for playing! Goodbye.")
break
if _name_ == "_main_":
main()