zshashz commited on
Commit
d451979
Β·
1 Parent(s): 8455be2
Files changed (1) hide show
  1. index.html +350 -383
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 - Complete Edition</title>
7
  <style>
8
  body {
9
  font-family: Arial, sans-serif;
@@ -153,14 +153,6 @@
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
  }
@@ -205,7 +197,7 @@
205
  </head>
206
  <body>
207
  <div id="game-container">
208
- <h1>Shy Guy Simulator - Complete Edition</h1>
209
 
210
  <div id="api-key-container">
211
  <input type="password" id="api-key" placeholder="Enter your Mistral API key" style="width: 100%; padding: 10px; margin-bottom: 10px;">
@@ -224,7 +216,7 @@
224
  <div class="chat-side">
225
  <div class="chat-container" id="chat-container"></div>
226
  <div id="input-container">
227
- <input type="text" id="user-input" placeholder="Type your encouragement as wingman...">
228
  <button onclick="handleUserInput()">Send</button>
229
  </div>
230
  </div>
@@ -254,13 +246,6 @@
254
  <span>Sister</span>
255
  </div>
256
  </div>
257
-
258
- <div class="movement-controls">
259
- <button onclick="move(0, -1)">↑</button>
260
- <button onclick="move(-1, 0)">←</button>
261
- <button onclick="move(1, 0)">β†’</button>
262
- <button onclick="move(0, 1)">↓</button>
263
- </div>
264
  </div>
265
  </div>
266
  </div>
@@ -278,10 +263,9 @@
278
  drinks: 0,
279
  time: new Date(2024, 0, 1, 20, 0),
280
  playerPos: { x: 0, y: 9 },
281
- moveSpeed: 1,
282
  isProcessing: false,
283
  hasSpokenToGirl: false,
