zshashz commited on
Commit
e3c370d
·
1 Parent(s): 4a9fc22
Files changed (2) hide show
  1. .gitignore +1 -1
  2. index.html +519 -270
.gitignore CHANGED
@@ -1 +1 @@
1
- *_s.js
 
1
+ *_s.html
index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Shy Guy Simulator - Grid Edition</title>
7
  <style>
8
  body {
9
  font-family: Arial, sans-serif;
@@ -16,7 +16,7 @@
16
 
17
  .game-layout {
18
  display: grid;
19
- grid-template-columns: 1fr 400px;
20
  gap: 20px;
21
  }
22
 
@@ -27,44 +27,92 @@
27
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
28
  }
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  .grid-container {
31
  display: grid;
32
- grid-template-columns: repeat(8, 1fr);
33
  gap: 2px;
34
- background-color: #333;
35
  padding: 10px;
 
36
  border-radius: 4px;
37
- margin-bottom: 20px;
38
  }
39
 
40
  .grid-cell {
41
  aspect-ratio: 1;
42
- background-color: #4a4a4a;
43
  border-radius: 2px;
44
  display: flex;
45
  align-items: center;
46
  justify-content: center;
47
- font-size: 20px;
 
48
  }
49
 
50
- .chat-container {
51
- height: 400px;
52
- overflow-y: auto;
53
- padding: 10px;
54
- background-color: #333;
55
- border-radius: 4px;
 
56
  }
57
 
58
- .message {
59
- margin: 10px 0;
60
- padding: 10px;
61
- border-radius: 4px;
62
- word-wrap: break-word;
 
 
 
 
 
 
 
 
 
 
 
 
63
  }
64
 
65
- .wingman { background-color: #2c5282; margin-right: 20%; }
66
- .shyguy { background-color: #4a5568; margin-left: 20%; }
67
- .error { background-color: #c53030; text-align: center; }
 
 
68
 
69
  #input-container {
70
  display: flex;
@@ -90,28 +138,62 @@
90
  cursor: pointer;
91
  }
92
 
 
 
 
 
93
  #stats {
 
 
 
 
 
 
 
 
 
 
 
94
  display: grid;
95
  grid-template-columns: repeat(3, 1fr);
96
- gap: 10px;
97
- margin-bottom: 20px;
 
98
  }
99
 
100
- .stat-box {
101
- background-color: #333;
102
- padding: 10px;
103
- border-radius: 4px;
104
- text-align: center;
 
 
 
 
 
 
 
105
  }
106
 
107
  #api-key-container {
108
  margin-bottom: 20px;
109
  }
 
 
 
 
 
 
 
 
 
 
 
110
  </style>
111
  </head>
112
  <body>
113
  <div id="game-container">
114
- <h1>Shy Guy Simulator - Grid Edition</h1>
115
 
116
  <div id="api-key-container">
117
  <input type="password" id="api-key" placeholder="Enter your Mistral API key" style="width: 100%; padding: 10px; margin-bottom: 10px;">
@@ -120,295 +202,462 @@
120
 
121
  <div id="game-content" style="display: none;">
122
  <div id="stats">
123
- <div class="stat-box">Confidence: <span id="confidence">0</span>%</div>
124
- <div class="stat-box">Drinks: <span id="drinks">0</span></div>
125
- <div class="stat-box">Time: <span id="time">8:00 PM</span></div>
 
126
  </div>
127
 
128
  <div class="game-layout">
129
- <div class="grid-container" id="grid"></div>
130
- <div class="chat-section">
131
  <div class="chat-container" id="chat-container"></div>
132
  <div id="input-container">
133
  <input type="text" id="user-input" placeholder="Type your encouragement as wingman...">
134
  <button onclick="handleUserInput()">Send</button>
135
  </div>
136
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  </div>
138
  </div>
139
  </div>
140
 
