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