284
- lastEmotion: 'anxious',
285
  locations: {
286
  bar: [{ x: 8, y: 1 }, { x: 9, y: 1 }],
287
  dj: [{ x: 4, y: 0 }, { x: 5, y: 0 }],
@@ -313,14 +297,13 @@
313
  2. When confidence < 30, prefer to move away or stay still
314
  3. Keep dialogue natural and brief (1-2 sentences)
315
  4. Movement should reflect emotional state
316
- 5. Account for obstacles at: [(3,3), (4,3), (6,6), (7,6)]
317
- 6. Consider locations of: bar(8,1 & 9,1), DJ(4,0 & 5,0), sister(2,5)
318
- Current state: Confidence: ${this.state.confidence}%, Anxiety: ${this.state.anxiety}%`
319
  }];
320
 
321
  this.initialize();
322
  this.initializeGrid();
323
- this.startAutonomousMovement();
324
  }
325
 
326
  initialize() {
@@ -329,398 +312,382 @@
329
  this.updateStats();
330
  }
331
 
332
- startAutonomousMovement() {
333
- this.movementInterval = setInterval(async () => {
334
- if (!this.state.isProcessing && !this.state.hasSpokenToGirl) {
335
- await this.getNextMove();
 
 
336
  }
337
- }, 5000);
338
- }
339
 
340
- stopAutonomousMovement() {
341
- if (this.movementInterval) {
342
- clearInterval(this.movementInterval);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
  }
344
  }
345
 
346
- async getNextMove() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  try {
348
- const response = await this.callMistralAPI();
349
- let parsedResponse;
 
 
 
 
 
 
 
 
 
 
350
 
351
- try {
352
- parsedResponse = JSON.parse(response);
353
- } catch (e) {
354
- const jsonMatch = response.match(/\{[\s\S]*\}/);
355
- if (jsonMatch) {
356
- parsedResponse = JSON.parse(jsonMatch[0]);
357
- } else {
358
- throw new Error('Could not parse LLM response');
359
- }
360
- }
361
 
362
- this.state.lastEmotion = parsedResponse.emotion;
363
- this.addMessage(parsedResponse.dialogue, 'shyguy');
 
 
 
 
 
 
 
 
364
 
365
- if (parsedResponse.movement) {
366
- await this.movePlayer(
367
- parsedResponse.movement.x,
368
- parsedResponse.movement.y
369
- );
370
  }
371
  } catch (error) {
372
- console.error('Error getting next move:', error);
 
 
 
 
373
  }
374
  }
375
-
376
- initializeGrid() {
377
- const grid = document.getElementById('party-grid');
378
- grid.innerHTML = '';
379
-
380
- for (let y = 0; y < 10; y++) {
381
- for (let x = 0; x < 10; x++) {
382
- const cell = document.createElement('div');
383
- cell.className = 'grid-cell';
384
- cell.dataset.x = x;
385
- cell.dataset.y = y;
386
-
387
- if (this.isLocation(x, y, 'bar')) {
388
- cell.classList.add('bar');
389
- cell.dataset.tooltip = "Bar - Get liquid courage";
390
- }
391
- if (this.isLocation(x, y, 'dj')) {
392
- cell.classList.add('dj');
393
- cell.dataset.tooltip = "DJ - Vibe to the music";
394
- }
395
- if (this.isLocation(x, y, 'girl')) {
396
- cell.classList.add('girl');
397
- cell.dataset.tooltip = "The girl you like";
398
- }
399
- if (this.isLocation(x, y, 'sister')) {
400
- cell.classList.add('sister');
401
- cell.dataset.tooltip = "Your sister - Get some encouragement";
402
- }
403
- if (this.isLocation(x, y, 'obstacles')) {
404
- cell.classList.add('obstacle');
405
- cell.dataset.tooltip = "Can't walk here";
406
- }
407
-
408
- if (x === this.state.playerPos.x && y === this.state.playerPos.y) {
409
- const player = document.createElement('div');
410
- player.id = 'player';
411
- if (this.state.drinks >= 3) player.classList.add('drunk-effect');
412
- cell.appendChild(player);
413
- }
414
-
415
- cell.addEventListener('mouseover', this.showTooltip);
416
- cell.addEventListener('mouseout', this.hideTooltip);
417
-
418
- grid.appendChild(cell);
419
- }
420
  }
 
 
421
  }
 
422
 
423
- showTooltip(e) {
424
- const tooltip = document.getElementById('tooltip');
425
- const tooltipText = e.target.dataset.tooltip;
426
-
427
- if (tooltipText) {
428
- tooltip.textContent = tooltipText;
429
- tooltip.style.display = 'block';
430
- tooltip.style.left = e.pageX + 10 + 'px';
431
- tooltip.style.top = e.pageY + 10 + 'px';
432
- }
433
- }
434
 
435
- hideTooltip() {
436
- const tooltip = document.getElementById('tooltip');
437
- tooltip.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
  }
439
 
440
- isLocation(x, y, type) {
441
- return this.state.locations[type].some(pos => pos.x === x && pos.y === y);
 
 
 
442
  }
 
 
 
443
 
444
- async movePlayer(dx, dy) {
445
- if (this.state.isProcessing) return;
446
-
447
- let newX = this.state.playerPos.x + dx * this.state.moveSpeed;
448
- let newY = this.state.playerPos.y + dy * this.state.moveSpeed;
449
-
450
- // Check boundaries
451
- newX = Math.max(0, Math.min(9, newX));
452
- newY = Math.max(0, Math.min(9, newY));
453
-
454
- // Check obstacles
455
- if (this.isLocation(newX, newY, 'obstacles')) return;
456
-
457
- // Random stumble when drunk
458
- if (this.state.drinks >= 3) {
459
- const stumbleChance = (this.state.drinks - 2) * 0.1;
460
- if (Math.random() < stumbleChance) {
461
- const randomDir = Math.random() < 0.5 ? 1 : -1;
462
- if (Math.random() < 0.5) {
463
- newX += randomDir;
464
- } else {
465
- newY += randomDir;
466
- }
467
- newX = Math.max(0, Math.min(9, newX));
468
- newY = Math.max(0, Math.min(9, newY));
469
- }
470
- }
471
- // Update position
472
- this.state.playerPos = { x: newX, y: newY };
473
 
474
- // Check for interactions
475
- if (this.isLocation(newX, newY, 'bar')) {
476
- this.state.drinks++;
477
- this.state.confidence = Math.min(100, this.state.confidence + 15);
478
- this.state.anxiety = Math.max(0, this.state.anxiety - 10);
479
- this.state.moveSpeed = Math.min(2, 1 + this.state.drinks * 0.2);
480
- await this.handleInput("*Takes another drink from the bar*");
481
-
482
- // Add extra stumbling when too drunk
483
- if (this.state.drinks > 5) {
484
- await this.handleInput("*Starting to feel really dizzy...*");
485
- this.state.confidence = Math.max(0, this.state.confidence - 5);
486
- }
487
  }
488
-
489
- if (this.isLocation(newX, newY, 'sister')) {
490
- this.state.confidence = Math.min(100, this.state.confidence + 20);
491
- this.state.anxiety = Math.max(0, this.state.anxiety - 15);
492
- await this.handleInput("*Talks to sister for encouragement*");
493
  }
494
-
495
- if (this.isLocation(newX, newY, 'dj')) {
496
- this.state.confidence = Math.min(100, this.state.confidence + 10);
497
- this.state.anxiety = Math.max(0, this.state.anxiety - 5);
498
- await this.handleInput("*Vibing to the music near the DJ*");
499
  }
500
-
501
- if (this.isLocation(newX, newY, 'girl')) {
502
- if (this.state.confidence >= 70 && this.state.anxiety <= 50) {
503
- await this.handleInput("*Finally gathered the courage to talk to her!*");
504
- this.gameWon();
505
- } else {
506
- await this.handleInput("*Gets too nervous and quickly walks away*");
507
- this.state.playerPos = {
508
- x: Math.max(0, newX - 2),
509
- y: Math.max(0, newY - 2)
510
- };
511
- this.state.anxiety += 15;
512
- this.state.confidence = Math.max(0, this.state.confidence - 10);
513
- }
514
  }
515
-
516
- // Advance time
517
- this.state.time = new Date(this.state.time.getTime() + 2 * 60000); // 2 minutes per move
518
-
519
- this.updateStats();
520
- this.initializeGrid();
521
- }
522
-
523
- async handleInput(userInput) {
524
- if (this.state.isProcessing) return;
525
- this.state.isProcessing = true;
526
-
527
- try {
528
- if (!userInput.trim()) throw new Error("Please enter some text");
529
-
530
- this.addMessage(userInput, 'wingman');
531
- this.addLoadingMessage();
532
-
533
- const currentState = `Current state: Confidence: ${this.state.confidence}%, Anxiety: ${this.state.anxiety}%, Drinks: ${this.state.drinks}, Location: ${this.getLocationDescription()}`;
534
-
535
- this.context.push({
536
- role: 'user',
537
- content: `${userInput}\n\n${currentState}`
538
- });
539
-
540
- const response = await this.callMistralAPI();
541
-
542
- this.removeLoadingMessage();
543
- this.addMessage(response, 'shyguy');
544
-
545
- this.context.push({
546
- role: 'assistant',
547
- content: response
548
- });
549
-
550
- // Keep context manageable
551
- if (this.context.length > 10) {
552
- this.context = [
553
- this.context[0],
554
- ...this.context.slice(-4)
555
- ];
556
- }
557
- } catch (error) {
558
- this.removeLoadingMessage();
559
- this.addMessage(`Error: ${error.message}`, 'error');
560
- console.error('Error:', error);
561
- } finally {
562
- this.state.isProcessing = false;
563
  }
564
- }
565
-
566
- getLocationDescription() {
567
- const { x, y } = this.state.playerPos;
568
- if (this.isLocation(x, y, 'bar')) return 'at the bar';
569
- if (this.isLocation(x, y, 'dj')) return 'near the DJ';
570
- if (this.isLocation(x, y, 'sister')) return 'with sister';
571
- if (this.isLocation(x, y, 'girl')) return 'near the girl';
572
- return 'in the room';
573
- }
574
-
575
- async callMistralAPI() {
576
- try {
577
- const response = await fetch('https://api.mistral.ai/v1/chat/completions', {
578
- method: 'POST',
579
- headers: {
580
- 'Content-Type': 'application/json',
581
- 'Authorization': `Bearer ${this.apiKey}`
582
- },
583
- body: JSON.stringify({
584
- model: 'mistral-large-latest',
585
- messages: this.context,
586
- max_tokens: 150,
587
- temperature: 0.7
588
- })
589
- });
590
-
591
- if (!response.ok) {
592
- const error = await response.json();
593
- throw new Error(error.error?.message || 'API request failed');
594
- }
595
-
596
- const data = await response.json();
597
- return data.choices[0].message.content;
598
- } catch (error) {
599
- if (error.message.includes('API key')) {
600
- throw new Error('Invalid API key. Please check your API key and try again.');
601
- }
602
- throw new Error('Failed to get response from AI. Please try again.');
603
- }
604
- }
605
-
606
- gameWon() {
607
- this.addMessage("Congratulations! You successfully talked to her, and she seems interested! The party wasn't so scary after all.", 'wingman');
608
- this.state.hasSpokenToGirl = true;
609
 
610
- const winScreen = document.createElement('div');
611
- winScreen.className = 'win-screen';
612
- winScreen.innerHTML = `
613
- <h2>You did it!</h2>
614
- <p>Final Stats:</p>
615
- <p>Confidence: ${this.state.confidence}%</p>
616
- <p>Anxiety: ${this.state.anxiety}%</p>
617
- <p>Drinks: ${this.state.drinks}</p>
618
- <p>Time taken: ${this.getTimeDifference()}</p>
619
- <button onclick="location.reload()">Play Again</button>
620
- `;
621
- document.body.appendChild(winScreen);
622
- }
623
-
624
- getTimeDifference() {
625
- const startTime = new Date(2024, 0, 1, 20, 0);
626
- const timeDiff = this.state.time - startTime;
627
- const minutes = Math.floor(timeDiff / 60000);
628
- return `${minutes} minutes`;
629
- }
630
-
631
- addMessage(text, type) {
632
- const chat = document.getElementById('chat-container');
633
- const messageDiv = document.createElement('div');
634
- messageDiv.className = `message ${type}`;
635
- messageDiv.textContent = text;
636
- chat.appendChild(messageDiv);
637
- chat.scrollTop = chat.scrollHeight;
638
- }
639
-
640
- addLoadingMessage() {
641
- const chat = document.getElementById('chat-container');
642
- const loadingDiv = document.createElement('div');
643
- loadingDiv.className = 'message shyguy typing';
644
- loadingDiv.id = 'loading-message';
645
- loadingDiv.textContent = 'Thinking...';
646
- chat.appendChild(loadingDiv);
647
- chat.scrollTop = chat.scrollHeight;
648
- }
649
-
650
- removeLoadingMessage() {
651
- const loadingMessage = document.getElementById('loading-message');
652
- if (loadingMessage) {
653
- loadingMessage.remove();
654
  }
655
- }
656
-
657
- updateStats() {
658
- document.getElementById('confidence').textContent = this.state.confidence;
659
- document.getElementById('anxiety').textContent = this.state.anxiety;
660
- document.getElementById('drinks').textContent = this.state.drinks;
661
- document.getElementById('time').textContent =
662
- this.state.time.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
663
  }
664
  }
 
665
 
666
- let game;
667
-
668
- function initializeGame() {
669
- const apiKey = document.getElementById('api-key').value.trim();
670
- if (!apiKey) {
671
- alert('Please enter your Mistral API key');
672
- return;
673
- }
674
-
675
- document.getElementById('api-key-container').style.display = 'none';
676
- document.getElementById('game-content').style.display = 'block';
677
-
678
- game = new ShyGuySimulator(apiKey);
679
- }
680
-
681
- async function handleUserInput() {
682
- if (!game) return;
683
-
684
- const input = document.getElementById('user-input');
685
- const text = input.value.trim();
686
- if (text) {
687
- await game.handleInput(text);
688
- input.value = '';
689
- }
690
  }
691
-
692
- async function move(dx, dy) {
693
- if (game) {
694
- await game.movePlayer(dx, dy);
695
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
696
  }
697
-
698
- // Keyboard controls
699
- document.addEventListener('keydown', async (e) => {
700
- if (!game) return;
701
-
702
- switch(e.key) {
703
- case 'ArrowUp':
704
- await move(0, -1);
705
- break;
706
- case 'ArrowDown':
707
- await move(0, 1);
708
- break;
709
- case 'ArrowLeft':
710
- await move(-1, 0);
711
- break;
712
- case 'ArrowRight':
713
- await move(1, 0);
714
- break;
715
- }
716
- });
717
-
718
- // Enter key for input
719
- document.getElementById('user-input')?.addEventListener('keypress', function(e) {
720
- if (e.key === 'Enter') {
721
- handleUserInput();
722
- }
723
- });
724
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
725
  </body>
726
  </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 - LLM Edition</title>
7
  <style>
8
  body {
9
  font-family: Arial, sans-serif;
 
153
  gap: 10px;
154
  }
155
 
 
 
 
 
 
 
 
 
156
  .drunk-effect {
157
  animation: wobble 1s infinite;
158
  }
 
197
  </head>
198
  <body>
199
  <div id="game-container">
200
+ <h1>Shy Guy Simulator - LLM Edition</h1>
201
 
202
  <div id="api-key-container">
203
  <input type="password" id="api-key" placeholder="Enter your Mistral API key" style="width: 100%; padding: 10px; margin-bottom: 10px;">
 
216
  <div class="chat-side">
217
  <div class="chat-container" id="chat-container"></div>
218
  <div id="input-container">
219
+ <input type="text" id="user-input" placeholder="Talk to your shy friend...">
220
  <button onclick="handleUserInput()">Send</button>
221
  </div>
222
  </div>
 
246
  <span>Sister</span>
247
  </div>
248
  </div>
 
 
 
 
 
 
 
249
  </div>
250
  </div>
251
  </div>
 
263
  drinks: 0,
264
  time: new Date(2024, 0, 1, 20, 0),
265
  playerPos: { x: 0, y: 9 },
 
266
  isProcessing: false,
267
  hasSpokenToGirl: false,
268
+ emotion: 'anxious',
269
  locations: {
270
  bar: [{ x: 8, y: 1 }, { x: 9, y: 1 }],
271
  dj: [{ x: 4, y: 0 }, { x: 5, y: 0 }],
 
297
  2. When confidence < 30, prefer to move away or stay still
298
  3. Keep dialogue natural and brief (1-2 sentences)
299
  4. Movement should reflect emotional state
300
+ 5. Consider current position and avoid obstacles
301
+ 6. React to encouragement from the wingman
302
+ 7. If near the girl and confidence/drinks are low, move away`
303
  }];
