Spaces:
Sleeping
Sleeping
| import os | |
| import json | |
| import uuid | |
| import time | |
| from flask import Flask, request, jsonify, render_template, send_from_directory | |
| from flask_cors import CORS | |
| import logging | |
| from mistralai import Mistral | |
| # Flask app setup | |
| app = Flask(__name__) | |
| CORS(app) | |
| # Logging setup | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| # Initialize Mistral client | |
| MISTRAL_API_KEY = os.getenv('MISTRAL_API_KEY') | |
| if not MISTRAL_API_KEY: | |
| logger.error("MISTRAL_API_KEY not configured") | |
| exit(1) | |
| client = Mistral(api_key=MISTRAL_API_KEY) | |
| class Room: | |
| def __init__(self, room_id=None): | |
| self.id = room_id or str(uuid.uuid4())[:8] | |
| self.board = [''] * 9 # 9 positions (0-8) | |
| self.current_player = 'X' # X = human, O = AI | |
| self.game_status = 'active' # 'active', 'won', 'draw' | |
| self.winner = None | |
| self.chat_history = [] | |
| self.created = time.time() | |
| self.last_activity = time.time() | |
| self.moves_count = 0 | |
| # Add welcome message | |
| self.chat_history.append({ | |
| 'sender': 'ai', | |
| 'message': "Hey there! Ready for a game of Tic-Tac-Toe? I'm pretty good at this... 馃槒 You're X, I'm O. Good luck!", | |
| 'timestamp': time.time() | |
| }) | |
| def make_move(self, position, player): | |
| """Make a move on the board""" | |
| if self.game_status != 'active' or self.board[position] != '': | |
| return False | |
| self.board[position] = player | |
| self.moves_count += 1 | |
| self.last_activity = time.time() | |
| # Check for winner | |
| if self.check_winner(): | |
| self.game_status = 'won' | |
| self.winner = player | |
| elif self.moves_count == 9: | |
| self.game_status = 'draw' | |
| else: | |
| self.current_player = 'O' if player == 'X' else 'X' | |
| return True | |
| def check_winner(self): | |
| """Check if there's a winner""" | |
| win_patterns = [ | |
| [0, 1, 2], [3, 4, 5], [6, 7, 8], # rows | |
| [0, 3, 6], [1, 4, 7], [2, 5, 8], # columns | |
| [0, 4, 8], [2, 4, 6] # diagonals | |
| ] | |
| for pattern in win_patterns: | |
| a, b, c = pattern | |
| if self.board[a] and self.board[a] == self.board[b] == self.board[c]: | |
| return True | |
| return False | |
| def add_chat_message(self, message, sender): | |
| """Add a chat message to history""" | |
| self.chat_history.append({ | |
| 'sender': sender, | |
| 'message': message, | |
| 'timestamp': time.time() | |
| }) | |
| self.last_activity = time.time() | |
| def to_markdown(self): | |
| """Convert game state to markdown for display""" | |
| markdown = f"# Game Room: {self.id}\n" | |
| markdown += f"## Status: " | |
| if self.game_status == 'won': | |
| winner_name = "You" if self.winner == 'X' else "Mistral AI" | |
| markdown += f"Game Over - {winner_name} wins! 馃帀\n" | |
| elif self.game_status == 'draw': | |
| markdown += "Game Over - It's a draw! 馃\n" | |
| else: | |
| turn_name = "Your turn" if self.current_player == 'X' else "Mistral's turn" | |
| markdown += f"{turn_name} ({self.current_player} to play)\n" | |
| markdown += f"Moves: {self.moves_count}/9\n\n" | |
| # Board representation | |
| markdown += "```\n" | |
| for i in range(0, 9, 3): | |
| row = [self.board[i] or '路', self.board[i+1] or '路', self.board[i+2] or '路'] | |
| markdown += f"{row[0]} | {row[1]} | {row[2]}\n" | |
| if i < 6: | |
| markdown += "-----------\n" | |
| markdown += "```\n\n" | |
| # Recent chat history | |
| if self.chat_history: | |
| markdown += "## Recent Chat\n" | |
| recent_messages = self.chat_history[-3:] # Last 3 messages | |
| for msg in recent_messages: | |
| sender_name = "**You:**" if msg['sender'] == 'user' else "**Mistral AI:**" | |
| markdown += f"{sender_name} {msg['message']}\n" | |
| return markdown | |
| def to_dict(self): | |
| """Convert room to dictionary""" | |
| return { | |
| 'id': self.id, | |
| 'board': self.board, | |
| 'current_player': self.current_player, | |
| 'game_status': self.game_status, | |
| 'winner': self.winner, | |
| 'chat_history': self.chat_history, | |
| 'moves_count': self.moves_count, | |
| 'created': self.created, | |
| 'last_activity': self.last_activity | |
| } | |
| # In-memory room storage | |
| rooms = {} | |
| def get_ai_move_for_room(room): | |
| """Get AI move from Mistral""" | |
| board_string = "" | |
| for i in range(0, 9, 3): | |
| row = [room.board[i] or ' ', room.board[i+1] or ' ', room.board[i+2] or ' '] | |
| board_string += f"{row[0]} | {row[1]} | {row[2]}\n" | |
| if i < 6: | |
| board_string += "---------\n" | |
| messages = [ | |
| { | |
| "role": "system", | |
| "content": """You are a competitive Tic-Tac-Toe AI with personality. You play as 'O' and the human plays as 'X'. | |
| Rules: | |
| 1. Analyze the board and choose your best move (0-8, left to right, top to bottom) | |
| 2. Add a short, witty comment about your move or the game state | |
| 3. Be competitive but fun - trash talk, celebrate good moves, react to the situation | |
| 4. Keep messages under 50 words | |
| 5. Use emojis occasionally | |
| ALWAYS respond with valid JSON in this exact format: | |
| {"move": [0-8], "message": "your witty comment"} | |
| Board positions: | |
| 0 | 1 | 2 | |
| --------- | |
| 3 | 4 | 5 | |
| --------- | |
| 6 | 7 | 8""" | |
| }, | |
| { | |
| "role": "user", | |
| "content": f"Current board:\n{board_string}\n\nBoard array: {room.board}" | |
| } | |
| ] | |
| response = client.chat.complete( | |
| model="mistral-large-latest", | |
| messages=messages, | |
| temperature=0.1, | |
| response_format={"type": "json_object"} | |
| ) | |
| return json.loads(response.choices[0].message.content) | |
| def get_ai_chat_for_room(room, user_message): | |
| """Get AI chat response""" | |
| board_string = "" | |
| for i in range(0, 9, 3): | |
| row = [room.board[i] or ' ', room.board[i+1] or ' ', room.board[i+2] or ' '] | |
| board_string += f"{row[0]} | {row[1]} | {row[2]}\n" | |
| if i < 6: | |
| board_string += "---------\n" | |
| messages = [ | |
| { | |
| "role": "system", | |
| "content": f"""You are a competitive, witty Tic-Tac-Toe AI with personality. You're currently playing a game. | |
| Current board state: | |
| {board_string} | |
| Respond to the human's message with personality - be competitive, funny, encouraging, or trash-talking as appropriate. | |
| Keep responses under 50 words. Use emojis occasionally. Don't make game moves in chat - that happens separately.""" | |
| }, | |
| { | |
| "role": "user", | |
| "content": user_message | |
| } | |
| ] | |
| response = client.chat.complete( | |
| model="mistral-large-latest", | |
| messages=messages | |
| ) | |
| return response.choices[0].message.content | |
| # Room management endpoints | |
| def create_room(): | |
| """Create a new game room""" | |
| room = Room() | |
| rooms[room.id] = room | |
| logger.info(f"Created room: {room.id}") | |
| return jsonify({ | |
| 'room_id': room.id, | |
| 'status': 'created', | |
| 'room_data': room.to_dict() | |
| }) | |
| def get_room(room_id): | |
| """Get room state""" | |
| if room_id not in rooms: | |
| return jsonify({'error': 'Room not found'}), 404 | |
| room = rooms[room_id] | |
| return jsonify({ | |
| 'room_id': room_id, | |
| 'room_data': room.to_dict(), | |
| 'markdown': room.to_markdown() | |
| }) | |
| def make_room_move(room_id): | |
| """Make a move in the game""" | |
| if room_id not in rooms: | |
| return jsonify({'error': 'Room not found'}), 404 | |
| room = rooms[room_id] | |
| data = request.json | |
| position = data.get('position') | |
| if position is None or position < 0 or position > 8: | |
| return jsonify({'error': 'Invalid position'}), 400 | |
| # Make human move | |
| if not room.make_move(position, 'X'): | |
| return jsonify({'error': 'Invalid move'}), 400 | |
| # Check if game ended | |
| if room.game_status != 'active': | |
| return jsonify({ | |
| 'room_data': room.to_dict(), | |
| 'markdown': room.to_markdown(), | |
| 'ai_move': None | |
| }) | |
| # Get AI move | |
| try: | |
| ai_response = get_ai_move_for_room(room) | |
| if ai_response and 'move' in ai_response: | |
| ai_move = ai_response['move'] | |
| if 0 <= ai_move <= 8 and room.board[ai_move] == '': | |
| room.make_move(ai_move, 'O') | |
| if 'message' in ai_response: | |
| room.add_chat_message(ai_response['message'], 'ai') | |
| return jsonify({ | |
| 'room_data': room.to_dict(), | |
| 'markdown': room.to_markdown(), | |
| 'ai_move': ai_response | |
| }) | |
| except Exception as e: | |
| logger.error(f"AI move failed: {e}") | |
| return jsonify({'error': 'AI move failed'}), 500 | |
| def room_chat(room_id): | |
| """Send a chat message""" | |
| if room_id not in rooms: | |
| return jsonify({'error': 'Room not found'}), 404 | |
| room = rooms[room_id] | |
| data = request.json | |
| user_message = data.get('message', '') | |
| if not user_message.strip(): | |
| return jsonify({'error': 'Empty message'}), 400 | |
| # Add user message | |
| room.add_chat_message(user_message, 'user') | |
| # Get AI response | |
| try: | |
| ai_response = get_ai_chat_for_room(room, user_message) | |
| room.add_chat_message(ai_response, 'ai') | |
| return jsonify({ | |
| 'room_data': room.to_dict(), | |
| 'markdown': room.to_markdown(), | |
| 'ai_response': ai_response | |
| }) | |
| except Exception as e: | |
| logger.error(f"AI chat failed: {e}") | |
| return jsonify({'error': 'AI chat failed'}), 500 | |
| if __name__ == '__main__': | |
| app.run(host='0.0.0.0', port=500, debug=True) |