zshashz commited on
Commit
d0d2794
·
1 Parent(s): 02d35b9
Files changed (1) hide show
  1. index.html +363 -357
index.html CHANGED
@@ -189,6 +189,18 @@
189
  z-index: 100;
190
  display: none;
191
  }
 
 
 
 
 
 
 
 
 
 
 
 
192
  </style>
193
  </head>
194
  <body>
@@ -257,406 +269,400 @@
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
-
409
- // Check for interactions
410
- if (this.isLocation(newX, newY, 'bar')) {
411
- this.state.drinks++;
412
- this.state.confidence = Math.min(100, this.state.confidence + 15);
413
- this.state.anxiety = Math.max(0, this.state.anxiety - 10);
414
- this.state.moveSpeed = Math.min(2, 1 + this.state.drinks * 0.2);
415
- await this.handleInput("*Takes another drink from the bar*");
416
  }
417
-
418
- if (this.isLocation(newX, newY, 'sister')) {
419
- this.state.confidence = Math.min(100, this.state.confidence + 20);
420
- this.state.anxiety = Math.max(0, this.state.anxiety - 15);
421
- await this.handleInput("*Talks to sister for encouragement*");
422
- }
423
-
424
- if (this.isLocation(newX, newY, 'dj')) {
425
- this.state.confidence = Math.min(100, this.state.confidence + 10);
426
- this.state.anxiety = Math.max(0, this.state.anxiety - 5);
427
- await this.handleInput("*Vibing to the music near the DJ*");
428
- }
429
-
430
- if (this.isLocation(newX, newY, 'girl')) {
431
- if (this.state.confidence >= 70) {
432
- await this.handleInput("*Finally gathered the courage to talk to her!*");
433
- this.gameWon();
434
- } else {
435
- await this.handleInput("*Gets too nervous and quickly walks away*");
436
- this.state.playerPos = {
437
- x: Math.max(0, newX - 2),
438
- y: Math.max(0, newY - 2)
439
- };
440
- this.state.anxiety += 15;
441
- this.state.confidence = Math.max(0, this.state.confidence - 10);
 
 
 
 
442
  }
443
  }
444
 
445
- this.updateStats();
446
- this.initializeGrid();
447
- }
448
-
449
- async handleInput(userInput) {
450
- if (this.state.isProcessing) return;
451
- this.state.isProcessing = true;
452
-
453
- try {
454
- if (!userInput.trim()) throw new Error("Please enter some text");
455
-
456
- this.addMessage(userInput, 'wingman');
457
- this.addLoadingMessage();
458
-
459
- const currentState = `Current state: Confidence: ${this.state.confidence}%, Anxiety: ${this.state.anxiety}%, Drinks: ${this.state.drinks}, Location: ${this.getLocationDescription()}`;
460
 
461
- this.context.push({
462
- role: 'user',
463
- content: `${userInput}\n\n${currentState}`
464
- });
 
 
 
 
 
 
 
 
 
465
 
466
- const response = await this.callMistralAPI();
467
-
468
- this.removeLoadingMessage();
469
- this.addMessage(response, 'shyguy');
470
-
471
- this.context.push({
472
- role: 'assistant',
473
- content: response
474
- });
475
-
476
- // Keep context manageable
477
- if (this.context.length > 10) {
478
- this.context = [
479
- this.context[0],
480
- ...this.context.slice(-4)
481
- ];
482
- }
483
- } catch (error) {
484
- this.removeLoadingMessage();
485
- this.addMessage(`Error: ${error.message}`, 'error');
486
- console.error('Error:', error);
487
- } finally {
488
- this.state.isProcessing = false;
489
  }
490
- }
491
 
492
- getLocationDescription() {
493
- const { x, y } = this.state.playerPos;
494
- if (this.isLocation(x, y, 'bar')) return 'at the bar';
495
- if (this.isLocation(x, y, 'dj')) return 'near the DJ';
496
- if (this.isLocation(x, y, 'sister')) return 'with sister';
497
- if (this.isLocation(x, y, 'girl')) return 'near the girl';
498
- return 'in the room';
499
- }
500
 