141
- <script>
142
- class ShyGuySimulator {
143
- constructor(apiKey) {
144
- this.apiKey = apiKey;
145
- this.state = {
146
- confidence: 0,
147
- drinks: 0,
148
- time: new Date(2024, 0, 1, 20, 0),
149
- position: { x: 0, y: 0 }, // Start at top-left
150
- isProcessing: false
151
- };
152
-
153
- this.targetPosition = { x: 7, y: 7 }; // Girl's position (bottom-right)
154
- this.gridSize = 8;
155
-
156
- // System prompt that instructs the model to return structured responses
157
- this.context = [{
158
- role: 'system',
159
- content: `You are roleplaying as a shy guy at a party, providing both dialogue and movement decisions.
160
- The party is on an 8x8 grid. You start at (0,0), and the girl you like is at (7,7).
161
- ALWAYS structure your responses in this exact format:
162
- {
163
- "dialogue": "Your spoken response here",
164
- "movement": {
165
- "x": number (-1, 0, or 1 for movement),
166
- "y": number (-1, 0, or 1 for movement)
167
- },
168
- "emotion": "anxious|nervous|slightly_confident|confident"
169
- }
170
-
171
- Rules:
172
- 1. When drinks > 2, be more likely to move toward the girl
173
- 2. When confidence < 30, prefer to move away or stay still
174
- 3. Keep dialogue natural and brief (1-2 sentences)
175
- 4. Movement should reflect emotional state`
176
- }];
177
-
178
- this.initialize();
179
- }
180
 
181
- initialize() {
182
- this.setupGrid();
183
- this.addMessage("Let's help you talk to her! I'll be your wingman tonight.", 'wingman');
184
- this.addMessage("I... I don't know about this. Maybe I should just go home...", 'shyguy');
185
- this.updateStats();
186
- this.updateGrid();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  }
188
-
189
- setupGrid() {
190
- const grid = document.getElementById('grid');
191
- grid.innerHTML = '';
192
- for (let y = 0; y < this.gridSize; y++) {
193
- for (let x = 0; x < this.gridSize; x++) {
194
- const cell = document.createElement('div');
195
- cell.className = 'grid-cell';
196
- cell.id = `cell-${x}-${y}`;
197
- grid.appendChild(cell);
198
- }
199
- }
200
  }
 
201
 
202
- updateGrid() {
203
- // Clear all cells
204
- document.querySelectorAll('.grid-cell').forEach(cell => {
205
- cell.textContent = '';
206
- });
207
 
208
- // Place shy guy
209
- const shyGuyCell = document.getElementById(`cell-${this.state.position.x}-${this.state.position.y}`);
210
- if (shyGuyCell) shyGuyCell.textContent = '😳';
 
 
211
 
212
- // Place girl
213
- const girlCell = document.getElementById(`cell-${this.targetPosition.x}-${this.targetPosition.y}`);
214
- if (girlCell) girlCell.textContent = '👧';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  }
 
 
216
 
217
- async handleInput(userInput) {
218
- if (this.state.isProcessing) return;
219
- this.state.isProcessing = true;
220
-
221
- try {
222
- if (!userInput.trim()) throw new Error("Please enter some text");
 
 
 
 
 
223
 
224
- this.addMessage(userInput, 'wingman');
225
- this.addLoadingMessage();
 
 
226
 
227
- const currentState = `Current state:
228
- - Confidence: ${this.state.confidence}%
229
- - Drinks: ${this.state.drinks}
230
- - Position: (${this.state.position.x}, ${this.state.position.y})
231
- - Distance to girl: ${this.calculateDistance()}`;
232
-
233
- this.context.push({
234
- role: 'user',
235
- content: `${userInput}\n\n${currentState}`
236
- });
237
 
238
- const response = await this.callMistralAPI();
239
-
240
- this.removeLoadingMessage();
241
- this.processAIResponse(response);
242
-
243
- this.context.push({
244
- role: 'assistant',
245
- content: response
246
- });
247
-
248
- if (this.context.length > 10) {
249
- this.context = [
250
- this.context[0],
251
- ...this.context.slice(-4)
252
- ];
253
- }
254
- } catch (error) {
255
- this.removeLoadingMessage();
256
- this.addMessage(`Error: ${error.message}`, 'error');
257
- console.error('Error:', error);
258
- } finally {
259
- this.state.isProcessing = false;
260
  }
 
 
261
  }
 
262
 
263
- calculateDistance() {
264
- const dx = this.targetPosition.x - this.state.position.x;
265
- const dy = this.targetPosition.y - this.state.position.y;
266
- return Math.sqrt(dx * dx + dy * dy);
 
 
 
 
 
 
 
 
 
267
  }
268
-
269
- processAIResponse(responseText) {
270
- try {
271
- // Extract JSON from response if it's wrapped in text
272
- const jsonMatch = responseText.match(/\{[\s\S]*\}/);
273
- const responseData = jsonMatch ? JSON.parse(jsonMatch[0]) : JSON.parse(responseText);
274
-
275
- // Add the dialogue to chat
276
- this.addMessage(responseData.dialogue, 'shyguy');
277
-
278
- // Update position
279
- const newX = Math.max(0, Math.min(7, this.state.position.x + responseData.movement.x));
280
- const newY = Math.max(0, Math.min(7, this.state.position.y + responseData.movement.y));
281
- this.state.position = { x: newX, y: newY };
282
-
283
- // Update confidence based on emotion
284
- const emotionConfidenceMap = {
285
- 'anxious': -5,
286
- 'nervous': 0,
287
- 'slightly_confident': 5,
288
- 'confident': 10
 
289
  };
290
- this.state.confidence = Math.max(0, Math.min(100,
291
- this.state.confidence + (emotionConfidenceMap[responseData.emotion] || 0)
292
- ));
293
-
294
- // Update the grid and stats
295
- this.updateGrid();
296
- this.updateStats();
297
-
298
- // Check win condition
299
- if (this.state.position.x === this.targetPosition.x &&
300
- this.state.position.y === this.targetPosition.y) {
301
- this.handleWin();
302
- }
303
- } catch (error) {
304
- console.error('Error processing AI response:', error);
305
- this.addMessage("I... uh... *mumbles something incoherent*", 'shyguy');
306
  }
307
  }
308
 
309
- handleWin() {
310
- this.addMessage("Oh my god, I actually made it! Hi... I've been wanting to talk to you...", 'shyguy');
311
- this.addMessage("Congratulations! You've helped Shy Guy reach his goal!", 'wingman');
312
- // Disable input
313
- document.getElementById('user-input').disabled = true;
314
- }
315
 
316
- async callMistralAPI() {
317
- try {
318
- const response = await fetch('https://api.mistral.ai/v1/chat/completions', {
319
- method: 'POST',
320
- headers: {
321
- 'Content-Type': 'application/json',
322
- 'Authorization': `Bearer ${this.apiKey}`
323
- },
324
- body: JSON.stringify({
325
- model: 'mistral-large-latest',
326
- messages: this.context,
327
- max_tokens: 150,
328
- temperature: 0.7
329
- })
330
- });
331
-
332
- if (!response.ok) {
333
- const error = await response.json();
334
- throw new Error(error.error?.message || 'API request failed');
335
- }
336
-
337
- const data = await response.json();
338
- return data.choices[0].message.content;
339
- } catch (error) {
340
- if (error.message.includes('API key')) {
341
- throw new Error('Invalid API key. Please check your API key and try again.');
342
- }
343
- throw new Error('Failed to get response from AI. Please try again.');
344
- }
345
- }
346
 
347
- addMessage(text, type) {
348
- const chat = document.getElementById('chat-container');
349
- const messageDiv = document.createElement('div');
350
- messageDiv.className = `message ${type}`;
351
- messageDiv.textContent = text;
352
- chat.appendChild(messageDiv);
353
- chat.scrollTop = chat.scrollHeight;
354
- }
355
 
356
- addLoadingMessage() {
357
- const chat = document.getElementById('chat-container');
358
- const loadingDiv = document.createElement('div');
359
- loadingDiv.className = 'message shyguy typing';
360
- loadingDiv.id = 'loading-message';
361
- loadingDiv.textContent = 'Thinking...';
362
- chat.appendChild(loadingDiv);
363
- chat.scrollTop = chat.scrollHeight;
364
- }
365
 
366
- removeLoadingMessage() {
367
- const loadingMessage = document.getElementById('loading-message');
368
- if (loadingMessage) {
369
- loadingMessage.remove();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
  }
 
 
 
 
 
 
371
  }
 
372
 
373
- updateStats() {
374
- document.getElementById('confidence').textContent = this.state.confidence;
375
- document.getElementById('drinks').textContent = this.state.drinks;
376
- document.getElementById('time').textContent =
377
- this.state.time.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
378
- }
 
379
  }
