// The HTML and CSS remain the same until the ShyGuySimulator class class ShyGuySimulator { constructor(apiKey) { this.apiKey = apiKey; this.state = { confidence: 0, anxiety: 100, drinks: 0, time: new Date(2024, 0, 1, 20, 0), playerPos: { x: 0, y: 9 }, moveSpeed: 1, isProcessing: false, hasSpokenToGirl: false, lastEmotion: 'anxious', locations: { bar: [{ x: 8, y: 1 }, { x: 9, y: 1 }], dj: [{ x: 4, y: 0 }, { x: 5, y: 0 }], girl: [{ x: 9, y: 8 }], sister: [{ x: 2, y: 5 }], obstacles: [ { x: 3, y: 3 }, { x: 4, y: 3 }, { x: 6, y: 6 }, { x: 7, y: 6 } ] } }; this.context = [{ role: 'system', content: `You are roleplaying as a shy guy at a party, providing both dialogue and movement decisions. The party is on a 10x10 grid. You start at (0,9), and the girl you like is at (9,8). ALWAYS structure your responses in this exact format: { "dialogue": "Your spoken response here", "movement": { "x": number (-1, 0, or 1 for movement), "y": number (-1, 0, or 1 for movement) }, "emotion": "anxious|nervous|slightly_confident|confident" } Rules: 1. When drinks > 2, be more likely to move toward the girl 2. When confidence < 30, prefer to move away or stay still 3. Keep dialogue natural and brief (1-2 sentences) 4. Movement should reflect emotional state 5. Account for obstacles at: [(3,3), (4,3), (6,6), (7,6)] 6. Consider locations of: bar(8,1 & 9,1), DJ(4,0 & 5,0), sister(2,5) Current state: Confidence: ${this.state.confidence}%, Anxiety: ${this.state.anxiety}%` }]; this.initialize(); this.initializeGrid(); this.startAutonomousMovement(); } // Add autonomous movement startAutonomousMovement() { this.movementInterval = setInterval(async () => { if (!this.state.isProcessing && !this.state.hasSpokenToGirl) { await this.getNextMove(); } }, 5000); // Move every 5 seconds } stopAutonomousMovement() { if (this.movementInterval) { clearInterval(this.movementInterval); } } async getNextMove() { try { const response = await this.callMistralAPI(); let parsedResponse; try { // Try to parse the response as JSON parsedResponse = JSON.parse(response); } catch (e) { // If parsing fails, try to extract JSON from the text const jsonMatch = response.match(/\{[\s\S]*\}/); if (jsonMatch) { parsedResponse = JSON.parse(jsonMatch[0]); } else { throw new Error('Could not parse LLM response'); } } // Update emotional state this.state.lastEmotion = parsedResponse.emotion; // Add the dialogue this.addMessage(parsedResponse.dialogue, 'shyguy'); // Execute the movement if (parsedResponse.movement) { await this.movePlayer( parsedResponse.movement.x, parsedResponse.movement.y ); } } catch (error) { console.error('Error getting next move:', error); } } async handleInput(userInput) { if (this.state.isProcessing) return; this.state.isProcessing = true; try { if (!userInput.trim()) throw new Error("Please enter some text"); this.addMessage(userInput, 'wingman'); this.addLoadingMessage(); const currentState = `Current state: Confidence: ${this.state.confidence}%, Anxiety: ${this.state.anxiety}%, Drinks: ${this.state.drinks}, Position: (${this.state.playerPos.x},${this.state.playerPos.y}), Location: ${this.getLocationDescription()}`; this.context.push({ role: 'user', content: `${userInput}\n\n${currentState}` }); await this.getNextMove(); } catch (error) { this.removeLoadingMessage(); this.addMessage(`Error: ${error.message}`, 'error'); console.error('Error:', error); } finally { this.state.isProcessing = false; } } async movePlayer(dx, dy) { let newX = this.state.playerPos.x + dx * this.state.moveSpeed; let newY = this.state.playerPos.y + dy * this.state.moveSpeed; // Check boundaries newX = Math.max(0, Math.min(9, newX)); newY = Math.max(0, Math.min(9, newY)); // Check obstacles if (this.isLocation(newX, newY, 'obstacles')) { return; } // Random stumble when drunk if (this.state.drinks >= 3) { const stumbleChance = (this.state.drinks - 2) * 0.1; if (Math.random() < stumbleChance) { const randomDir = Math.random() < 0.5 ? 1 : -1; if (Math.random() < 0.5) { newX += randomDir; } else { newY += randomDir; } newX = Math.max(0, Math.min(9, newX)); newY = Math.max(0, Math.min(9, newY)); } } // Update position this.state.playerPos = { x: newX, y: newY }; // Handle location interactions await this.handleLocationInteraction(newX, newY); // Advance time this.state.time = new Date(this.state.time.getTime() + 2 * 60000); this.updateStats(); this.initializeGrid(); } async handleLocationInteraction(x, y) { if (this.isLocation(x, y, 'bar')) { this.state.drinks++; this.state.confidence = Math.min(100, this.state.confidence + 15); this.state.anxiety = Math.max(0, this.state.anxiety - 10); this.state.moveSpeed = Math.min(2, 1 + this.state.drinks * 0.2); if (this.state.drinks > 5) { this.state.confidence = Math.max(0, this.state.confidence - 5); await this.handleInput("*Starting to feel really dizzy...*"); } } if (this.isLocation(x, y, 'sister')) { this.state.confidence = Math.min(100, this.state.confidence + 20); this.state.anxiety = Math.max(0, this.state.anxiety - 15); } if (this.isLocation(x, y, 'dj')) { this.state.confidence = Math.min(100, this.state.confidence + 10); this.state.anxiety = Math.max(0, this.state.anxiety - 5); } if (this.isLocation(x, y, 'girl')) { if (this.state.confidence >= 70 && this.state.anxiety <= 50) { this.gameWon(); } else { this.state.playerPos = { x: Math.max(0, x - 2), y: Math.max(0, y - 2) }; this.state.anxiety += 15; this.state.confidence = Math.max(0, this.state.confidence - 10); } } } gameWon() { this.stopAutonomousMovement(); // Rest of gameWon implementation remains the same } // Rest of the class implementation remains the same } // The rest of the code (event listeners, etc.) remains the same