|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Shy Guy Simulator - LLM Edition</title> |
|
<style> |
|
body { |
|
font-family: Arial, sans-serif; |
|
max-width: 1200px; |
|
margin: 20px auto; |
|
padding: 20px; |
|
background-color: #1a1a1a; |
|
color: #fff; |
|
} |
|
|
|
.game-layout { |
|
display: grid; |
|
grid-template-columns: 2fr 3fr; |
|
gap: 20px; |
|
} |
|
|
|
#game-container { |
|
background-color: #2a2a2a; |
|
padding: 20px; |
|
border-radius: 8px; |
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); |
|
} |
|
|
|
.chat-container { |
|
height: 300px; |
|
overflow-y: auto; |
|
margin: 20px 0; |
|
padding: 10px; |
|
background-color: #333; |
|
border-radius: 4px; |
|
} |
|
|
|
.message { |
|
margin: 10px 0; |
|
padding: 10px; |
|
border-radius: 4px; |
|
word-wrap: break-word; |
|
} |
|
|
|
.wingman-msg { |
|
background-color: #2c5282; |
|
margin-right: 20%; |
|
} |
|
|
|
.shyguy { |
|
background-color: #4a5568; |
|
margin-left: 20%; |
|
} |
|
|
|
.error { |
|
background-color: #c53030; |
|
text-align: center; |
|
} |
|
|
|
.viewport { |
|
width: 800px; |
|
height: 800px; |
|
overflow: hidden; |
|
position: relative; |
|
border: 2px solid #4a5568; |
|
border-radius: 4px; |
|
} |
|
|
|
.grid-container { |
|
width: 10240px; |
|
height: 10240px; |
|
position: relative; |
|
background-color: #1a1a1a; |
|
background-image: linear-gradient(#2d3748 1px, transparent 1px), |
|
linear-gradient(90deg, #2d3748 1px, transparent 1px); |
|
background-size: 20px 20px; |
|
} |
|
|
|
#player, #wingman { |
|
width: 16px; |
|
height: 16px; |
|
border-radius: 50%; |
|
position: absolute; |
|
transition: all 0.1s ease; |
|
z-index: 2; |
|
} |
|
|
|
#player { |
|
background-color: #4299e1; |
|
} |
|
|
|
#wingman { |
|
background-color: #48bb78; |
|
} |
|
|
|
.location-marker { |
|
position: absolute; |
|
width: 20px; |
|
height: 20px; |
|
border-radius: 4px; |
|
} |
|
|
|
.bar { background-color: #744210; } |
|
.dj { background-color: #2c5282; } |
|
.girl { background-color: #d53f8c; } |
|
.sister { background-color: #805ad5; } |
|
.obstacle { background-color: #4a5568; } |
|
|
|
.legend { |
|
display: flex; |
|
gap: 10px; |
|
margin-top: 10px; |
|
flex-wrap: wrap; |
|
} |
|
|
|
.legend-item { |
|
display: flex; |
|
align-items: center; |
|
gap: 5px; |
|
} |
|
|
|
.legend-color { |
|
width: 20px; |
|
height: 20px; |
|
border-radius: 4px; |
|
} |
|
|
|
#input-container { |
|
display: flex; |
|
gap: 10px; |
|
margin-top: 20px; |
|
} |
|
|
|
#user-input { |
|
flex-grow: 1; |
|
padding: 10px; |
|
border: none; |
|
border-radius: 4px; |
|
background-color: #4a4a4a; |
|
color: white; |
|
} |
|
|
|
button { |
|
padding: 10px 20px; |
|
background-color: #4299e1; |
|
color: white; |
|
border: none; |
|
border-radius: 4px; |
|
cursor: pointer; |
|
} |
|
|
|
button:hover { |
|
background-color: #3182ce; |
|
} |
|
|
|
#stats { |
|
margin-top: 20px; |
|
padding: 10px; |
|
background-color: #333; |
|
border-radius: 4px; |
|
display: flex; |
|
justify-content: space-between; |
|
flex-wrap: wrap; |
|
gap: 10px; |
|
} |
|
|
|
.drunk-effect { |
|
animation: wobble 1s infinite; |
|
} |
|
|
|
@keyframes wobble { |
|
0% { transform: translate(0, 0) rotate(0deg); } |
|
15% { transform: translate(-5%, 0) rotate(-5deg); } |
|
30% { transform: translate(5%, 0) rotate(5deg); } |
|
45% { transform: translate(-5%, 0) rotate(-3deg); } |
|
60% { transform: translate(5%, 0) rotate(3deg); } |
|
75% { transform: translate(-5%, 0) rotate(-1deg); } |
|
100% { transform: translate(0, 0) rotate(0deg); } |
|
} |
|
|
|
#api-key-container { |
|
margin-bottom: 20px; |
|
} |
|
|
|
.tooltip { |
|
position: fixed; |
|
background: rgba(0, 0, 0, 0.8); |
|
padding: 5px; |
|
border-radius: 4px; |
|
font-size: 12px; |
|
pointer-events: none; |
|
z-index: 100; |
|
display: none; |
|
} |
|
|
|
.win-screen { |
|
position: fixed; |
|
top: 50%; |
|
left: 50%; |
|
transform: translate(-50%, -50%); |
|
background: rgba(0, 0, 0, 0.9); |
|
padding: 20px; |
|
border-radius: 10px; |
|
text-align: center; |
|
z-index: 1000; |
|
} |
|
|
|
.controls-info { |
|
margin-top: 10px; |
|
padding: 10px; |
|
background-color: #333; |
|
border-radius: 4px; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div id="game-container"> |
|
<h1>Shy Guy Simulator - LLM Edition</h1> |
|
|
|
<div id="api-key-container"> |
|
<input type="password" id="api-key" placeholder="Enter your Mistral API key" style="width: 100%; padding: 10px; margin-bottom: 10px;"> |
|
<button onclick="initializeGame()" id="start-button">Start Game</button> |
|
</div> |
|
|
|
<div id="game-content" style="display: none;"> |
|
<div id="stats"> |
|
<span>Confidence: <span id="confidence">0</span>%</span> |
|
<span>Anxiety: <span id="anxiety">100</span>%</span> |
|
<span>Drinks: <span id="drinks">0</span></span> |
|
<span>Time: <span id="time">8:00 PM</span></span> |
|
</div> |
|
|
|
<div class="game-layout"> |
|
<div class="chat-side"> |
|
<div class="chat-container" id="chat-container"></div> |
|
<div id="input-container"> |
|
<input type="text" id="user-input" placeholder="Talk to your shy friend..."> |
|
<button onclick="handleUserInput()">Send</button> |
|
</div> |
|
<div class="controls-info"> |
|
Use arrow keys to move the wingman (green dot).<br> |
|
Get close to push the shy guy (blue dot) in the right direction! |
|
</div> |
|
</div> |
|
|
|
<div class="game-side"> |
|
<div class="viewport"> |
|
<div class="grid-container" id="party-grid"></div> |
|
</div> |
|
|
|
<div class="legend"> |
|
<div class="legend-item"> |
|
<div class="legend-color" style="background-color: #4299e1;"></div> |
|
<span>Shy Guy</span> |
|
</div> |
|
<div class="legend-item"> |
|
<div class="legend-color" style="background-color: #48bb78;"></div> |
|
<span>Wingman (You)</span> |
|
</div> |
|
<div class="legend-item"> |
|
<div class="legend-color" style="background-color: #744210;"></div> |
|
<span>Bar</span> |
|
</div> |
|
<div class="legend-item"> |
|
<div class="legend-color" style="background-color: #2c5282;"></div> |
|
<span>DJ</span> |
|
</div> |
|
<div class="legend-item"> |
|
<div class="legend-color" style="background-color: #d53f8c;"></div> |
|
<span>Girl</span> |
|
</div> |
|
<div class="legend-item"> |
|
<div class="legend-color" style="background-color: #805ad5;"></div> |
|
<span>Sister</span> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="tooltip" id="tooltip"></div> |
|
|
|
<script> |
|
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: 50, y: 450 }, |
|
wingmanPos: { x: 40, y: 450 }, |
|
isProcessing: false, |
|
hasSpokenToGirl: false, |
|
emotion: 'anxious', |
|
locations: { |
|
bar: [{ x: 450, y: 50 }, { x: 451, y: 50 }], |
|
dj: [{ x: 250, y: 20 }, { x: 251, y: 20 }], |
|
girl: [{ x: 450, y: 450 }], |
|
sister: [{ x: 150, y: 250 }], |
|
obstacles: [ |
|
{ x: 200, y: 200 }, { x: 201, y: 200 }, |
|
{ x: 300, y: 300 }, { x: 301, y: 300 } |
|
] |
|
} |
|
}; |
|
|
|
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 512x512 grid. Movement values should be between -30 and 30 units per move. |
|
ALWAYS structure your responses in this exact format: |
|
{ |
|
"dialogue": "Your spoken response here", |
|
"movement": { |
|
"x": number (-30 to 30), |
|
"y": number (-30 to 30) |
|
}, |
|
"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. Consider current position and avoid obstacles |
|
6. React to encouragement from the wingman |
|
7. If near the girl and confidence/drinks are low, move away` |
|
}]; |
|
|
|
this.initialize(); |
|
this.initializeGrid(); |
|
this.setupKeyboardControls(); |
|
} |
|
|
|
initialize() { |
|
this.addMessage("Hey! I'll be your wingman tonight. I see that girl you like over there - let's help you talk to her!", 'wingman-msg'); |
|
this.addMessage("I... I don't know about this. Maybe I should just go home...", 'shyguy'); |
|
this.updateStats(); |
|
} |
|
|
|
setupKeyboardControls() { |
|
document.addEventListener('keydown', (e) => { |
|
const moveDistance = 5; |
|
let dx = 0, dy = 0; |
|
|
|
switch(e.key) { |
|
case 'ArrowLeft': |
|
dx = -moveDistance; |
|
break; |
|
case 'ArrowRight': |
|
dx = moveDistance; |
|
break; |
|
case 'ArrowUp': |
|
dy = -moveDistance; |
|
break; |
|
case 'ArrowDown': |
|
dy = moveDistance; |
|
break; |
|
} |
|
|
|
if (dx !== 0 || dy !== 0) { |
|
this.moveWingman(dx, dy); |
|
} |
|
}); |
|
} |
|
|
|
moveWingman(dx, dy) { |
|
let newX = this.state.wingmanPos.x + dx; |
|
let newY = this.state.wingmanPos.y + dy; |
|
|
|
|
|
newX = Math.max(0, Math.min(511, newX)); |
|
newY = Math.max(0, Math.min(511, newY)); |
|
|
|
|
|
const distanceToPlayer = Math.hypot( |
|
newX - this.state.playerPos.x, |
|
newY - this.state.playerPos.y |
|
); |
|
|
|
if (distanceToPlayer < 20) { |
|
|
|
const pushForce = 10; |
|
const pushDx = (this.state.playerPos.x - newX) / distanceToPlayer * pushForce; |
|
const pushDy = (this.state.playerPos.y - newY) / distanceToPlayer * pushForce; |
|
|
|
|
|
this.movePlayer(pushDx, pushDy, true); |
|
} |
|
|
|
|
|
this.state.wingmanPos = { x: newX, y: newY }; |
|
this.updateCharacterPositions(); |
|
} |
|
|
|
async movePlayer(dx, dy, isPush = false) { |
|
let newX = this.state.playerPos.x + dx; |
|
let newY = this.state.playerPos.y + dy; |
|
|
|
|
|
newX = Math.max(0, Math.min(511, newX)); |
|
newY = Math.max(0, Math.min(511, newY)); |
|
|
|
|
|
if (this.isLocationNearby(newX, newY, 'obstacles', 15)) { |
|
return; |
|
} |
|
|
|
|
|
if (this.state.drinks >= 3 && !isPush) { |
|
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 * 10; |
|
} else { |
|
newY += randomDir * 10; |
|
} |
|
newX = Math.max(0, Math.min(511, newX)); |
|
newY = Math.max(0, Math.min(511, newY)); |
|
} |
|
} |
|
|
|
|
|
this.state.playerPos = { x: newX, y: newY }; |
|
|
|
if (!isPush) { |
|
|
|
await this.handleLocationInteraction(newX, newY); |
|
this.state.time = new Date(this.state.time.getTime() + 2 * 60000); |
|
} |
|
|
|
this.updateStats(); |
|
this.updateCharacterPositions(); |
|
} |
|
|
|
isLocationNearby(x, y, type, radius = 10) { |
|
return this.state.locations[type].some(pos => |
|
Math.hypot(x - pos.x * 20, y - pos.y * 20) < radius |
|
); |
|
} |
|
|
|
updateCharacterPositions() { |
|
const player = document.getElementById('player'); |
|
const wingman = document.getElementById('wingman'); |
|
|
|
if (player) { |
|
player.style.left = `${this.state.playerPos.x}px`; |
|
player.style.top = `${this.state.playerPos.y}px`; |
|
if (this.state.drinks >= 3) { |
|
player.classList.add('drunk-effect'); |
|
} else { |
|
player.classList.remove('drunk-effect'); |
|
} |
|
} |
|
|
|
if (wingman) { |
|
wingman.style.left = `${this.state.wingmanPos.x}px`; |
|
wingman.style.top = `${this.state.wingmanPos.y}px`; |
|
} |
|
|
|
|
|
const viewport = document.querySelector('.viewport'); |
|
if (viewport) { |
|
const scrollX = this.state.playerPos.x - viewport.clientWidth / 2; |
|
const scrollY = this.state.playerPos.y - viewport.clientHeight / 2; |
|
viewport.scrollTo(scrollX, scrollY); |
|
} |
|
} |
|
|
|
initializeGrid() { |
|
const grid = document.getElementById('party-grid'); |
|
grid.innerHTML = ''; |
|
|
|
|
|
Object.entries(this.state.locations).forEach(([type, positions]) => { |
|
positions.forEach(pos => { |
|
const marker = document.createElement('div'); |
|
marker.className = `location-marker ${type}`; |
|
marker.style.left = `${pos.x * 20}px`; |
|
marker.style.top = `${pos.y * 20}px`; |
|
grid.appendChild(marker); |
|
}); |
|
}); |
|
|
|
|
|
const player = document.createElement('div'); |
|
player.id = 'player'; |
|
if (this.state.drinks >= 3) player.classList.add('drunk-effect'); |
|
grid.appendChild(player); |
|
|
|
const wingman = document.createElement('div'); |
|
wingman.id = 'wingman'; |
|
grid.appendChild(wingman); |
|
|
|
|
|
this.updateCharacterPositions(); |
|
} |
|
|
|
async handleLocationInteraction(x, y) { |
|
x = Math.floor(x / 20); |
|
y = Math.floor(y / 20); |
|
|
|
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); |
|
await this.addMessage("*Takes another drink from the bar*", 'shyguy'); |
|
|
|
if (this.state.drinks > 5) { |
|
this.state.confidence = Math.max(0, this.state.confidence - 5); |
|
await this.addMessage("*Starting to feel really dizzy...*", 'shyguy'); |
|
} |
|
} |
|
|
|
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); |
|
await this.addMessage("*Gets some encouragement from sister*", 'shyguy'); |
|
} |
|
|
|
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); |
|
await this.addMessage("*Vibing to the music*", 'shyguy'); |
|
} |
|
|
|
if (this.isLocation(x, y, 'girl')) { |
|
if (this.state.confidence >= 70 && this.state.anxiety <= 50) { |
|
await this.addMessage("*Finally gathered the courage to talk to her!*", 'shyguy'); |
|
this.gameWon(); |
|
} else { |
|
await this.addMessage("*Gets too nervous and quickly walks away*", 'shyguy'); |
|
this.state.playerPos = { |
|
x: Math.max(0, x - 2) * 20, |
|
y: Math.max(0, y - 2) * 20 |
|
}; |
|
this.state.anxiety += 15; |
|
this.state.confidence = Math.max(0, this.state.confidence - 10); |
|
} |
|
} |
|
} |
|
|
|
isLocation(x, y, type) { |
|
return this.state.locations[type].some(pos => pos.x === x && pos.y === y); |
|
} |
|
|
|
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-msg'); |
|
this.addLoadingMessage(); |
|
|
|
const currentState = `Current state: |
|
Confidence: ${this.state.confidence}%, |
|
Anxiety: ${this.state.anxiety}%, |
|
Drinks: ${this.state.drinks}, |
|
Location: ${this.getLocationDescription()}, |
|
Position: (${Math.floor(this.state.playerPos.x / 20)},${Math.floor(this.state.playerPos.y / 20)}), |
|
Emotion: ${this.state.emotion}`; |
|
|
|
this.context.push({ |
|
role: 'user', |
|
content: `${userInput}\n\n${currentState}` |
|
}); |
|
|
|
const llmResponse = await this.callMistralAPI(); |
|
const dialogue = await this.processLLMResponse(llmResponse); |
|
|
|
this.removeLoadingMessage(); |
|
this.addMessage(dialogue, 'shyguy'); |
|
|
|
this.context.push({ |
|
role: 'assistant', |
|
content: llmResponse |
|
}); |
|
|
|
if (this.context.length > 10) { |
|
this.context = [ |
|
this.context[0], |
|
...this.context.slice(-4) |
|
]; |
|
} |
|
} catch (error) { |
|
this.removeLoadingMessage(); |
|
this.addMessage(`Error: ${error.message}`, 'error'); |
|
console.error('Error:', error); |
|
} finally { |
|
this.state.isProcessing = false; |
|
} |
|
} |
|
|
|
async processLLMResponse(response) { |
|
try { |
|
const jsonMatch = response.match(/\{[\s\S]*\}/); |
|
if (!jsonMatch) { |
|
throw new Error("Invalid response format"); |
|
} |
|
|
|
const parsedResponse = JSON.parse(jsonMatch[0]); |
|
|
|
if (!parsedResponse.dialogue || !parsedResponse.movement || !parsedResponse.emotion) { |
|
throw new Error("Missing required fields in response"); |
|
} |
|
|
|
this.state.emotion = parsedResponse.emotion; |
|
|
|
await this.movePlayer( |
|
parsedResponse.movement.x, |
|
parsedResponse.movement.y |
|
); |
|
|
|
this.updateEmotionalState(parsedResponse.emotion); |
|
|
|
return parsedResponse.dialogue; |
|
} catch (error) { |
|
console.error("Error processing LLM response:", error); |
|
return "I'm not sure what to do right now..."; |
|
} |
|
} |
|
|
|
updateEmotionalState(emotion) { |
|
const emotionEffects = { |
|
'anxious': { confidence: -5, anxiety: 5 }, |
|
'nervous': { confidence: -2, anxiety: 2 }, |
|
'slightly_confident': { confidence: 5, anxiety: -5 }, |
|
'confident': { confidence: 10, anxiety: -10 } |
|
}; |
|
|
|
const effect = emotionEffects[emotion] || { confidence: 0, anxiety: 0 }; |
|
|
|
this.state.confidence = Math.max(0, Math.min(100, this.state.confidence + effect.confidence)); |
|
this.state.anxiety = Math.max(0, Math.min(100, this.state.anxiety + effect.anxiety)); |
|
} |
|
|
|
async callMistralAPI() { |
|
try { |
|
const response = await fetch('https://api.mistral.ai/v1/chat/completions', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
'Authorization': `Bearer ${this.apiKey}` |
|
}, |
|
body: JSON.stringify({ |
|
model: 'mistral-large-latest', |
|
messages: this.context, |
|
max_tokens: 150, |
|
temperature: 0.7 |
|
}) |
|
}); |
|
|
|
if (!response.ok) { |
|
const error = await response.json(); |
|
throw new Error(error.error?.message || 'API request failed'); |
|
} |
|
|
|
const data = await response.json(); |
|
return data.choices[0].message.content; |
|
} catch (error) { |
|
if (error.message.includes('API key')) { |
|
throw new Error('Invalid API key. Please check your API key and try again.'); |
|
} |
|
throw new Error('Failed to get response from AI. Please try again.'); |
|
} |
|
} |
|
|
|
getLocationDescription() { |
|
const x = Math.floor(this.state.playerPos.x / 20); |
|
const y = Math.floor(this.state.playerPos.y / 20); |
|
|
|
if (this.isLocation(x, y, 'bar')) return 'at the bar'; |
|
if (this.isLocation(x, y, 'dj')) return 'near the DJ'; |
|
if (this.isLocation(x, y, 'sister')) return 'with sister'; |
|
if (this.isLocation(x, y, 'girl')) return 'near the girl'; |
|
return 'in the room'; |
|
} |
|
|
|
gameWon() { |
|
this.addMessage("Congratulations! You successfully talked to her, and she seems interested! The party wasn't so scary after all.", 'wingman-msg'); |
|
this.state.hasSpokenToGirl = true; |
|
|
|
const winScreen = document.createElement('div'); |
|
winScreen.className = 'win-screen'; |
|
winScreen.innerHTML = ` |
|
<h2>You did it!</h2> |
|
<p>Final Stats:</p> |
|
<p>Confidence: ${this.state.confidence}%</p> |
|
<p>Anxiety: ${this.state.anxiety}%</p> |
|
<p>Drinks: ${this.state.drinks}</p> |
|
<p>Time taken: ${this.getTimeDifference()}</p> |
|
<button onclick="location.reload()">Play Again</button> |
|
`; |
|
document.body.appendChild(winScreen); |
|
} |
|
|
|
getTimeDifference() { |
|
const startTime = new Date(2024, 0, 1, 20, 0); |
|
const timeDiff = this.state.time - startTime; |
|
const minutes = Math.floor(timeDiff / 60000); |
|
return `${minutes} minutes`; |
|
} |
|
|
|
addMessage(text, type) { |
|
const chat = document.getElementById('chat-container'); |
|
const messageDiv = document.createElement('div'); |
|
messageDiv.className = `message ${type}`; |
|
messageDiv.textContent = text; |
|
chat.appendChild(messageDiv); |
|
chat.scrollTop = chat.scrollHeight; |
|
} |
|
|
|
addLoadingMessage() { |
|
const chat = document.getElementById('chat-container'); |
|
const loadingDiv = document.createElement('div'); |
|
loadingDiv.className = 'message shyguy typing'; |
|
loadingDiv.id = 'loading-message'; |
|
loadingDiv.textContent = 'Thinking...'; |
|
chat.appendChild(loadingDiv); |
|
chat.scrollTop = chat.scrollHeight; |
|
} |
|
|
|
removeLoadingMessage() { |
|
const loadingMessage = document.getElementById('loading-message'); |
|
if (loadingMessage) { |
|
loadingMessage.remove(); |
|
} |
|
} |
|
|
|
updateStats() { |
|
document.getElementById('confidence').textContent = Math.round(this.state.confidence); |
|
document.getElementById('anxiety').textContent = Math.round(this.state.anxiety); |
|
document.getElementById('drinks').textContent = this.state.drinks; |
|
document.getElementById('time').textContent = |
|
this.state.time.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }); |
|
} |
|
} |
|
|
|
let game; |
|
|
|
function initializeGame() { |
|
const apiKey = document.getElementById('api-key').value.trim(); |
|
if (!apiKey) { |
|
alert('Please enter your Mistral API key'); |
|
return; |
|
} |
|
|
|
document.getElementById('api-key-container').style.display = 'none'; |
|
document.getElementById('game-content').style.display = 'block'; |
|
|
|
game = new ShyGuySimulator(apiKey); |
|
} |
|
|
|
async function handleUserInput() { |
|
if (!game) return; |
|
|
|
const input = document.getElementById('user-input'); |
|
const text = input.value.trim(); |
|
if (text) { |
|
await game.handleInput(text); |
|
input.value = ''; |
|
} |
|
} |
|
|
|
document.getElementById('user-input')?.addEventListener('keypress', function(e) { |
|
if (e.key === 'Enter') { |
|
handleUserInput(); |
|
} |
|
}); |
|
</script> |
|
</body> |
|
</html> |