380
 
381
- let game;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
 
383
- function initializeGame() {
384
- const apiKey = document.getElementById('api-key').value.trim();
385
- if (!apiKey) {
386
- alert('Please enter your Mistral API key');
387
- return;
 
 
388
  }
 
389
 
390
- document.getElementById('api-key-container').style.display = 'none';
391
- document.getElementById('game-content').style.display = 'block';
 
392
 
393
- game = new ShyGuySimulator(apiKey);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
  }
395
 
396
- async function handleUserInput() {
397
- if (!game) return;
398
-
399
- const input = document.getElementById('user-input');
400
- const text = input.value.trim();
401
- if (text) {
402
- await game.handleInput(text);
403
- input.value = '';
404
- }
405
  }
406
 
407
- document.getElementById('user-input')?.addEventListener('keypress', function(e) {
408
- if (e.key === 'Enter') {
409
- handleUserInput();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
  }
411
- });
412
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
  </body>
414
- </html>
 
 
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Shy Guy Simulator - Complete Edition</title>
7
  <style>
8
  body {
9
  font-family: Arial, sans-serif;
 
16
 
17
  .game-layout {
18
  display: grid;
19
+ grid-template-columns: 2fr 3fr;
20
  gap: 20px;
21
  }
22
 
 
27
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
28
  }
