class TicTacToeGame { constructor() { this.board = Array(9).fill(''); this.currentPlayer = 'X'; // Human is X, AI is O this.gameActive = true; this.cells = document.querySelectorAll('.cell'); this.gameStatus = document.getElementById('gameStatus'); this.initGame(); } initGame() { this.cells.forEach((cell, index) => { cell.addEventListener('click', () => this.handleCellClick(index)); }); } async handleCellClick(index) { if (!this.gameActive || this.board[index] !== '' || this.currentPlayer !== 'X') { return; } // Make human move this.makeMove(index, 'X'); // Check for win/draw if (this.checkGameEnd()) { return; } // AI's turn this.gameStatus.textContent = "Mistral is thinking..."; await this.makeAIMove(); } makeMove(index, player) { this.board[index] = player; this.cells[index].textContent = player; this.cells[index].classList.add(player.toLowerCase()); } async makeAIMove() { try { const aiMove = await this.getAIMoveAndChat(); console.log('AI Move received:', aiMove); console.log('Current board:', this.board); console.log('Move valid?', aiMove.move >= 0 && aiMove.move < 9); console.log('Square empty?', this.board[aiMove.move] === ''); if (aiMove && typeof aiMove.move === 'number' && aiMove.move >= 0 && aiMove.move < 9 && this.board[aiMove.move] === '') { this.makeMove(aiMove.move, 'O'); // Add AI's chat message if (aiMove.message) { this.addChatMessage(aiMove.message, 'ai'); } if (!this.checkGameEnd()) { this.gameStatus.textContent = "Your turn! Click a square."; } } else { // Fallback to random move console.warn('AI move invalid, using fallback. Move:', aiMove.move, 'Board:', this.board); const emptySpots = this.board.map((cell, index) => cell === '' ? index : null).filter(val => val !== null); if (emptySpots.length > 0) { const randomMove = emptySpots[Math.floor(Math.random() * emptySpots.length)]; this.makeMove(randomMove, 'O'); this.addChatMessage("Technical hiccup! But I'm still playing! 🤖", 'ai'); if (!this.checkGameEnd()) { this.gameStatus.textContent = "Your turn! Click a square."; } } else { this.gameStatus.textContent = "Game error - no valid moves!"; } } } catch (error) { console.error('AI move failed:', error); // Fallback to random move instead of error const emptySpots = this.board.map((cell, index) => cell === '' ? index : null).filter(val => val !== null); if (emptySpots.length > 0) { const randomMove = emptySpots[Math.floor(Math.random() * emptySpots.length)]; this.makeMove(randomMove, 'O'); this.addChatMessage("Connection issues, but I'm improvising! 😅", 'ai'); if (!this.checkGameEnd()) { this.gameStatus.textContent = "Your turn! Click a square."; } } } } async getAIMoveAndChat() { const response = await fetch("/ai-move", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ board: this.board }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return data; } async callMistralAPI(messages) { const response = await fetch("/mistral-proxy", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ model: "mistral-large-latest", messages: messages, temperature: 0.7, response_format: { type: "json_object" } }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return data.choices[0].message.content; } boardToString() { let result = ""; for (let i = 0; i < 9; i += 3) { result += `${this.board[i] || ' '} | ${this.board[i+1] || ' '} | ${this.board[i+2] || ' '}\n`; if (i < 6) result += "---------\n"; } return result; } checkGameEnd() { const winner = this.checkWinner(); if (winner) { this.gameActive = false; if (winner === 'X') { this.gameStatus.textContent = "🎉 You won! Impressive!"; this.addChatMessage("Wow! You actually beat me! 😱 Well played, human! 🏆", 'ai'); } else { this.gameStatus.textContent = "💀 Mistral AI wins!"; this.addChatMessage("Victory is mine! 😈 Better luck next time! 🤖", 'ai'); } return true; } if (this.board.every(cell => cell !== '')) { this.gameActive = false; this.gameStatus.textContent = "🤝 It's a draw!"; this.addChatMessage("A respectable draw! You're better than I expected! 🤝", 'ai'); return true; } return false; } checkWinner() { const winPatterns = [ [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 (const pattern of winPatterns) { const [a, b, c] = pattern; if (this.board[a] && this.board[a] === this.board[b] && this.board[a] === this.board[c]) { return this.board[a]; } } return null; } addChatMessage(message, sender) { const chatMessages = document.getElementById('chatMessages'); const messageDiv = document.createElement('div'); messageDiv.className = `message ${sender}`; const senderDiv = document.createElement('div'); senderDiv.className = 'message-sender'; senderDiv.textContent = sender === 'user' ? 'You:' : 'Mistral AI:'; const contentDiv = document.createElement('div'); contentDiv.textContent = message; messageDiv.appendChild(senderDiv); messageDiv.appendChild(contentDiv); chatMessages.appendChild(messageDiv); // Scroll to bottom chatMessages.scrollTop = chatMessages.scrollHeight; } async sendChatMessage(message) { if (!message.trim()) return; // Add user message this.addChatMessage(message, 'user'); try { const aiResponse = await this.getAIChatResponse(message); this.addChatMessage(aiResponse, 'ai'); } catch (error) { console.error('Chat failed:', error); this.addChatMessage("Sorry, I'm having trouble responding right now! 🤖", 'ai'); } } async getAIChatResponse(userMessage) { const response = await fetch("/ai-chat", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ message: userMessage, board: this.board }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return data.message; } } // Initialize game when page loads let game; document.addEventListener('DOMContentLoaded', () => { game = new TicTacToeGame(); }); // Chat functions function sendChatMessage() { const chatInput = document.getElementById('chatInput'); const message = chatInput.value.trim(); if (message) { game.sendChatMessage(message); chatInput.value = ''; } } function handleEnter(event) { if (event.key === 'Enter') { sendChatMessage(); } } // Reset game function resetGame() { game = new TicTacToeGame(); // Clear board visually game.cells.forEach(cell => { cell.textContent = ''; cell.classList.remove('x', 'o'); }); game.gameStatus.textContent = "Your turn! Click a square to play X"; game.addChatMessage("New game! Let's see if you can do better this time! 😏", 'ai'); }