aa
Browse files- .gitignore +1 -0
- index.html +176 -80
.gitignore
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
*_s.js
|
index.html
CHANGED
@@ -3,17 +3,23 @@
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
-
<title>Shy Guy Simulator -
|
7 |
<style>
|
8 |
body {
|
9 |
font-family: Arial, sans-serif;
|
10 |
-
max-width:
|
11 |
margin: 20px auto;
|
12 |
padding: 20px;
|
13 |
background-color: #1a1a1a;
|
14 |
color: #fff;
|
15 |
}
|
16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
#game-container {
|
18 |
background-color: #2a2a2a;
|
19 |
padding: 20px;
|
@@ -21,10 +27,29 @@
|
|
21 |
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
22 |
}
|
23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
.chat-container {
|
25 |
height: 400px;
|
26 |
overflow-y: auto;
|
27 |
-
margin: 20px 0;
|
28 |
padding: 10px;
|
29 |
background-color: #333;
|
30 |
border-radius: 4px;
|
@@ -37,20 +62,9 @@
|
|
37 |
word-wrap: break-word;
|
38 |
}
|
39 |
|
40 |
-
.wingman {
|
41 |
-
|
42 |
-
|
43 |
-
}
|
44 |
-
|
45 |
-
.shyguy {
|
46 |
-
background-color: #4a5568;
|
47 |
-
margin-left: 20%;
|
48 |
-
}
|
49 |
-
|
50 |
-
.error {
|
51 |
-
background-color: #c53030;
|
52 |
-
text-align: center;
|
53 |
-
}
|
54 |
|
55 |
#input-container {
|
56 |
display: flex;
|
@@ -76,22 +90,18 @@
|
|
76 |
cursor: pointer;
|
77 |
}
|
78 |
|
79 |
-
|
80 |
-
|
|
|
|
|
|
|
81 |
}
|
82 |
|
83 |
-
|
84 |
-
margin-top: 20px;
|
85 |
-
padding: 10px;
|
86 |
background-color: #333;
|
|
|
87 |
border-radius: 4px;
|
88 |
-
|
89 |
-
justify-content: space-between;
|
90 |
-
}
|
91 |
-
|
92 |
-
.typing {
|
93 |
-
font-style: italic;
|
94 |
-
color: #718096;
|
95 |
}
|
96 |
|
97 |
#api-key-container {
|
@@ -101,7 +111,7 @@
|
|
101 |
</head>
|
102 |
<body>
|
103 |
<div id="game-container">
|
104 |
-
<h1>Shy Guy Simulator -
|
105 |
|
106 |
<div id="api-key-container">
|
107 |
<input type="password" id="api-key" placeholder="Enter your Mistral API key" style="width: 100%; padding: 10px; margin-bottom: 10px;">
|
@@ -110,51 +120,98 @@
|
|
110 |
|
111 |
<div id="game-content" style="display: none;">
|
112 |
<div id="stats">
|
113 |
-
<
|
114 |
-
<
|
115 |
-
<
|
116 |
</div>
|
117 |
-
|
118 |
-
<div
|
119 |
-
<
|
120 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
121 |
</div>
|
122 |
</div>
|
123 |
</div>
|
124 |
|
125 |
<script>
|
126 |
-
let game;
|
127 |
-
|
128 |
class ShyGuySimulator {
|
129 |
constructor(apiKey) {
|
130 |
this.apiKey = apiKey;
|
131 |
this.state = {
|
132 |
confidence: 0,
|
133 |
-
|
134 |
time: new Date(2024, 0, 1, 20, 0),
|
135 |
-
|
136 |
-
hasSpokenToGirl: false,
|
137 |
isProcessing: false
|
138 |
};
|
139 |
|
140 |
-
this.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
141 |
{
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
}
|
149 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
150 |
|
151 |
this.initialize();
|
152 |
}
|
153 |
|
154 |
initialize() {
|
155 |
-
this.
|
|
|
156 |
this.addMessage("I... I don't know about this. Maybe I should just go home...", 'shyguy');
|
157 |
this.updateStats();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
158 |
}
|
159 |
|
160 |
async handleInput(userInput) {
|
@@ -167,7 +224,11 @@
|
|
167 |
this.addMessage(userInput, 'wingman');
|
168 |
this.addLoadingMessage();
|
169 |
|
170 |
-
const currentState = `Current
|
|
|
|
|
|
|
|
|
171 |
|
172 |
this.context.push({
|
173 |
role: 'user',
|
@@ -177,9 +238,7 @@
|
|
177 |
const response = await this.callMistralAPI();
|
178 |
|
179 |
this.removeLoadingMessage();
|
180 |
-
this.
|
181 |
-
|
182 |
-
this.updateGameState(response);
|
183 |
|
184 |
this.context.push({
|
185 |
role: 'assistant',
|
@@ -201,6 +260,59 @@
|
|
201 |
}
|
202 |
}
|
203 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
204 |
async callMistralAPI() {
|
205 |
try {
|
206 |
const response = await fetch('https://api.mistral.ai/v1/chat/completions', {
|
@@ -232,31 +344,6 @@
|
|
232 |
}
|
233 |
}
|
234 |
|
235 |
-
updateGameState(response) {
|
236 |
-
const lowerResponse = response.toLowerCase();
|
237 |
-
|
238 |
-
// Update confidence
|
239 |
-
if (lowerResponse.includes('okay') || lowerResponse.includes('maybe') || lowerResponse.includes('right')) {
|
240 |
-
this.state.confidence = Math.min(100, this.state.confidence + 10);
|
241 |
-
this.state.anxiety = Math.max(0, this.state.anxiety - 5);
|
242 |
-
} else if (lowerResponse.includes('no') || lowerResponse.includes('can\'t') || lowerResponse.includes('scared')) {
|
243 |
-
this.state.confidence = Math.max(0, this.state.confidence - 5);
|
244 |
-
this.state.anxiety = Math.min(100, this.state.anxiety + 10);
|
245 |
-
}
|
246 |
-
|
247 |
-
// Advance time
|
248 |
-
this.state.time = new Date(this.state.time.getTime() + 5 * 60000);
|
249 |
-
|
250 |
-
this.updateStats();
|
251 |
-
}
|
252 |
-
|
253 |
-
updateStats() {
|
254 |
-
document.getElementById('confidence').textContent = this.state.confidence;
|
255 |
-
document.getElementById('anxiety').textContent = this.state.anxiety;
|
256 |
-
document.getElementById('time').textContent =
|
257 |
-
this.state.time.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
|
258 |
-
}
|
259 |
-
|
260 |
addMessage(text, type) {
|
261 |
const chat = document.getElementById('chat-container');
|
262 |
const messageDiv = document.createElement('div');
|
@@ -282,8 +369,17 @@
|
|
282 |
loadingMessage.remove();
|
283 |
}
|
284 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
285 |
}
|
286 |
|
|
|
|
|
287 |
function initializeGame() {
|
288 |
const apiKey = document.getElementById('api-key').value.trim();
|
289 |
if (!apiKey) {
|
|
|
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;
|
10 |
+
max-width: 1200px;
|
11 |
margin: 20px auto;
|
12 |
padding: 20px;
|
13 |
background-color: #1a1a1a;
|
14 |
color: #fff;
|
15 |
}
|
16 |
|
17 |
+
.game-layout {
|
18 |
+
display: grid;
|
19 |
+
grid-template-columns: 1fr 400px;
|
20 |
+
gap: 20px;
|
21 |
+
}
|
22 |
+
|
23 |
#game-container {
|
24 |
background-color: #2a2a2a;
|
25 |
padding: 20px;
|
|
|
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;
|
|
|
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 |
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 {
|
|
|
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 |
|
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) {
|
|
|
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',
|
|
|
238 |
const response = await this.callMistralAPI();
|
239 |
|
240 |
this.removeLoadingMessage();
|
241 |
+
this.processAIResponse(response);
|
|
|
|
|
242 |
|
243 |
this.context.push({
|
244 |
role: 'assistant',
|
|
|
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', {
|
|
|
344 |
}
|
345 |
}
|
346 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
347 |
addMessage(text, type) {
|
348 |
const chat = document.getElementById('chat-container');
|
349 |
const messageDiv = document.createElement('div');
|
|
|
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) {
|