29
 
30
+ .chat-container {
31
+ height: 300px;
32
+ overflow-y: auto;
33
+ margin: 20px 0;
34
+ padding: 10px;
35
+ background-color: #333;
36
+ border-radius: 4px;
37
+ }
38
+
39
+ .message {
40
+ margin: 10px 0;
41
+ padding: 10px;
42
+ border-radius: 4px;
43
+ word-wrap: break-word;
44
+ }
45
+
46
+ .wingman {
47
+ background-color: #2c5282;
48
+ margin-right: 20%;
49
+ }
50
+
51
+ .shyguy {
52
+ background-color: #4a5568;
53
+ margin-left: 20%;
54
+ }
55
+
56
+ .error {
57
+ background-color: #c53030;
58
+ text-align: center;
59
+ }
60
+
61
  .grid-container {
62
  display: grid;
63
+ grid-template-columns: repeat(10, 1fr);
64
  gap: 2px;
65
+ background-color: #1a1a1a;
66
  padding: 10px;
67
+ margin: 20px 0;
68
  border-radius: 4px;
69
+ aspect-ratio: 1;
70
  }
71
 
72
  .grid-cell {
73
  aspect-ratio: 1;
74
+ background-color: #2d3748;
75
  border-radius: 2px;
76
  display: flex;
77
  align-items: center;
78
  justify-content: center;
79
+ position: relative;
80
+ cursor: pointer;
81
  }
82
 
83
+ #player {
84
+ background-color: #4299e1;
85
+ width: 80%;
86
+ height: 80%;
87
+ border-radius: 50%;
88
+ position: absolute;
89
+ transition: all 0.3s ease;
90
  }
91
 