304
 
305
  this.initialize();
306
  this.initializeGrid();
 
307
  }
308
 
309
  initialize() {
 
312
  this.updateStats();
313
  }
314
 
315
+ async processLLMResponse(response) {
316
+ try {
317
+ // Extract the JSON from the response
318
+ const jsonMatch = response.match(/\{[\s\S]*\}/);
319
+ if (!jsonMatch) {
320
+ throw new Error("Invalid response format");
321
  }
 
 
322
 
323
+ const parsedResponse = JSON.parse(jsonMatch[0]);
324
+
325
+ // Validate the response format
326
+ if (!parsedResponse.dialogue || !parsedResponse.movement || !parsedResponse.emotion) {
327
+ throw new Error("Missing required fields in response");
328
+ }
329
+
330
+ // Store the emotion state
331
+ this.state.emotion = parsedResponse.emotion;
332
+
333
+ // Apply the movement
334
+ await this.movePlayer(
335
+ parsedResponse.movement.x,
336
+ parsedResponse.movement.y
337
+ );
338
+
339
+ // Update player state based on emotion
340
+ this.updateEmotionalState(parsedResponse.emotion);
341
+
342
+ return parsedResponse.dialogue;
343
+ } catch (error) {
344
+ console.error("Error processing LLM response:", error);
345
+ return "I'm not sure what to do right now...";
346
  }
347
  }
