Spaces:
Sleeping
Sleeping
| 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'); | |
| } |