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