501
- async callMistralAPI() {
502
- try {
503
- const response = await fetch('https://api.mistral.ai/v1/chat/completions', {
504
- method: 'POST',
505
- headers: {
506
- 'Content-Type': 'application/json',
507
- 'Authorization': `Bearer ${this.apiKey}`
508
- },
509
- body: JSON.stringify({
510
- model: 'mistral-large-latest',
511
- messages: this.context,
512
- max_tokens: 150,
513
- temperature: 0.7
514
- })
515
- });
516
-
517
- if (!response.ok) {
518
- const error = await response.json();
519
- throw new Error(error.error?.message || 'API request failed');
520
- }
521
 
522
- const data = await response.json();
523
- return data.choices[0].message.content;
524
- } catch (error) {
525
- if (error.message.includes('API key')) {
526
- throw new Error('Invalid API key. Please check your API key and try again.');
527
  }
528
- throw new Error('Failed to get response from AI. Please try again.');
529
  }
530
- }
531
-
532
- gameWon() {
533
- this.addMessage("Congratulations! You successfully talked to her, and she seems interested! The party wasn't so scary after all.", 'wingman');
534
- this.state.hasSpokenToGirl = true;
535
-
536
- // Create win screen
537
- const winScreen = document.createElement('div');
538
- winScreen.style.position = 'fixed';
539
- winScreen.style.top = '50%';
540
- winScreen.style.left = '50%';
541
- winScreen.style.transform = 'translate(-50%, -50%)';
542
- winScreen.style.background = 'rgba(0, 0, 0, 0.9)';
543
- winScreen.style.padding = '20px';
544
- winScreen.style.borderRadius = '10px';
545
- winScreen.style.textAlign = 'center';
546
- winScreen.innerHTML = `
547
- <h2>You did it!</h2>
548
- <p>Final Stats:</p>
549
- <p>Confidence: ${this.state.confidence}%</p>
550
- <p>Anxiety: ${this.state.anxiety}%</p>
551
- <p>Drinks: ${this.state.drinks}</p>
552
- <p>Time taken: ${this.getTimeDifference()}</p>
553
- <button onclick="location.reload()">Play Again</button>
554
- `;
555
- document.body.appendChild(winScreen);
556
- }
557
-
558
- getTimeDifference() {
559
- const startTime = new Date(2024, 0, 1, 20, 0);
560
- const timeDiff = this.state.time - startTime;
561
- const minutes = Math.floor(timeDiff / 60000);
562
- return `${minutes} minutes`;
563
- }
564
-
565
- addMessage(text, type) {
566
- const chat = document.getElementById('chat-container');
567
- const messageDiv = document.createElement('div');
568
- messageDiv.className = `message ${type}`;
569
- messageDiv.textContent = text;
570
- chat.appendChild(messageDiv);
571
- chat.scrollTop = chat.scrollHeight;
572
- }
573
 
574
- addLoadingMessage() {
575
- const chat = document.getElementById('chat-container');
576
- const loadingDiv = document.createElement('div');
577
- loadingDiv.className = 'message shyguy typing';
578
- loadingDiv.id = 'loading-message';
579
- loadingDiv.textContent = 'Thinking...';
580
- chat.appendChild(loadingDiv);
581
- chat.scrollTop = chat.scrollHeight;
582
- }
583
-
584
- removeLoadingMessage() {
585
- const loadingMessage = document.getElementById('loading-message');
586
- if (loadingMessage) {
587
- loadingMessage.remove();
588
  }
589
  }
590
 
591
- updateStats() {
592
- document.getElementById('confidence').textContent = this.state.confidence;
593
- document.getElementById('anxiety').textContent = this.state.anxiety;
594
- document.getElementById('drinks').textContent = this.state.drinks;
595
- document.getElementById('time').textContent =
596
- this.state.time.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
597
- }
598
- }
599
 
600
- let game;
 
 
 
 
 
601
 
602
- function initializeGame() {
603
- const apiKey = document.getElementById('api-key').value.trim();
604
- if (!apiKey) {
605
- alert('Please enter your Mistral API key');
606
- return;
607
  }
608
 
609
- document.getElementById('api-key-container').style.display = 'none';
610
- document.getElementById('game-content').style.display = 'block';
611
-
612
- game = new ShyGuySimulator(apiKey);
613
- }
614
-
615
- async function handleUserInput() {
616
- if (!game) return;
617
-
618
- const input = document.getElementById('user-input');
619
- const text = input.value.trim();
620
- if (text) {
621
- await game.handleInput(text);
622
- input.value = '';
623
  }
624
- }
625
 