348
 
349
+ updateEmotionalState(emotion) {
350
+ const emotionEffects = {
351
+ 'anxious': { confidence: -5, anxiety: 5 },
352
+ 'nervous': { confidence: -2, anxiety: 2 },
353
+ 'slightly_confident': { confidence: 5, anxiety: -5 },
354
+ 'confident': { confidence: 10, anxiety: -10 }
355
+ };
356
+
357
+ const effect = emotionEffects[emotion] || { confidence: 0, anxiety: 0 };
358
+
359
+ this.state.confidence = Math.max(0, Math.min(100, this.state.confidence + effect.confidence));
360
+ this.state.anxiety = Math.max(0, Math.min(100, this.state.anxiety + effect.anxiety));
361
+ }
362
+
363
+ async handleInput(userInput) {
364
+ if (this.state.isProcessing) return;
365
+ this.state.isProcessing = true;
366
+
367
  try {
368
+ if (!userInput.trim()) throw new Error("Please enter some text");
369
+
370
+ this.addMessage(userInput, 'wingman');
371
+ this.addLoadingMessage();
372
+
373
+ const currentState = `Current state:
374
+ Confidence: ${this.state.confidence}%,
375
+ Anxiety: ${this.state.anxiety}%,
376
+ Drinks: ${this.state.drinks},
377
+ Location: ${this.getLocationDescription()},
378
+ Position: (${this.state.playerPos.x},${this.state.playerPos.y}),
379
+ Emotion: ${this.state.emotion}`;
380
 
381
+ this.context.push({
382
+ role: 'user',
383
+ content: `${userInput}\n\n${currentState}`
384
+ });
 
 
 
 
 
 
385
 
386
+ const llmResponse = await this.callMistralAPI();
387
+ const dialogue = await this.processLLMResponse(llmResponse);
388
+
389
+ this.removeLoadingMessage();
390
+ this.addMessage(dialogue, 'shyguy');
391
+
392
+ this.context.push({
393
+ role: 'assistant',
394
+ content: llmResponse
395
+ });
396
 
397
+ if (this.context.length > 10) {
398
+ this.context = [
399
+ this.context[0],
400
+ ...this.context.slice(-4)
401
+ ];
402
  }
403
  } catch (error) {
404
+ this.removeLoadingMessage();
405
+ this.addMessage(`Error: ${error.message}`, 'error');
406
+ console.error('Error:', error);
407
+ } finally {
408
+ this.state.isProcessing = false;
409
  }
410
  }
