| // 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 |