626
- async function move(dx, dy) {
627
- if (game) {
628
- await game.movePlayer(dx, dy);
 
629
  }
630
- }
631
 
632
- // Keyboard controls
633
- document.addEventListener('keydown', async (e) => {
634
- if (!game) return;
635
-
636
- switch(e.key) {
637
- case 'ArrowUp':
638
- await move(0, -1);
639
- break;
640
- case 'ArrowDown':
641
- await move(0, 1);
642
- break;
643
- case 'ArrowLeft':
644
- await move(-1, 0);
645
- break;
646
- case 'ArrowRight':
647
- await move(1, 0);
648
- break;
649
- }
650
- });
651
 
652
- // Enter key for input
653
- document.getElementById('user-input')?.addEventListener('keypress', function(e) {
654
- if (e.key === 'Enter') {
655
- handleUserInput();
656
- }
657
- });
658
- </script>
659
  </body>
660
- </html>
661
-
662
-
 
189
  z-index: 100;
190
  display: none;
191
  }
192
+
193
+ .win-screen {
194
+ position: fixed;
195
+ top: 50%;
196
+ left: 50%;
197
+ transform: translate(-50%, -50%);
198
+ background: rgba(0, 0, 0, 0.9);
199
+ padding: 20px;
200
+ border-radius: 10px;
201
+ text-align: center;
202
+ z-index: 1000;
203
+ }
204
  </style>
205
  </head>
206
  <body>
 
269
  <div class="tooltip" id="tooltip"></div>
270
 
271
  <script>