411
+
412
+ async movePlayer(dx, dy) {
413
+ let newX = this.state.playerPos.x + dx;
414
+ let newY = this.state.playerPos.y + dy;
415
+
416
+ // Check boundaries
417
+ newX = Math.max(0, Math.min(9, newX));
418
+ newY = Math.max(0, Math.min(9, newY));
419
+
420
+ // Check obstacles
421
+ if (this.isLocation(newX, newY, 'obstacles')) return;
422
+
423
+ // Random stumble when drunk
424
+ if (this.state.drinks >= 3) {
425
+ const stumbleChance = (this.state.drinks - 2) * 0.1;
426
+ if (Math.random() < stumbleChance) {
427
+ const randomDir = Math.random() < 0.5 ? 1 : -1;
428
+ if (Math.random() < 0.5) {
429
+ newX += randomDir;
430
+ } else {
431
+ newY += randomDir;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
432
  }
433
+ newX = Math.max(0, Math.min(9, newX));
434
+ newY = Math.max(0, Math.min(9, newY));
435
  }
436
+ }
437
 
438
+ // Update position
439
+ this.state.playerPos = { x: newX, y: newY };
440
+
441
+ // Handle location interactions
442
+ await this.handleLocationInteraction(newX, newY);
 
 
 
 
 
 
443
 
444
+ // Advance time
445
+ this.state.time = new Date(this.state.time.getTime() + 2 * 60000); // 2 minutes per move
446
+
447
+ this.updateStats();
448
+ this.initializeGrid();
449
+ }
450
+
451
+ async handleLocationInteraction(x, y) {
452
+ if (this.isLocation(x, y, 'bar')) {
453
+ this.state.drinks++;
454
+ this.state.confidence = Math.min(100, this.state.confidence + 15);
455
+ this.state.anxiety = Math.max(0, this.state.anxiety - 10);
456
+ await this.addMessage("*Takes another drink from the bar*", 'shyguy');
457
+
458
+ if (this.state.drinks > 5) {
459
+ this.state.confidence = Math.max(0, this.state.confidence - 5);
460
+ await this.addMessage("*Starting to feel really dizzy...*", 'shyguy');
461
+ }
462
+ }
463
+
464
+ if (this.isLocation(x, y, 'sister')) {
465
+ this.state.confidence = Math.min(100, this.state.confidence + 20);
466
+ this.state.anxiety = Math.max(0, this.state.anxiety - 15);
467
+ await this.addMessage("*Gets some encouragement from sister*", 'shyguy');
468
+ }
469
+
470
+ if (this.isLocation(x, y, 'dj')) {
471
+ this.state.confidence = Math.min(100, this.state.confidence + 10);
472
+ this.state.anxiety = Math.max(0, this.state.anxiety - 5);
473
+ await this.addMessage("*Vibing to the music*", 'shyguy');
474
+ }
475
+
476
+ if (this.isLocation(x, y, 'girl')) {
477
+ if (this.state.confidence >= 70 && this.state.anxiety <= 50) {
478
+ await this.addMessage("*Finally gathered the courage to talk to her!*", 'shyguy');
479
+ this.gameWon();
480
+ } else {
481
+ await this.addMessage("*Gets too nervous and quickly walks away*", 'shyguy');
482
+ this.state.playerPos = {
483
+ x: Math.max(0, x - 2),
484
+ y: Math.max(0, y - 2)
485
+ };
486
+ this.state.anxiety += 15;
487
+ this.state.confidence = Math.max(0, this.state.confidence - 10);
488
+ }
489
+ }
490
+ }
491
+
492
+ async callMistralAPI() {
493
+ try {
494
+ const response = await fetch('https://api.mistral.ai/v1/chat/completions', {
495
+ method: 'POST',
496
+ headers: {
497
+ 'Content-Type': 'application/json',
498
+ 'Authorization': `Bearer ${this.apiKey}`
499
+ },
500
+ body: JSON.stringify({
501
+ model: 'mistral-large-latest',
502
+ messages: this.context,
503
+ max_tokens: 150,
504
+ temperature: 0.7
505
+ })
506
+ });
507
+
508
+ if (!response.ok) {
509
+ const error = await response.json();
510
+ throw new Error(error.error?.message || 'API request failed');
511
  }
512
 
513
+ const data = await response.json();
514
+ return data.choices[0].message.content;
515
+ } catch (error) {
516
+ if (error.message.includes('API key')) {
517
+ throw new Error('Invalid API key. Please check your API key and try again.');
518
  }
519
+ throw new Error('Failed to get response from AI. Please try again.');
520
+ }
521
+ }
522
 