92
+ .bar { background-color: #744210 !important; }
93
+ .dj { background-color: #2c5282 !important; }
94
+ .girl { background-color: #d53f8c !important; }
95
+ .sister { background-color: #805ad5 !important; }
96
+ .obstacle { background-color: #4a5568 !important; }
97
+
98
+ .legend {
99
+ display: flex;
100
+ gap: 10px;
101
+ margin-top: 10px;
102
+ flex-wrap: wrap;
103
+ }
104
+
105
+ .legend-item {
106
+ display: flex;
107
+ align-items: center;
108
+ gap: 5px;
109
  }
110
 
111
+ .legend-color {
112
+ width: 20px;
113
+ height: 20px;
114
+ border-radius: 4px;
115
+ }
116
 
117
  #input-container {
118
  display: flex;
 
138
  cursor: pointer;
139
  }
140
 
141
+ button:hover {
142
+ background-color: #3182ce;
143
+ }
144
+
145
  #stats {
146
+ margin-top: 20px;
147
+ padding: 10px;
148
+ background-color: #333;
149
+ border-radius: 4px;
150
+ display: flex;
151
+ justify-content: space-between;
152
+ flex-wrap: wrap;
153
+ gap: 10px;
154
+ }
155
+
156
+ .movement-controls {
157
  display: grid;
158
  grid-template-columns: repeat(3, 1fr);
159
+ gap: 5px;
160
+ max-width: 200px;
161
+ margin: 20px auto;
162
  }
163
 
164
+ .drunk-effect {
165
+ animation: wobble 1s infinite;
166
+ }
167
+
168
+ @keyframes wobble {
169
+ 0% { transform: translate(0, 0) rotate(0deg); }
170
+ 15% { transform: translate(-5%, 0) rotate(-5deg); }
171
+ 30% { transform: translate(5%, 0) rotate(5deg); }
172
+ 45% { transform: translate(-5%, 0) rotate(-3deg); }
173
+ 60% { transform: translate(5%, 0) rotate(3deg); }
174
+ 75% { transform: translate(-5%, 0) rotate(-1deg); }
175
+ 100% { transform: translate(0, 0) rotate(0deg); }
176
  }
177
 
178
  #api-key-container {
179
  margin-bottom: 20px;
180
  }
181
+
182
+ .tooltip {
183
+ position: absolute;
184
+ background: rgba(0, 0, 0, 0.8);
185
+ padding: 5px;
186
+ border-radius: 4px;
187
+ font-size: 12px;
188
+ pointer-events: none;
189
+ z-index: 100;
190
+ display: none;
191
+ }
192
  </style>
193
  </head>
194
  <body>
195
  <div id="game-container">
196
+ <h1>Shy Guy Simulator - Complete Edition</h1>
197
 
198
  <div id="api-key-container">
199
  <input type="password" id="api-key" placeholder="Enter your Mistral API key" style="width: 100%; padding: 10px; margin-bottom: 10px;">
 
202
 
203
  <div id="game-content" style="display: none;">
204
  <div id="stats">
205
+ <span>Confidence: <span id="confidence">0</span>%</span>
206
+ <span>Anxiety: <span id="anxiety">100</span>%</span>
207
+ <span>Drinks: <span id="drinks">0</span></span>
208
+ <span>Time: <span id="time">8:00 PM</span></span>
209
  </div>
210
 
211
  <div class="game-layout">
212
+ <div class="chat-side">
 
213
  <div class="chat-container" id="chat-container"></div>
214
  <div id="input-container">
215
  <input type="text" id="user-input" placeholder="Type your encouragement as wingman...">
216
  <button onclick="handleUserInput()">Send</button>
217
  </div>
218
  </div>
219
+
220
+ <div class="game-side">
221
+ <div class="grid-container" id="party-grid"></div>
222
+
223
+ <div class="legend">
224
+ <div class="legend-item">
225
+ <div class="legend-color" style="background-color: #4299e1;"></div>
226
+ <span>You</span>
227
+ </div>
228
+ <div class="legend-item">
229
+ <div class="legend-color" style="background-color: #744210;"></div>
230
+ <span>Bar</span>
231
+ </div>
232
+ <div class="legend-item">
233
+ <div class="legend-color" style="background-color: #2c5282;"></div>
234
+ <span>DJ</span>
235
+ </div>
236
+ <div class="legend-item">
237
+ <div class="legend-color" style="background-color: #d53f8c;"></div>
238
+ <span>Girl</span>
239
+ </div>
240
+ <div class="legend-item">
241
+ <div class="legend-color" style="background-color: #805ad5;"></div>
242
+ <span>Sister</span>
243
+ </div>
244
+ </div>
245
+
246
+ <div class="movement-controls">
247
+ <button onclick="move(0, -1)">↑</button>
248
+ <button onclick="move(-1, 0)">←</button>
249
+ <button onclick="move(1, 0)">→</button>
250
+ <button onclick="move(0, 1)">↓</button>
251
+ </div>
252
+ </div>
253
  </div>
254
  </div>
255
  </div>
256
 
257
+ <div class="tooltip" id="tooltip"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
+ <script>
260
+ class ShyGuySimulator {
261
+ constructor(apiKey) {
262
+ this.apiKey = apiKey;
263
+ this.state = {
264
+ confidence: 0,
265
+ anxiety: 100,
266
+ drinks: 0,
267
+ time: new Date(2024, 0, 1, 20, 0),
268
+ playerPos: { x: 0, y: 9 }, // Start at bottom left
269
+ moveSpeed: 1,
270
+ isProcessing: false,
271
+ hasSpokenToGirl: false,
272
+ locations: {
273
+ bar: [{ x: 8, y: 1 }, { x: 9, y: 1 }],
274
+ dj: [{ x: 4, y: 0 }, { x: 5, y: 0 }],
275
+ girl: [{ x: 9, y: 8 }],
276
+ sister: [{ x: 2, y: 5 }],
277
+ obstacles: [
278
+ { x: 3, y: 3 }, { x: 4, y: 3 },
279
+ { x: 6, y: 6 }, { x: 7, y: 6 }
280
+ ]
281
  }
282
+ };
283
+
284
+ this.context = [
285
+ {
286
+ role: 'system',
287
+ content: `You are roleplaying as a shy and anxious guy at a homecoming party.
288
+ You're standing near the entrance, and the girl you like is across the room.
289
+ Your responses should reflect your social anxiety and reluctance to approach her.
290
+ Keep responses concise (max 2-3 sentences) and natural.
291
+ Express hesitation, worry, and self-doubt while reacting to the wingman's encouragement.
292
+ Current state: Confidence: ${this.state.confidence}%, Anxiety: ${this.state.anxiety}%`
 
293
  }
294
+ ];
295
 
296
+ this.initialize();
297
+ this.initializeGrid();
298
+ }
 
 
299
 
300
+ initialize() {
301
+ 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');
302
+ this.addMessage("I... I don't know about this. Maybe I should just go home...", 'shyguy');
303
+ this.updateStats();
304
+ }
305
 
306
+ initializeGrid() {
307
+ const grid = document.getElementById('party-grid');
308
+ grid.innerHTML = '';
309
+
310
+ for (let y = 0; y < 10; y++) {
311
+ for (let x = 0; x < 10; x++) {
312
+ const cell = document.createElement('div');
313
+ cell.className = 'grid-cell';
314
+ cell.dataset.x = x;
315
+ cell.dataset.y = y;
316
+
317
+ // Add special locations
318
+ if (this.isLocation(x, y, 'bar')) {
319
+ cell.classList.add('bar');
320
+ cell.dataset.tooltip = "Bar - Get liquid courage";
321
+ }
322
+ if (this.isLocation(x, y, 'dj')) {
323
+ cell.classList.add('dj');
324
+ cell.dataset.tooltip = "DJ - Vibe to the music";
325
+ }
326
+ if (this.isLocation(x, y, 'girl')) {
327
+ cell.classList.add('girl');
328
+ cell.dataset.tooltip = "The girl you like";
329
+ }
330
+ if (this.isLocation(x, y, 'sister')) {
331
+ cell.classList.add('sister');
332
+ cell.dataset.tooltip = "Your sister - Get some encouragement";
333
+ }
334
+ if (this.isLocation(x, y, 'obstacles')) {
335
+ cell.classList.add('obstacle');
336
+ cell.dataset.tooltip = "Can't walk here";
337
+ }
338
+
339
+ if (x === this.state.playerPos.x && y === this.state.playerPos.y) {
340
+ const player = document.createElement('div');
341
+ player.id = 'player';
342
+ if (this.state.drinks >= 3) player.classList.add('drunk-effect');
343
+ cell.appendChild(player);
344
+ }
345
+
346
+ // Add tooltip events
347
+ cell.addEventListener('mouseover', this.showTooltip);
348
+ cell.addEventListener('mouseout', this.hideTooltip);
349
+
350
+ grid.appendChild(cell);
351
  }
352
+ }
353
+ }
354
 
355
+ showTooltip(e) {
356
+ const tooltip = document.getElementById('tooltip');
357
+ const tooltipText = e.target.dataset.tooltip;
358
+
359
+ if (tooltipText) {
360
+ tooltip.textContent = tooltipText;
361
+ tooltip.style.display = 'block';
362
+ tooltip.style.left = e.pageX + 10 + 'px';
363
+ tooltip.style.top = e.pageY + 10 + 'px';
364
+ }
365
+ }
366
 
367
+ hideTooltip() {
368
+ const tooltip = document.getElementById('tooltip');
369
+ tooltip.style.display = 'none';
370
+ }
371
 
372
+ isLocation(x, y, type) {
373
+ return this.state.locations[type].some(pos => pos.x === x && pos.y === y);
374
+ }
 
 
 
 
 
 
 
375
 
376
+ async movePlayer(dx, dy) {
377
+ if (this.state.isProcessing) return;
378
+
379
+ let newX = this.state.playerPos.x + dx * this.state.moveSpeed;
380
+ let newY = this.state.playerPos.y + dy * this.state.moveSpeed;
381
+
382
+ // Check boundaries
383
+ newX = Math.max(0, Math.min(9, newX));
384
+ newY = Math.max(0, Math.min(9, newY));
385
+
386
+ // Check obstacles
387
+ if (this.isLocation(newX, newY, 'obstacles')) return;
388
+
389
+ // Random stumble when drunk
390
+ if (this.state.drinks >= 3) {
391
+ const stumbleChance = (this.state.drinks - 2) * 0.1;
392
+ if (Math.random() < stumbleChance) {
393
+ const randomDir = Math.random() < 0.5 ? 1 : -1;
394
+ if (Math.random() < 0.5) {
395
+ newX += randomDir;
396
+ } else {
397
+ newY += randomDir;
398
  }
399
+ newX = Math.max(0, Math.min(9, newX));
400
+ newY = Math.max(0, Math.min(9, newY));
401
  }
402
+ }
403
 
404
+ // Update position
405
+ this.state.playerPos = { x: newX, y: newY };
406
+
407
+ // Check for interactions
408
+ [Previous code remains the same until the movePlayer method...]
409
+
410
+ // Check for interactions
411
+ if (this.isLocation(newX, newY, 'bar')) {
412
+ this.state.drinks++;
413
+ this.state.confidence = Math.min(100, this.state.confidence + 15);
414
+ this.state.anxiety = Math.max(0, this.state.anxiety - 10);
415
+ this.state.moveSpeed = Math.min(2, 1 + this.state.drinks * 0.2);
416
+ await this.handleInput("*Takes another drink from the bar*");
417
  }
418
+
419
+ if (this.isLocation(newX, newY, 'sister')) {
420
+ this.state.confidence = Math.min(100, this.state.confidence + 20);
421
+ this.state.anxiety = Math.max(0, this.state.anxiety - 15);
422
+ await this.handleInput("*Talks to sister for encouragement*");
423
+ }
424
+
425
+ if (this.isLocation(newX, newY, 'dj')) {
426
+ this.state.confidence = Math.min(100, this.state.confidence + 10);
427
+ this.state.anxiety = Math.max(0, this.state.anxiety - 5);
428
+ await this.handleInput("*Vibing to the music near the DJ*");
429
+ }
430
+
431
+ if (this.isLocation(newX, newY, 'girl')) {
432
+ if (this.state.confidence >= 70) {
433
+ await this.handleInput("*Finally gathered the courage to talk to her!*");
434
+ this.gameWon();
435
+ } else {
436
+ await this.handleInput("*Gets too nervous and quickly walks away*");
437
+ this.state.playerPos = {
438
+ x: Math.max(0, newX - 2),
439
+ y: Math.max(0, newY - 2)
440
  };
441
+ this.state.anxiety += 15;
442
+ this.state.confidence = Math.max(0, this.state.confidence - 10);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
  }
444
  }
445
 
446
+ this.updateStats();
447
+ this.initializeGrid();
448
+ }
 
 
 
449
 
450
+ async handleInput(userInput) {
451
+ if (this.state.isProcessing) return;
452
+ this.state.isProcessing = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
 
454
+ try {
455
+ if (!userInput.trim()) throw new Error("Please enter some text");
 
 
 
 
 
 
456
 
457
+ this.addMessage(userInput, 'wingman');
458
+ this.addLoadingMessage();
 
 
 
 
 
 
 
459
 
460
+ const currentState = `Current state: Confidence: ${this.state.confidence}%, Anxiety: ${this.state.anxiety}%, Drinks: ${this.state.drinks}, Location: ${this.getLocationDescription()}`;
461
+
462
+ this.context.push({
463
+ role: 'user',
464
+ content: `${userInput}\n\n${currentState}`
465
+ });
466
+
467
+ const response = await this.callMistralAPI();
468
+
469
+ this.removeLoadingMessage();
470
+ this.addMessage(response, 'shyguy');
471
+
472
+ this.context.push({
473
+ role: 'assistant',
474
+ content: response
475
+ });
476
+
477
+ // Keep context manageable
478
+ if (this.context.length > 10) {
479
+ this.context = [
480
+ this.context[0],
481
+ ...this.context.slice(-4)
482
+ ];
483
  }
484
+ } catch (error) {
485
+ this.removeLoadingMessage();
486
+ this.addMessage(`Error: ${error.message}`, 'error');
487
+ console.error('Error:', error);
488
+ } finally {
489
+ this.state.isProcessing = false;
490
  }
491
+ }
492
 
493
+ getLocationDescription() {
494
+ const { x, y } = this.state.playerPos;
495
+ if (this.isLocation(x, y, 'bar')) return 'at the bar';
496
+ if (this.isLocation(x, y, 'dj')) return 'near the DJ';
497
+ if (this.isLocation(x, y, 'sister')) return 'with sister';
498
+ if (this.isLocation(x, y, 'girl')) return 'near the girl';
499
+ return 'in the room';
500
  }
501
 
502
+ async callMistralAPI() {
503
+ try {
504
+ const response = await fetch('https://api.mistral.ai/v1/chat/completions', {
505
+ method: 'POST',
506
+ headers: {
507
+ 'Content-Type': 'application/json',
508
+ 'Authorization': `Bearer ${this.apiKey}`
509
+ },
510
+ body: JSON.stringify({
511
+ model: 'mistral-large-latest',
512
+ messages: this.context,
513
+ max_tokens: 150,
514
+ temperature: 0.7
515
+ })
516
+ });
517
+
518
+ if (!response.ok) {
519
+ const error = await response.json();
520
+ throw new Error(error.error?.message || 'API request failed');
521
+ }
522
 
523
+ const data = await response.json();
524
+ return data.choices[0].message.content;
525
+ } catch (error) {
526
+ if (error.message.includes('API key')) {
527
+ throw new Error('Invalid API key. Please check your API key and try again.');
528
+ }
529
+ throw new Error('Failed to get response from AI. Please try again.');
530
  }
531
+ }
532
 
533
+ gameWon() {
534
+ this.addMessage("Congratulations! You successfully talked to her, and she seems interested! The party wasn't so scary after all.", 'wingman');
535
+ this.state.hasSpokenToGirl = true;
536
 
537
+ // Create win screen
538
+ const winScreen = document.createElement('div');
539
+ winScreen.style.position = 'fixed';
540
+ winScreen.style.top = '50%';
541
+ winScreen.style.left = '50%';
542
+ winScreen.style.transform = 'translate(-50%, -50%)';
543
+ winScreen.style.background = 'rgba(0, 0, 0, 0.9)';
544
+ winScreen.style.padding = '20px';
545
+ winScreen.style.borderRadius = '10px';
546
+ winScreen.style.textAlign = 'center';
547
+ winScreen.innerHTML = `
548
+ <h2>You did it!</h2>
549
+ <p>Final Stats:</p>
550
+ <p>Confidence: ${this.state.confidence}%</p>
551
+ <p>Anxiety: ${this.state.anxiety}%</p>
552
+ <p>Drinks: ${this.state.drinks}</p>
553
+ <p>Time taken: ${this.getTimeDifference()}</p>
554
+ <button onclick="location.reload()">Play Again</button>
555
+ `;
556
+ document.body.appendChild(winScreen);
557
  }
558
 
559
+ getTimeDifference() {
560
+ const startTime = new Date(2024, 0, 1, 20, 0);
561
+ const timeDiff = this.state.time - startTime;
562
+ const minutes = Math.floor(timeDiff / 60000);
563
+ return `${minutes} minutes`;
 
 
 
 
564
  }
565
 
566
+ addMessage(text, type) {
567
+ const chat = document.getElementById('chat-container');
568
+ const messageDiv = document.createElement('div');
569
+ messageDiv.className = `message ${type}`;
570
+ messageDiv.textContent = text;
571
+ chat.appendChild(messageDiv);
572
+ chat.scrollTop = chat.scrollHeight;
573
+ }
574
+
575
+ addLoadingMessage() {
576
+ const chat = document.getElementById('chat-container');
577
+ const loadingDiv = document.createElement('div');
578
+ loadingDiv.className = 'message shyguy typing';
579
+ loadingDiv.id = 'loading-message';
580
+ loadingDiv.textContent = 'Thinking...';
581
+ chat.appendChild(loadingDiv);
582
+ chat.scrollTop = chat.scrollHeight;
583
+ }
584
+
585
+ removeLoadingMessage() {
586
+ const loadingMessage = document.getElementById('loading-message');
587
+ if (loadingMessage) {
588
+ loadingMessage.remove();
589
  }
590
+ }
591
+
592
+ updateStats() {
593
+ document.getElementById('confidence').textContent = this.state.confidence;
594
+ document.getElementById('anxiety').textContent = this.state.anxiety;
595
+ document.getElementById('drinks').textContent = this.state.drinks;
596
+ document.getElementById('time').textContent =
597
+ this.state.time.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
598
+ }
599
+ }
600
+
601
+ let game;
602
+
603
+ function initializeGame() {
604
+ const apiKey = document.getElementById('api-key').value.trim();
605
+ if (!apiKey) {
606
+ alert('Please enter your Mistral API key');
607
+ return;
608
+ }
609
+
610
+ document.getElementById('api-key-container').style.display = 'none';
611
+ document.getElementById('game-content').style.display = 'block';
612
+
613
+ game = new ShyGuySimulator(apiKey);
614
+ }
615
+
616
+ async function handleUserInput() {
617
+ if (!game) return;
618
+
619
+ const input = document.getElementById('user-input');
620
+ const text = input.value.trim();
621
+ if (text) {
622
+ await game.handleInput(text);
623
+ input.value = '';
624
+ }
625
+ }
626
+
627
+ async function move(dx, dy) {
628
+ if (game) {
629
+ await game.movePlayer(dx, dy);
630
+ }
631
+ }
632
+
633
+ // Keyboard controls
634
+ document.addEventListener('keydown', async (e) => {
635
+ if (!game) return;
636
+
637
+ switch(e.key) {
638
+ case 'ArrowUp':
639
+ await move(0, -1);
640
+ break;
641
+ case 'ArrowDown':
642
+ await move(0, 1);
643
+ break;
644
+ case 'ArrowLeft':
645
+ await move(-1, 0);
646
+ break;
647
+ case 'ArrowRight':
648
+ await move(1, 0);
649
+ break;
650
+ }
651
+ });
652
+
653
+ // Enter key for input
654
+ document.getElementById('user-input')?.addEventListener('keypress', function(e) {
655
+ if (e.key === 'Enter') {
656
+ handleUserInput();
657
+ }
658
+ });
659
+ </script>
660
  </body>
661
+ </html>
662
+
663
+