272
+ class ShyGuySimulator {
273
+ constructor(apiKey) {
274
+ this.apiKey = apiKey;
275
+ this.state = {
276
+ confidence: 0,
277
+ anxiety: 100,
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
+ locations: {
285
+ bar: [{ x: 8, y: 1 }, { x: 9, y: 1 }],
286
+ dj: [{ x: 4, y: 0 }, { x: 5, y: 0 }],
287
+ girl: [{ x: 9, y: 8 }],
288
+ sister: [{ x: 2, y: 5 }],
289
+ obstacles: [
290
+ { x: 3, y: 3 }, { x: 4, y: 3 },
291
+ { x: 6, y: 6 }, { x: 7, y: 6 }
292
+ ]
293
+ }
294
+ };
295
+
296
+ this.context = [
297
+ {
298
+ role: 'system',
299
+ content: `You are roleplaying as a shy and anxious guy at a homecoming party.
300
+ You're standing near the entrance, and the girl you like is across the room.
301
+ Your responses should reflect your social anxiety and reluctance to approach her.
302
+ Keep responses concise (max 2-3 sentences) and natural.
303
+ Express hesitation, worry, and self-doubt while reacting to the wingman's encouragement.
304
+ Current state: Confidence: ${this.state.confidence}%, Anxiety: ${this.state.anxiety}%`
305
+ }
306
+ ];
307
+
308
+ this.initialize();
309
+ this.initializeGrid();
310
  }
 
 
 
 
 
311
 
312
+ initialize() {
313
+ 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');
314
+ this.addMessage("I... I don't know about this. Maybe I should just go home...", 'shyguy');
315
+ this.updateStats();
316
+ }
317
 
318
+ initializeGrid() {
319
+ const grid = document.getElementById('party-grid');
320
+ grid.innerHTML = '';
 
 
 
 
 
 
 
321
 
322
+ for (let y = 0; y < 10; y++) {
323
+ for (let x = 0; x < 10; x++) {
324
+ const cell = document.createElement('div');
325
+ cell.className = 'grid-cell';
326
+ cell.dataset.x = x;
327
+ cell.dataset.y = y;
328
+
329
+ if (this.isLocation(x, y, 'bar')) {
330
+ cell.classList.add('bar');
331
+ cell.dataset.tooltip = "Bar - Get liquid courage";
332
+ }
333
+ if (this.isLocation(x, y, 'dj')) {
334
+ cell.classList.add('dj');
335
+ cell.dataset.tooltip = "DJ - Vibe to the music";
336
+ }
337
+ if (this.isLocation(x, y, 'girl')) {
338
+ cell.classList.add('girl');
339
+ cell.dataset.tooltip = "The girl you like";
340
+ }
341
+ if (this.isLocation(x, y, 'sister')) {
342
+ cell.classList.add('sister');
343
+ cell.dataset.tooltip = "Your sister - Get some encouragement";
344
+ }
345
+ if (this.isLocation(x, y, 'obstacles')) {
346
+ cell.classList.add('obstacle');
347
+ cell.dataset.tooltip = "Can't walk here";
348
+ }
349
+
350
+ if (x === this.state.playerPos.x && y === this.state.playerPos.y) {
351
+ const player = document.createElement('div');
352
+ player.id = 'player';
353
+ if (this.state.drinks >= 3) player.classList.add('drunk-effect');
354
+ cell.appendChild(player);
355
+ }
356
+
357
+ cell.addEventListener('mouseover', this.showTooltip);
358
+ cell.addEventListener('mouseout', this.hideTooltip);
359
+
360
+ grid.appendChild(cell);
361
+ }
362
  }
363
+ }
364
+
365
+ showTooltip(e) {
366
+ const tooltip = document.getElementById('tooltip');
367
+ const tooltipText = e.target.dataset.tooltip;
368
+
369
+ if (tooltipText) {
370
+ tooltip.textContent = tooltipText;
371
+ tooltip.style.display = 'block';
372
+ tooltip.style.left = e.pageX + 10 + 'px';
373
+ tooltip.style.top = e.pageY + 10 + 'px';
374
  }
375
+ }
376
+
377
+ hideTooltip() {
378
+ const tooltip = document.getElementById('tooltip');
379
+ tooltip.style.display = 'none';
380
+ }
381
+
382
+ isLocation(x, y, type) {
383
+ return this.state.locations[type].some(pos => pos.x === x && pos.y === y);
384
+ }
385
+
386
+ async movePlayer(dx, dy) {
387
+ if (this.state.isProcessing) return;
388
+
389
+ let newX = this.state.playerPos.x + dx * this.state.moveSpeed;
390
+ let newY = this.state.playerPos.y + dy * this.state.moveSpeed;
391
+
392
+ // Check boundaries
393
+ newX = Math.max(0, Math.min(9, newX));
394
+ newY = Math.max(0, Math.min(9, newY));
395
+
396
+ // Check obstacles
397
+ if (this.isLocation(newX, newY, 'obstacles')) return;
398
+
399
+ // Random stumble when drunk
400
+ if (this.state.drinks >= 3) {
401
+ const stumbleChance = (this.state.drinks - 2) * 0.1;
402
+ if (Math.random() < stumbleChance) {
403
+ const randomDir = Math.random() < 0.5 ? 1 : -1;
404
+ if (Math.random() < 0.5) {
405
+ newX += randomDir;
406
+ } else {
407
+ newY += randomDir;
408
+ }
409
+ newX = Math.max(0, Math.min(9, newX));
410
+ newY = Math.max(0, Math.min(9, newY));
411
+ }
412
+ }
413
+ // Update position
414
+ this.state.playerPos = { x: newX, y: newY };
415
+
416
+ // Check for interactions
417
+ if (this.isLocation(newX, newY, 'bar')) {
418
+ this.state.drinks++;
419
+ this.state.confidence = Math.min(100, this.state.confidence + 15);
420
+ this.state.anxiety = Math.max(0, this.state.anxiety - 10);
421
+ this.state.moveSpeed = Math.min(2, 1 + this.state.drinks * 0.2);
422
+ await this.handleInput("*Takes another drink from the bar*");
423
+
424
+ // Add extra stumbling when too drunk
425
+ if (this.state.drinks > 5) {
426
+ await this.handleInput("*Starting to feel really dizzy...*");
427
+ this.state.confidence = Math.max(0, this.state.confidence - 5);
428
+ }
429
  }
430
+
431
+ if (this.isLocation(newX, newY, 'sister')) {
432
+ this.state.confidence = Math.min(100, this.state.confidence + 20);
433
+ this.state.anxiety = Math.max(0, this.state.anxiety - 15);
434
+ await this.handleInput("*Talks to sister for encouragement*");
435
  }
436
 
437
+ if (this.isLocation(newX, newY, 'dj')) {
438
+ this.state.confidence = Math.min(100, this.state.confidence + 10);
439
+ this.state.anxiety = Math.max(0, this.state.anxiety - 5);
440
+ await this.handleInput("*Vibing to the music near the DJ*");
 
441
  }
442
 
443
+ if (this.isLocation(newX, newY, 'girl')) {
444
+ if (this.state.confidence >= 70 && this.state.anxiety <= 50) {
445
+ await this.handleInput("*Finally gathered the courage to talk to her!*");
446
+ this.gameWon();
447
+ } else {
448
+ await this.handleInput("*Gets too nervous and quickly walks away*");
449
+ this.state.playerPos = {
450
+ x: Math.max(0, newX - 2),
451
+ y: Math.max(0, newY - 2)
452
+ };
453
+ this.state.anxiety += 15;
454
+ this.state.confidence = Math.max(0, this.state.confidence - 10);
455
+ }
456
+ }
457
+
458
+ // Advance time
459
+ this.state.time = new Date(this.state.time.getTime() + 2 * 60000); // 2 minutes per move
460
 
461
+ this.updateStats();
462
+ this.initializeGrid();
463
  }
 
 
464
 
465
+ async handleInput(userInput) {
466
+ if (this.state.isProcessing) return;
467
+ this.state.isProcessing = true;
 
 
 
 
 
 
 
 
468
 
469
+ try {
470
+ if (!userInput.trim()) throw new Error("Please enter some text");
 
 
471
 
472
+ this.addMessage(userInput, 'wingman');
473
+ this.addLoadingMessage();
 
474
 
475
+ const currentState = `Current state: Confidence: ${this.state.confidence}%, Anxiety: ${this.state.anxiety}%, Drinks: ${this.state.drinks}, Location: ${this.getLocationDescription()}`;
476
+
477
+ this.context.push({
478
+ role: 'user',
479
+ content: `${userInput}\n\n${currentState}`
480
+ });
481
+
482
+ const response = await this.callMistralAPI();
483
+
484
+ this.removeLoadingMessage();
485
+ this.addMessage(response, 'shyguy');
486
+
487
+ this.context.push({
488
+ role: 'assistant',
489
+ content: response
490
+ });
491
+
492
+ // Keep context manageable
493
+ if (this.context.length > 10) {
494
+ this.context = [
495
+ this.context[0],
496
+ ...this.context.slice(-4)
497
+ ];
498
+ }
499
+ } catch (error) {
500
+ this.removeLoadingMessage();
501
+ this.addMessage(`Error: ${error.message}`, 'error');
502
+ console.error('Error:', error);
503
+ } finally {
504
+ this.state.isProcessing = false;
505
  }
 
 
506
  }
 
507
 
508
+ getLocationDescription() {
509
+ const { x, y } = this.state.playerPos;
510
+ if (this.isLocation(x, y, 'bar')) return 'at the bar';
511
+ if (this.isLocation(x, y, 'dj')) return 'near the DJ';
512
+ if (this.isLocation(x, y, 'sister')) return 'with sister';
513
+ if (this.isLocation(x, y, 'girl')) return 'near the girl';
514
+ return 'in the room';
 
 
 
 
 
515
  }
516
+
517
+ async callMistralAPI() {
518
+ try {
519
+ const response = await fetch('https://api.mistral.ai/v1/chat/completions', {
520
+ method: 'POST',
521
+ headers: {
522
+ 'Content-Type': 'application/json',
523
+ 'Authorization': `Bearer ${this.apiKey}`
524
+ },
525
+ body: JSON.stringify({
526
+ model: 'mistral-large-latest',
527
+ messages: this.context,
528
+ max_tokens: 150,
529
+ temperature: 0.7
530
+ })
531
+ });
532
+
533
+ if (!response.ok) {
534
+ const error = await response.json();
535
+ throw new Error(error.error?.message || 'API request failed');
536
+ }
537
+
538
+ const data = await response.json();
539
+ return data.choices[0].message.content;
540
+ } catch (error) {
541
+ if (error.message.includes('API key')) {
542
+ throw new Error('Invalid API key. Please check your API key and try again.');
543
+ }
544
+ throw new Error('Failed to get response from AI. Please try again.');
545
  }
546
  }
547
 
548
+ gameWon() {
549
+ this.addMessage("Congratulations! You successfully talked to her, and she seems interested! The party wasn't so scary after all.", 'wingman');
550
+ this.state.hasSpokenToGirl = true;
 
 
 
 
 
 
 
 
 
 
 
 
551
 
552
+ const winScreen = document.createElement('div');
553
+ winScreen.className = 'win-screen';
554
+ winScreen.innerHTML = `
555
+ <h2>You did it!</h2>
556
+ <p>Final Stats:</p>
557
+ <p>Confidence: ${this.state.confidence}%</p>
558
+ <p>Anxiety: ${this.state.anxiety}%</p>
559
+ <p>Drinks: ${this.state.drinks}</p>
560
+ <p>Time taken: ${this.getTimeDifference()}</p>
561
+ <button onclick="location.reload()">Play Again</button>
562
+ `;
563
+ document.body.appendChild(winScreen);
564
+ }
565
 
566
+ getTimeDifference() {
567
+ const startTime = new Date(2024, 0, 1, 20, 0);
568
+ const timeDiff = this.state.time - startTime;
569
+ const minutes = Math.floor(timeDiff / 60000);
570
+ return `${minutes} minutes`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
571
  }
 
572
 
573
+ addMessage(text, type) {
574
+ const chat = document.getElementById('chat-container');
575
+ const messageDiv = document.createElement('div');
576
+ messageDiv.className = `message ${type}`;
577
+ messageDiv.textContent = text;
578
+ chat.appendChild(messageDiv);
579
+ chat.scrollTop = chat.scrollHeight;
580
+ }
581
 
582
+ addLoadingMessage() {
583
+ const chat = document.getElementById('chat-container');
584
+ const loadingDiv = document.createElement('div');
585
+ loadingDiv.className = 'message shyguy typing';
586
+ loadingDiv.id = 'loading-message';
587
+ loadingDiv.textContent = 'Thinking...';
588
+ chat.appendChild(loadingDiv);
589
+ chat.scrollTop = chat.scrollHeight;
590
+ }
 
 
 
 
 
 
 
 
 
 
 
591
 
592
+ removeLoadingMessage() {
593
+ const loadingMessage = document.getElementById('loading-message');
594
+ if (loadingMessage) {
595
+ loadingMessage.remove();
 
596
  }
 
597
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
598
 
599
+ updateStats() {
600
+ document.getElementById('confidence').textContent = this.state.confidence;
601
+ document.getElementById('anxiety').textContent = this.state.anxiety;
602
+ document.getElementById('drinks').textContent = this.state.drinks;
603
+ document.getElementById('time').textContent =
604
+ this.state.time.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
 
 
 
 
 
 
 
 
605
  }
606
  }
607
 
608
+ let game;
 
 
 
 
 
 
 
609
 
610
+ function initializeGame() {
611
+ const apiKey = document.getElementById('api-key').value.trim();
612
+ if (!apiKey) {
613
+ alert('Please enter your Mistral API key');
614
+ return;
615
+ }
616
 
617
+ document.getElementById('api-key-container').style.display = 'none';
618
+ document.getElementById('game-content').style.display = 'block';
619
+
620
+ game = new ShyGuySimulator(apiKey);
 
621
  }
622
 
623
+ async function handleUserInput() {
624
+ if (!game) return;
625
+
626
+ const input = document.getElementById('user-input');
627
+ const text = input.value.trim();
628
+ if (text) {
629
+ await game.handleInput(text);
630
+ input.value = '';
631
+ }
 
 
 
 
 
632
  }
 
633
 
634
+ async function move(dx, dy) {
635
+ if (game) {
636
+ await game.movePlayer(dx, dy);
637
+ }
638
  }
 
639
 
640
+ // Keyboard controls
641
+ document.addEventListener('keydown', async (e) => {
642
+ if (!game) return;
643
+
644
+ switch(e.key) {
645
+ case 'ArrowUp':
646
+ await move(0, -1);
647
+ break;
648
+ case 'ArrowDown':
649
+ await move(0, 1);
650
+ break;
651
+ case 'ArrowLeft':
652
+ await move(-1, 0);
653
+ break;
654
+ case 'ArrowRight':
655
+ await move(1, 0);
656
+ break;
657
+ }
658
+ });
659
 
660
+ // Enter key for input
661
+ document.getElementById('user-input')?.addEventListener('keypress', function(e) {
662
+ if (e.key === 'Enter') {
663
+ handleUserInput();
664
+ }
665
+ });
666
+ </script>
667
  </body>
668
+ </html>