523
+ initializeGrid() {
524
+ const grid = document.getElementById('party-grid');
525
+ grid.innerHTML = '';
526
+
527
+ for (let y = 0; y < 10; y++) {
528
+ for (let x = 0; x < 10; x++) {
529
+ const cell = document.createElement('div');
530
+ cell.className = 'grid-cell';
531
+ cell.dataset.x = x;
532
+ cell.dataset.y = y;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
533
 
534
+ if (this.isLocation(x, y, 'bar')) {
535
+ cell.classList.add('bar');
536
+ cell.dataset.tooltip = "Bar - Get liquid courage";
 
 
 
 
 
 
 
 
 
 
537
  }
538
+ if (this.isLocation(x, y, 'dj')) {
539
+ cell.classList.add('dj');
540
+ cell.dataset.tooltip = "DJ - Vibe to the music";
 
 
541
  }
542
+ if (this.isLocation(x, y, 'girl')) {
543
+ cell.classList.add('girl');
544
+ cell.dataset.tooltip = "The girl you like";
 
 
545
  }
546
+ if (this.isLocation(x, y, 'sister')) {
547
+ cell.classList.add('sister');
548
+ cell.dataset.tooltip = "Your sister - Get some encouragement";
 
 
 
 
 
 
 
 
 
 
 
549
  }
550
+ if (this.isLocation(x, y, 'obstacles')) {
551
+ cell.classList.add('obstacle');
552
+ cell.dataset.tooltip = "Can't walk here";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
553
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
554
 
555
+ if (x === this.state.playerPos.x && y === this.state.playerPos.y) {
556
+ const player = document.createElement('div');
557
+ player.id = 'player';
558
+ if (this.state.drinks >= 3) player.classList.add('drunk-effect');
559
+ cell.appendChild(player);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
  }
561
+
562
+ cell.addEventListener('mouseover', this.showTooltip);
563
+ cell.addEventListener('mouseout', this.hideTooltip);
564
+
565
+ grid.appendChild(cell);
 
 
 
566
  }
567
  }
568
+ }
569
 
570
+ showTooltip(e) {
571
+ const tooltip = document.getElementById('tooltip');
572
+ const tooltipText = e.target.dataset.tooltip;
573
+
574
+ if (tooltipText) {
575
+ tooltip.textContent = tooltipText;
576
+ tooltip.style.display = 'block';
577
+ tooltip.style.left = e.pageX + 10 + 'px';
578
+ tooltip.style.top = e.pageY + 10 + 'px';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
579
  }
580
+ }
581
+
582
+ hideTooltip() {
583
+ const tooltip = document.getElementById('tooltip');
584
+ tooltip.style.display = 'none';
585
+ }
586
+
587
+ isLocation(x, y, type) {
588
+ return this.state.locations[type].some(pos => pos.x === x && pos.y === y);
589
+ }
590
+
591
+ getLocationDescription() {
592
+ const { x, y } = this.state.playerPos;
593
+ if (this.isLocation(x, y, 'bar')) return 'at the bar';
594
+ if (this.isLocation(x, y, 'dj')) return 'near the DJ';
595
+ if (this.isLocation(x, y, 'sister')) return 'with sister';
596
+ if (this.isLocation(x, y, 'girl')) return 'near the girl';
597
+ return 'in the room';
598
+ }
599
+
600
+ gameWon() {
601
+ this.addMessage("Congratulations! You successfully talked to her, and she seems interested! The party wasn't so scary after all.", 'wingman');
602
+ this.state.hasSpokenToGirl = true;
603
+
604
+ const winScreen = document.createElement('div');
605
+ winScreen.className = 'win-screen';
606
+ winScreen.innerHTML = `
607
+ <h2>You did it!</h2>
608
+ <p>Final Stats:</p>
609
+ <p>Confidence: ${this.state.confidence}%</p>
610
+ <p>Anxiety: ${this.state.anxiety}%</p>
611
+ <p>Drinks: ${this.state.drinks}</p>
612
+ <p>Time taken: ${this.getTimeDifference()}</p>
613
+ <button onclick="location.reload()">Play Again</button>
614
+ `;
615
+ document.body.appendChild(winScreen);
616
+ }
617
+
618
+ getTimeDifference() {
619
+ const startTime = new Date(2024, 0, 1, 20, 0);
620
+ const timeDiff = this.state.time - startTime;
621
+ const minutes = Math.floor(timeDiff / 60000);
622
+ return `${minutes} minutes`;
623
+ }
624
+
625
+ addMessage(text, type) {
626
+ const chat = document.getElementById('chat-container');
627
+ const messageDiv = document.createElement('div');
628
+ messageDiv.className = `message ${type}`;
629
+ messageDiv.textContent = text;
630
+ chat.appendChild(messageDiv);
631
+ chat.scrollTop = chat.scrollHeight;
632
+ }
633
+
634
+ addLoadingMessage() {
635
+ const chat = document.getElementById('chat-container');
636
+ const loadingDiv = document.createElement('div');
637
+ loadingDiv.className = 'message shyguy typing';
638
+ loadingDiv.id = 'loading-message';
639
+ loadingDiv.textContent = 'Thinking...';
640
+ chat.appendChild(loadingDiv);
641
+ chat.scrollTop = chat.scrollHeight;
642
+ }
643
+
644
+ removeLoadingMessage() {
645
+ const loadingMessage = document.getElementById('loading-message');
646
+ if (loadingMessage) {
647
+ loadingMessage.remove();
648
  }
649
+ }
650
+
651
+ updateStats() {
652
+ document.getElementById('confidence').textContent = this.state.confidence;
653
+ document.getElementById('anxiety').textContent = this.state.anxiety;
654
+ document.getElementById('drinks').textContent = this.state.drinks;
655
+ document.getElementById('time').textContent =
656
+ this.state.time.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
657
+ }
658
+ }
659
+
660
+ let game;
661
+
662
+ function initializeGame() {
663
+ const apiKey = document.getElementById('api-key').value.trim();
664
+ if (!apiKey) {
665
+ alert('Please enter your Mistral API key');
666
+ return;
667
+ }
668
+
669
+ document.getElementById('api-key-container').style.display = 'none';
670
+ document.getElementById('game-content').style.display = 'block';
671
+
672
+ game = new ShyGuySimulator(apiKey);
673
+ }
674
+
675
+ async function handleUserInput() {
676
+ if (!game) return;
677
+
678
+ const input = document.getElementById('user-input');
679
+ const text = input.value.trim();
680
+ if (text) {
681
+ await game.handleInput(text);
682
+ input.value = '';
683
+ }
684
+ }
685
+
686
+ document.getElementById('user-input')?.addEventListener('keypress', function(e) {
687
+ if (e.key === 'Enter') {
688
+ handleUserInput();
689
+ }
690
+ });
691
+ </script>
692
  </body>
693
  </html>