Spaces:
Sleeping
Sleeping
game v0.111
Browse files- gameState.js +0 -83
- static/assets/css/style.css +424 -0
- static/assets/fonts/horrorbrush.ttf +0 -0
- static/assets/img/blood-2.png +0 -0
- static/assets/img/hand.png +0 -0
- static/assets/img/help.png +0 -0
- static/assets/img/logo.png +0 -0
- static/assets/img/perso.png +0 -0
- static/assets/img/splatter.png +0 -0
- static/assets/img/vite.svg +1 -0
- static/game/game-with-chat.html +0 -537
- static/game/gameState.js +83 -0
- static/game/index.html +31 -54
- static/game/test prompt.txt +39 -0
- static/how-to-play.html +32 -0
- static/index.html +32 -147
- static/mistral/index.html +274 -0
- test/.DS_Store +0 -0
gameState.js
DELETED
@@ -1,83 +0,0 @@
|
|
1 |
-
class GameState {
|
2 |
-
constructor() {
|
3 |
-
// Initialize the lists of possible locations and targets
|
4 |
-
this.rooms = [
|
5 |
-
"main hallway",
|
6 |
-
"living room",
|
7 |
-
"guest bedroom",
|
8 |
-
"office",
|
9 |
-
"bathroom",
|
10 |
-
"kitchen",
|
11 |
-
"dining room",
|
12 |
-
"north hallway",
|
13 |
-
"storage",
|
14 |
-
];
|
15 |
-
|
16 |
-
this.hideTargets = ["closet", "bed"];
|
17 |
-
this.searchTargets = ["dresser", "drawer"];
|
18 |
-
this.useTargets = ["flashlight", "knife"];
|
19 |
-
|
20 |
-
// Track current game state
|
21 |
-
this.currentRoom = "main hallway";
|
22 |
-
this.inventory = [];
|
23 |
-
}
|
24 |
-
|
25 |
-
// Method to generate the prompt with current game state
|
26 |
-
getPrompt() {
|
27 |
-
return `You are an AI assistant roleplaying as the user's girlfriend. Your task is to respond to the user's messages by selecting an appropriate action and target, then formatting your response as a JSON object.
|
28 |
-
|
29 |
-
Here are the possible actions and their associated targets:
|
30 |
-
|
31 |
-
- go: [${this.rooms.join(", ")}]
|
32 |
-
- hide: [${this.hideTargets.join(", ")}]
|
33 |
-
- search: [${this.searchTargets.join(", ")}]
|
34 |
-
- use: [${this.useTargets.join(", ")}]
|
35 |
-
|
36 |
-
You will receive a message from the user in the following format:
|
37 |
-
<user_message>
|
38 |
-
{{USER_MESSAGE}}
|
39 |
-
</user_message>
|
40 |
-
|
41 |
-
To process the user's message and generate a response, follow these steps:
|
42 |
-
|
43 |
-
1. Analyze the user's message to determine the most appropriate action from the list: go, hide, search, or use.
|
44 |
-
2. Based on the chosen action, select an appropriate target from the associated list. If the action is "use", the target can be any object mentioned in the user's message.
|
45 |
-
3. Create a brief, natural-sounding response message that acknowledges the user's request or command.
|
46 |
-
4. Construct a JSON object with the following structure:
|
47 |
-
{
|
48 |
-
"message": "Your response message here",
|
49 |
-
"action": "chosen action",
|
50 |
-
"target": "chosen target"
|
51 |
-
}
|
52 |
-
|
53 |
-
Here are two examples to guide you:
|
54 |
-
|
55 |
-
Example 1:
|
56 |
-
<user_message>Go to the bathroom, now!</user_message>
|
57 |
-
Response:
|
58 |
-
{
|
59 |
-
"message": "Okay, I'm on my way to the bathroom.",
|
60 |
-
"action": "go",
|
61 |
-
"target": "bathroom"
|
62 |
-
}
|
63 |
-
|
64 |
-
Example 2:
|
65 |
-
<user_message>Quick, hide under the bed!</user_message>
|
66 |
-
Response:
|
67 |
-
{
|
68 |
-
"message": "Alright, I'm hiding under the bed right away.",
|
69 |
-
"action": "hide",
|
70 |
-
"target": "bed"
|
71 |
-
}
|
72 |
-
|
73 |
-
Remember:
|
74 |
-
- Always respond in the JSON format specified above.
|
75 |
-
- Choose the most appropriate action and target based on the user's message.
|
76 |
-
- Keep your response message brief and natural-sounding.
|
77 |
-
- If the user's message doesn't clearly indicate an action or target from the provided lists, use your best judgment to select the most relevant options.
|
78 |
-
- Do not include any explanation or additional text outside of the JSON object in your response.`;
|
79 |
-
}
|
80 |
-
}
|
81 |
-
|
82 |
-
// Export the class for use in other files
|
83 |
-
export default GameState;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/assets/css/style.css
ADDED
@@ -0,0 +1,424 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@font-face {
|
2 |
+
font-family: "HorrorBrush";
|
3 |
+
src: url("/static/assets/fonts/horrorbrush.ttf") format("truetype");
|
4 |
+
}
|
5 |
+
|
6 |
+
* {
|
7 |
+
margin: 0;
|
8 |
+
padding: 0;
|
9 |
+
box-sizing: border-box;
|
10 |
+
}
|
11 |
+
|
12 |
+
body {
|
13 |
+
background-color: #000000;
|
14 |
+
min-height: 100vh;
|
15 |
+
display: flex;
|
16 |
+
flex-direction: column;
|
17 |
+
align-items: center;
|
18 |
+
position: relative;
|
19 |
+
overflow: hidden;
|
20 |
+
}
|
21 |
+
|
22 |
+
.background-elements {
|
23 |
+
position: absolute;
|
24 |
+
width: 100%;
|
25 |
+
height: 100%;
|
26 |
+
pointer-events: none;
|
27 |
+
}
|
28 |
+
|
29 |
+
.blood {
|
30 |
+
position: absolute;
|
31 |
+
}
|
32 |
+
|
33 |
+
.blood-top-left {
|
34 |
+
top: 0;
|
35 |
+
left: 0;
|
36 |
+
width: 350px;
|
37 |
+
opacity: 50%;
|
38 |
+
}
|
39 |
+
|
40 |
+
.blood-bottom-right {
|
41 |
+
bottom: 0;
|
42 |
+
right: 0;
|
43 |
+
width: 250px;
|
44 |
+
position: absolute;
|
45 |
+
bottom: -120px;
|
46 |
+
right: -50px;
|
47 |
+
opacity: 40%;
|
48 |
+
}
|
49 |
+
|
50 |
+
.blood-top-right {
|
51 |
+
position: absolute;
|
52 |
+
top: 0;
|
53 |
+
right: 0;
|
54 |
+
width: 250px;
|
55 |
+
margin: 20px 20px 0px 0px;
|
56 |
+
transform: rotate(30deg);
|
57 |
+
}
|
58 |
+
|
59 |
+
.splatter {
|
60 |
+
position: absolute;
|
61 |
+
opacity: 60%;
|
62 |
+
bottom: 240px;
|
63 |
+
left: 30%;
|
64 |
+
transform: translateX(-50%);
|
65 |
+
transform: rotate(20deg);
|
66 |
+
width: 400px;
|
67 |
+
}
|
68 |
+
|
69 |
+
main {
|
70 |
+
display: flex;
|
71 |
+
flex-direction: column;
|
72 |
+
align-items: center;
|
73 |
+
gap: 100px;
|
74 |
+
z-index: 1;
|
75 |
+
margin-top: 200px;
|
76 |
+
}
|
77 |
+
|
78 |
+
.logo {
|
79 |
+
position: absolute;
|
80 |
+
top: 40px;
|
81 |
+
left: 50%;
|
82 |
+
transform: translateX(-50%);
|
83 |
+
animation: flicker 4s linear infinite;
|
84 |
+
}
|
85 |
+
|
86 |
+
.logo img {
|
87 |
+
width: 230px;
|
88 |
+
height: auto;
|
89 |
+
}
|
90 |
+
|
91 |
+
.menu {
|
92 |
+
display: flex;
|
93 |
+
flex-direction: column;
|
94 |
+
gap: 30px;
|
95 |
+
align-items: center;
|
96 |
+
position: relative;
|
97 |
+
}
|
98 |
+
|
99 |
+
.menu::after {
|
100 |
+
content: "";
|
101 |
+
position: absolute;
|
102 |
+
width: 800px;
|
103 |
+
height: 800px;
|
104 |
+
background: radial-gradient(
|
105 |
+
circle,
|
106 |
+
rgba(155, 0, 0, 0.35) 0%,
|
107 |
+
rgba(0, 0, 0, 0) 70%
|
108 |
+
);
|
109 |
+
z-index: -1;
|
110 |
+
top: 50%;
|
111 |
+
left: 50%;
|
112 |
+
transform: translate(-50%, -50%);
|
113 |
+
}
|
114 |
+
|
115 |
+
.menu-item {
|
116 |
+
font-family: "HorrorBrush", cursive;
|
117 |
+
font-size: 48px;
|
118 |
+
color: #fff;
|
119 |
+
text-decoration: none;
|
120 |
+
transition: color 0.3s ease;
|
121 |
+
letter-spacing: 4px;
|
122 |
+
text-transform: uppercase;
|
123 |
+
}
|
124 |
+
|
125 |
+
.new-game {
|
126 |
+
color: #9b0000;
|
127 |
+
}
|
128 |
+
|
129 |
+
.menu-item:hover {
|
130 |
+
color: #9b0000;
|
131 |
+
}
|
132 |
+
|
133 |
+
.how-to-play:hover {
|
134 |
+
color: #9b0000;
|
135 |
+
}
|
136 |
+
|
137 |
+
.level-maker:hover {
|
138 |
+
color: #9b0000;
|
139 |
+
}
|
140 |
+
|
141 |
+
@keyframes flicker {
|
142 |
+
0% {
|
143 |
+
opacity: 1;
|
144 |
+
}
|
145 |
+
5% {
|
146 |
+
opacity: 0.9;
|
147 |
+
}
|
148 |
+
10% {
|
149 |
+
opacity: 1;
|
150 |
+
}
|
151 |
+
15% {
|
152 |
+
opacity: 0.4;
|
153 |
+
}
|
154 |
+
16% {
|
155 |
+
opacity: 1;
|
156 |
+
}
|
157 |
+
17% {
|
158 |
+
opacity: 0.4;
|
159 |
+
}
|
160 |
+
18% {
|
161 |
+
opacity: 1;
|
162 |
+
}
|
163 |
+
35% {
|
164 |
+
opacity: 1;
|
165 |
+
}
|
166 |
+
36% {
|
167 |
+
opacity: 0.3;
|
168 |
+
}
|
169 |
+
37% {
|
170 |
+
opacity: 1;
|
171 |
+
}
|
172 |
+
38% {
|
173 |
+
opacity: 0.5;
|
174 |
+
}
|
175 |
+
39% {
|
176 |
+
opacity: 1;
|
177 |
+
}
|
178 |
+
50% {
|
179 |
+
opacity: 1;
|
180 |
+
}
|
181 |
+
51% {
|
182 |
+
opacity: 0.7;
|
183 |
+
}
|
184 |
+
52% {
|
185 |
+
opacity: 1;
|
186 |
+
}
|
187 |
+
53% {
|
188 |
+
opacity: 0.4;
|
189 |
+
}
|
190 |
+
54% {
|
191 |
+
opacity: 1;
|
192 |
+
}
|
193 |
+
85% {
|
194 |
+
opacity: 1;
|
195 |
+
}
|
196 |
+
86% {
|
197 |
+
opacity: 0.6;
|
198 |
+
}
|
199 |
+
87% {
|
200 |
+
opacity: 1;
|
201 |
+
}
|
202 |
+
88% {
|
203 |
+
opacity: 0.4;
|
204 |
+
}
|
205 |
+
89% {
|
206 |
+
opacity: 1;
|
207 |
+
}
|
208 |
+
100% {
|
209 |
+
opacity: 1;
|
210 |
+
}
|
211 |
+
}
|
212 |
+
|
213 |
+
.led-bar {
|
214 |
+
position: fixed;
|
215 |
+
top: 0;
|
216 |
+
left: 0;
|
217 |
+
width: 100%;
|
218 |
+
height: 2px;
|
219 |
+
background: linear-gradient(
|
220 |
+
90deg,
|
221 |
+
transparent 0%,
|
222 |
+
rgba(255, 0, 0, 0.4) 20%,
|
223 |
+
rgba(255, 0, 0, 0.8) 35%,
|
224 |
+
rgba(255, 50, 50, 1) 50%,
|
225 |
+
rgba(255, 0, 0, 0.8) 65%,
|
226 |
+
rgba(255, 0, 0, 0.4) 80%,
|
227 |
+
transparent 100%
|
228 |
+
);
|
229 |
+
z-index: 100;
|
230 |
+
animation: ledFlicker 4s infinite, ledPulse 10s infinite;
|
231 |
+
box-shadow: 0 0 20px rgba(255, 0, 0, 0.7), 0 0 35px rgba(255, 0, 0, 0.5),
|
232 |
+
0 0 50px rgba(255, 0, 0, 0.4), 0 0 70px rgba(155, 0, 0, 0.3);
|
233 |
+
filter: blur(0.6px);
|
234 |
+
}
|
235 |
+
|
236 |
+
.light-beam {
|
237 |
+
position: absolute;
|
238 |
+
top: 0;
|
239 |
+
left: 0;
|
240 |
+
width: 100%;
|
241 |
+
height: 250px;
|
242 |
+
background: linear-gradient(
|
243 |
+
180deg,
|
244 |
+
rgba(255, 0, 0, 0.3) 0%,
|
245 |
+
rgba(255, 0, 0, 0.2) 20%,
|
246 |
+
rgba(255, 0, 0, 0.15) 30%,
|
247 |
+
rgba(155, 0, 0, 0.08) 60%,
|
248 |
+
transparent 100%
|
249 |
+
);
|
250 |
+
animation: beamFlicker 4s infinite;
|
251 |
+
pointer-events: none;
|
252 |
+
filter: blur(2px);
|
253 |
+
}
|
254 |
+
|
255 |
+
@keyframes ledFlicker {
|
256 |
+
0% {
|
257 |
+
opacity: 1;
|
258 |
+
}
|
259 |
+
95% {
|
260 |
+
opacity: 1;
|
261 |
+
}
|
262 |
+
96% {
|
263 |
+
opacity: 0.3;
|
264 |
+
}
|
265 |
+
97% {
|
266 |
+
opacity: 1;
|
267 |
+
}
|
268 |
+
98% {
|
269 |
+
opacity: 0.2;
|
270 |
+
}
|
271 |
+
99% {
|
272 |
+
opacity: 0.9;
|
273 |
+
}
|
274 |
+
100% {
|
275 |
+
opacity: 1;
|
276 |
+
}
|
277 |
+
}
|
278 |
+
|
279 |
+
@keyframes ledPulse {
|
280 |
+
0% {
|
281 |
+
filter: brightness(1) blur(0.6px);
|
282 |
+
}
|
283 |
+
50% {
|
284 |
+
filter: brightness(1.3) blur(0.4px);
|
285 |
+
}
|
286 |
+
100% {
|
287 |
+
filter: brightness(1) blur(0.6px);
|
288 |
+
}
|
289 |
+
}
|
290 |
+
|
291 |
+
@keyframes beamFlicker {
|
292 |
+
0% {
|
293 |
+
opacity: 0.7;
|
294 |
+
}
|
295 |
+
95% {
|
296 |
+
opacity: 0.7;
|
297 |
+
}
|
298 |
+
96% {
|
299 |
+
opacity: 0.2;
|
300 |
+
}
|
301 |
+
97% {
|
302 |
+
opacity: 0.7;
|
303 |
+
}
|
304 |
+
98% {
|
305 |
+
opacity: 0.1;
|
306 |
+
}
|
307 |
+
99% {
|
308 |
+
opacity: 0.6;
|
309 |
+
}
|
310 |
+
100% {
|
311 |
+
opacity: 0.7;
|
312 |
+
}
|
313 |
+
}
|
314 |
+
|
315 |
+
/* How to Play Page Styles */
|
316 |
+
.content {
|
317 |
+
color: #fff;
|
318 |
+
text-align: center;
|
319 |
+
max-width: 800px;
|
320 |
+
margin: 40px auto;
|
321 |
+
padding: 20px;
|
322 |
+
}
|
323 |
+
|
324 |
+
.content h1 {
|
325 |
+
font-family: "HorrorBrush", cursive;
|
326 |
+
font-size: 64px;
|
327 |
+
color: #9b0000;
|
328 |
+
position: absolute;
|
329 |
+
top: 40px;
|
330 |
+
left: 50%;
|
331 |
+
transform: translateX(-50%);
|
332 |
+
letter-spacing: 4px;
|
333 |
+
}
|
334 |
+
|
335 |
+
.back-button {
|
336 |
+
font-family: "HorrorBrush", cursive;
|
337 |
+
font-size: 36px;
|
338 |
+
color: #fff;
|
339 |
+
text-decoration: none;
|
340 |
+
transition: color 0.3s ease;
|
341 |
+
letter-spacing: 4px;
|
342 |
+
text-transform: uppercase;
|
343 |
+
position: absolute;
|
344 |
+
bottom: 40px;
|
345 |
+
left: 50%;
|
346 |
+
transform: translateX(-50%);
|
347 |
+
}
|
348 |
+
|
349 |
+
.back-button:hover {
|
350 |
+
color: #9b0000;
|
351 |
+
}
|
352 |
+
|
353 |
+
.character {
|
354 |
+
position: absolute;
|
355 |
+
bottom: -20px;
|
356 |
+
left: 50%;
|
357 |
+
transform: translateX(-50%);
|
358 |
+
z-index: 2;
|
359 |
+
animation: characterFlicker 6s infinite;
|
360 |
+
}
|
361 |
+
|
362 |
+
.character img {
|
363 |
+
width: 450px;
|
364 |
+
height: auto;
|
365 |
+
}
|
366 |
+
|
367 |
+
@keyframes characterFlicker {
|
368 |
+
0% {
|
369 |
+
opacity: 1;
|
370 |
+
filter: brightness(1);
|
371 |
+
}
|
372 |
+
42% {
|
373 |
+
opacity: 1;
|
374 |
+
filter: brightness(1);
|
375 |
+
}
|
376 |
+
43% {
|
377 |
+
opacity: 0.8;
|
378 |
+
filter: brightness(1.2);
|
379 |
+
}
|
380 |
+
44% {
|
381 |
+
opacity: 1;
|
382 |
+
filter: brightness(1);
|
383 |
+
}
|
384 |
+
45% {
|
385 |
+
opacity: 0.6;
|
386 |
+
filter: brightness(1.3);
|
387 |
+
}
|
388 |
+
46% {
|
389 |
+
opacity: 1;
|
390 |
+
filter: brightness(1);
|
391 |
+
}
|
392 |
+
47% {
|
393 |
+
opacity: 0.2;
|
394 |
+
filter: brightness(1.5);
|
395 |
+
}
|
396 |
+
48% {
|
397 |
+
opacity: 1;
|
398 |
+
filter: brightness(1);
|
399 |
+
}
|
400 |
+
49% {
|
401 |
+
opacity: 0.4;
|
402 |
+
filter: brightness(1.2);
|
403 |
+
}
|
404 |
+
50% {
|
405 |
+
opacity: 1;
|
406 |
+
filter: brightness(1);
|
407 |
+
}
|
408 |
+
80% {
|
409 |
+
opacity: 1;
|
410 |
+
filter: brightness(1);
|
411 |
+
}
|
412 |
+
81% {
|
413 |
+
opacity: 0.5;
|
414 |
+
filter: brightness(1.3);
|
415 |
+
}
|
416 |
+
82% {
|
417 |
+
opacity: 1;
|
418 |
+
filter: brightness(1);
|
419 |
+
}
|
420 |
+
100% {
|
421 |
+
opacity: 1;
|
422 |
+
filter: brightness(1);
|
423 |
+
}
|
424 |
+
}
|
static/assets/fonts/horrorbrush.ttf
ADDED
Binary file (286 kB). View file
|
|
static/assets/img/blood-2.png
ADDED
static/assets/img/hand.png
ADDED
static/assets/img/help.png
ADDED
static/assets/img/logo.png
ADDED
static/assets/img/perso.png
ADDED
static/assets/img/splatter.png
ADDED
static/assets/img/vite.svg
ADDED
static/game/game-with-chat.html
DELETED
@@ -1,537 +0,0 @@
|
|
1 |
-
<!DOCTYPE html>
|
2 |
-
<html>
|
3 |
-
<head>
|
4 |
-
<title>Get Me Out! - Apartment Escape Game with Chat</title>
|
5 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script>
|
6 |
-
<style>
|
7 |
-
/* Layout */
|
8 |
-
body {
|
9 |
-
margin: 0;
|
10 |
-
font-family: Arial, sans-serif;
|
11 |
-
display: flex;
|
12 |
-
height: 100vh;
|
13 |
-
}
|
14 |
-
#gameContainer {
|
15 |
-
display: flex;
|
16 |
-
width: 100%;
|
17 |
-
height: 100%;
|
18 |
-
}
|
19 |
-
#mapSection {
|
20 |
-
flex: 1;
|
21 |
-
position: relative;
|
22 |
-
background: #f5f5f5;
|
23 |
-
display: flex;
|
24 |
-
flex-direction: column;
|
25 |
-
align-items: center;
|
26 |
-
}
|
27 |
-
/* We'll place the P5 canvas in here */
|
28 |
-
.map-wrapper {
|
29 |
-
margin: 20px;
|
30 |
-
transform-origin: top left;
|
31 |
-
}
|
32 |
-
|
33 |
-
#chatSection {
|
34 |
-
width: 400px;
|
35 |
-
border-left: 1px solid #ddd;
|
36 |
-
display: flex;
|
37 |
-
flex-direction: column;
|
38 |
-
background: white;
|
39 |
-
}
|
40 |
-
#chatHistory {
|
41 |
-
flex: 1;
|
42 |
-
overflow-y: auto;
|
43 |
-
padding: 20px;
|
44 |
-
background: #f8f9fa;
|
45 |
-
}
|
46 |
-
#chatControls {
|
47 |
-
padding: 20px;
|
48 |
-
border-top: 1px solid #ddd;
|
49 |
-
background: white;
|
50 |
-
}
|
51 |
-
.chat-message {
|
52 |
-
padding: 10px;
|
53 |
-
margin: 5px 0;
|
54 |
-
border-radius: 16px;
|
55 |
-
max-width: 80%;
|
56 |
-
word-wrap: break-word;
|
57 |
-
}
|
58 |
-
.user-message {
|
59 |
-
background: #007AFF;
|
60 |
-
color: white;
|
61 |
-
margin-left: auto;
|
62 |
-
border-radius: 16px 16px 4px 16px;
|
63 |
-
}
|
64 |
-
.assistant-message {
|
65 |
-
background: #E9E9EB;
|
66 |
-
color: black;
|
67 |
-
margin-right: auto;
|
68 |
-
border-radius: 16px 16px 16px 4px;
|
69 |
-
}
|
70 |
-
textarea {
|
71 |
-
width: 100%;
|
72 |
-
padding: 10px;
|
73 |
-
border: 1px solid #ddd;
|
74 |
-
border-radius: 4px;
|
75 |
-
margin-bottom: 10px;
|
76 |
-
resize: none;
|
77 |
-
font-family: inherit;
|
78 |
-
}
|
79 |
-
button {
|
80 |
-
padding: 8px 16px;
|
81 |
-
background: #007AFF;
|
82 |
-
color: white;
|
83 |
-
border: none;
|
84 |
-
border-radius: 4px;
|
85 |
-
cursor: pointer;
|
86 |
-
}
|
87 |
-
button:hover {
|
88 |
-
background: #0056b3;
|
89 |
-
}
|
90 |
-
.loading-dots {
|
91 |
-
display: inline-flex;
|
92 |
-
gap: 4px;
|
93 |
-
padding: 5px;
|
94 |
-
margin: 5px 0;
|
95 |
-
}
|
96 |
-
.dot {
|
97 |
-
width: 8px;
|
98 |
-
height: 8px;
|
99 |
-
background: #6c757d;
|
100 |
-
border-radius: 50%;
|
101 |
-
animation: wave 1.3s linear infinite;
|
102 |
-
}
|
103 |
-
.dot:nth-child(2) { animation-delay: -1.1s; }
|
104 |
-
.dot:nth-child(3) { animation-delay: -0.9s; }
|
105 |
-
@keyframes wave {
|
106 |
-
0%, 60%, 100% { transform: translateY(0); }
|
107 |
-
30% { transform: translateY(-4px); }
|
108 |
-
}
|
109 |
-
|
110 |
-
/* Modal for API key (if needed) */
|
111 |
-
#apiKeyModal {
|
112 |
-
display: none;
|
113 |
-
position: fixed;
|
114 |
-
top: 0; left: 0;
|
115 |
-
width: 100%; height: 100%;
|
116 |
-
background: rgba(0,0,0,0.5);
|
117 |
-
justify-content: center;
|
118 |
-
align-items: center;
|
119 |
-
}
|
120 |
-
.modal-content {
|
121 |
-
background: white;
|
122 |
-
padding: 20px;
|
123 |
-
border-radius: 8px;
|
124 |
-
width: 300px;
|
125 |
-
}
|
126 |
-
#apiKey {
|
127 |
-
width: 100%;
|
128 |
-
margin: 10px 0;
|
129 |
-
padding: 8px;
|
130 |
-
}
|
131 |
-
</style>
|
132 |
-
</head>
|
133 |
-
<body>
|
134 |
-
<div id="gameContainer">
|
135 |
-
<div id="mapSection">
|
136 |
-
<div id="mapWrapper" class="map-wrapper">
|
137 |
-
<!-- P5.js canvas goes here -->
|
138 |
-
</div>
|
139 |
-
</div>
|
140 |
-
<div id="chatSection">
|
141 |
-
<div id="chatHistory"></div>
|
142 |
-
<div id="chatControls">
|
143 |
-
<textarea id="prompt" placeholder="Type your message..." rows="3"></textarea>
|
144 |
-
<button onclick="sendMessage()">Send</button>
|
145 |
-
</div>
|
146 |
-
</div>
|
147 |
-
</div>
|
148 |
-
<div id="apiKeyModal">
|
149 |
-
<div class="modal-content">
|
150 |
-
<h3>Enter Mistral API Key</h3>
|
151 |
-
<input type="password" id="apiKey" placeholder="Enter your API key">
|
152 |
-
<button onclick="saveApiKey()">Save</button>
|
153 |
-
</div>
|
154 |
-
</div>
|
155 |
-
|
156 |
-
<script>
|
157 |
-
/*
|
158 |
-
* This script loads ./apt.json to set up the map,
|
159 |
-
* draws it in p5.js, and provides a chat interface on the right.
|
160 |
-
*/
|
161 |
-
|
162 |
-
// Constants and variables for the map
|
163 |
-
const CELL_SIZE = 30;
|
164 |
-
let GRID_COLS = 0;
|
165 |
-
let GRID_ROWS = 0;
|
166 |
-
let grid = [];
|
167 |
-
let rooms = new Map();
|
168 |
-
let characterPos = null;
|
169 |
-
let path = [];
|
170 |
-
let isMoving = false;
|
171 |
-
let moveInterval = null;
|
172 |
-
|
173 |
-
// Chat variables
|
174 |
-
let apiKey = localStorage.getItem('mistralApiKey');
|
175 |
-
const systemPrompt = `You are the game master of "Get Me Out!", a single-player text-based survival game. The scenario: a player must guide their girlfriend to safety via text messages while she's being hunted by a murderous clown in their apartment. The player has access to security cameras and can send instructions through text.
|
176 |
-
|
177 |
-
RESPONSE FORMAT:
|
178 |
-
You must ALWAYS respond with a JSON object. The response should reflect the girlfriend's reaction to the player's message.
|
179 |
-
|
180 |
-
1. For movement instructions ("go" action):
|
181 |
-
{
|
182 |
-
"action": "go",
|
183 |
-
"to": "[room name]",
|
184 |
-
"textMessage": "[girlfriend's response]"
|
185 |
-
}
|
186 |
-
|
187 |
-
2. For any other input or unclear instructions:
|
188 |
-
{
|
189 |
-
"textMessage": "[girlfriend's response]"
|
190 |
-
}
|
191 |
-
|
192 |
-
VALID ROOMS:
|
193 |
-
Only these rooms are recognized for movement:
|
194 |
-
- Main Bathroom
|
195 |
-
- Guest Toilet
|
196 |
-
- Dining Room
|
197 |
-
- Kitchen
|
198 |
-
- TV Room
|
199 |
-
- Living Room
|
200 |
-
- Hallway
|
201 |
-
- Office
|
202 |
-
- Bedroom
|
203 |
-
|
204 |
-
CHARACTER BEHAVIOR:
|
205 |
-
The girlfriend is aware of the danger and extremely distressed. Her text responses should be:
|
206 |
-
- Brief and urgent
|
207 |
-
- Reflect genuine fear and panic
|
208 |
-
- Written like real text messages (short, quick responses)
|
209 |
-
- No time for pleasantries or long explanations
|
210 |
-
- May include typos or rushed writing due to stress
|
211 |
-
`;
|
212 |
-
|
213 |
-
// Load apt.json upon page start
|
214 |
-
async function loadAptJson() {
|
215 |
-
try {
|
216 |
-
const response = await fetch('./apt.json');
|
217 |
-
if (!response.ok) {
|
218 |
-
alert("Failed to load apt.json!");
|
219 |
-
return;
|
220 |
-
}
|
221 |
-
const data = await response.json();
|
222 |
-
// Update local variables from apt.json
|
223 |
-
GRID_COLS = data.gridCols || 40;
|
224 |
-
GRID_ROWS = data.gridRows || 20;
|
225 |
-
grid = data.grid || [];
|
226 |
-
rooms = new Map(data.rooms);
|
227 |
-
characterPos = data.characterPos || { x: 0, y: 0 };
|
228 |
-
|
229 |
-
// Once loaded, initialize the P5 canvas with correct dims
|
230 |
-
let canvas = createCanvas(GRID_COLS * CELL_SIZE, GRID_ROWS * CELL_SIZE);
|
231 |
-
canvas.parent('mapWrapper');
|
232 |
-
adjustScale();
|
233 |
-
|
234 |
-
} catch (e) {
|
235 |
-
console.error("Error loading apt.json:", e);
|
236 |
-
}
|
237 |
-
}
|
238 |
-
|
239 |
-
function adjustScale() {
|
240 |
-
const availableWidth = window.innerWidth - 400 - 40; // space for chat + margins
|
241 |
-
const actualCanvasWidth = GRID_COLS * CELL_SIZE;
|
242 |
-
const scale = availableWidth / actualCanvasWidth;
|
243 |
-
const mapWrapper = document.querySelector('#mapSection .map-wrapper');
|
244 |
-
if (mapWrapper) {
|
245 |
-
// use CSS zoom or transform
|
246 |
-
mapWrapper.style.zoom = scale;
|
247 |
-
}
|
248 |
-
}
|
249 |
-
|
250 |
-
window.addEventListener('resize', adjustScale);
|
251 |
-
|
252 |
-
// p5.js setup
|
253 |
-
function setup() {
|
254 |
-
// We'll wait to createCanvas until apt.json is loaded
|
255 |
-
loadAptJson();
|
256 |
-
}
|
257 |
-
|
258 |
-
// p5.js draw loop
|
259 |
-
function draw() {
|
260 |
-
if (!grid || grid.length === 0) {
|
261 |
-
// No grid loaded yet or apt.json not ready
|
262 |
-
return;
|
263 |
-
}
|
264 |
-
background(255);
|
265 |
-
drawGrid();
|
266 |
-
drawRooms();
|
267 |
-
drawWallsAndDoors();
|
268 |
-
drawPath();
|
269 |
-
drawCharacter();
|
270 |
-
}
|
271 |
-
|
272 |
-
function drawGrid() {
|
273 |
-
stroke(200);
|
274 |
-
for (let x = 0; x <= GRID_COLS; x++) {
|
275 |
-
line(x * CELL_SIZE, 0, x * CELL_SIZE, GRID_ROWS*CELL_SIZE);
|
276 |
-
}
|
277 |
-
for (let y = 0; y <= GRID_ROWS; y++) {
|
278 |
-
line(0, y * CELL_SIZE, GRID_COLS*CELL_SIZE, y * CELL_SIZE);
|
279 |
-
}
|
280 |
-
}
|
281 |
-
|
282 |
-
function drawRooms() {
|
283 |
-
for (let [roomName, cells] of rooms) {
|
284 |
-
const hue = stringToHue(roomName);
|
285 |
-
fill(hue, 30, 95, 0.3);
|
286 |
-
noStroke();
|
287 |
-
for (let cell of cells) {
|
288 |
-
rect(cell.x * CELL_SIZE, cell.y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
|
289 |
-
}
|
290 |
-
if (cells.length > 0) {
|
291 |
-
fill(0);
|
292 |
-
textAlign(CENTER, CENTER);
|
293 |
-
textSize(10);
|
294 |
-
text(roomName,
|
295 |
-
cells[0].x * CELL_SIZE + CELL_SIZE/2,
|
296 |
-
cells[0].y * CELL_SIZE + CELL_SIZE/2
|
297 |
-
);
|
298 |
-
}
|
299 |
-
}
|
300 |
-
}
|
301 |
-
|
302 |
-
function drawWallsAndDoors() {
|
303 |
-
for (let y = 0; y < GRID_ROWS; y++) {
|
304 |
-
for (let x = 0; x < GRID_COLS; x++) {
|
305 |
-
const cell = grid[y][x];
|
306 |
-
if (cell.type === 'wall') {
|
307 |
-
fill(0);
|
308 |
-
noStroke();
|
309 |
-
rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
|
310 |
-
} else if (cell.type === 'door') {
|
311 |
-
fill(255, 0, 0);
|
312 |
-
noStroke();
|
313 |
-
rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
|
314 |
-
}
|
315 |
-
}
|
316 |
-
}
|
317 |
-
}
|
318 |
-
|
319 |
-
function drawPath() {
|
320 |
-
if (path.length > 0 && isMoving) {
|
321 |
-
noFill();
|
322 |
-
stroke(0, 255, 0);
|
323 |
-
strokeWeight(2);
|
324 |
-
line(
|
325 |
-
characterPos.x * CELL_SIZE + CELL_SIZE/2,
|
326 |
-
characterPos.y * CELL_SIZE + CELL_SIZE/2,
|
327 |
-
path[0].x * CELL_SIZE + CELL_SIZE/2,
|
328 |
-
path[0].y * CELL_SIZE + CELL_SIZE/2
|
329 |
-
);
|
330 |
-
for (let i = 0; i < path.length - 1; i++) {
|
331 |
-
line(
|
332 |
-
path[i].x * CELL_SIZE + CELL_SIZE/2,
|
333 |
-
path[i].y * CELL_SIZE + CELL_SIZE/2,
|
334 |
-
path[i + 1].x * CELL_SIZE + CELL_SIZE/2,
|
335 |
-
path[i + 1].y * CELL_SIZE + CELL_SIZE/2
|
336 |
-
);
|
337 |
-
}
|
338 |
-
strokeWeight(1);
|
339 |
-
}
|
340 |
-
}
|
341 |
-
|
342 |
-
function drawCharacter() {
|
343 |
-
if (characterPos) {
|
344 |
-
textSize(CELL_SIZE * 0.8);
|
345 |
-
textAlign(CENTER, CENTER);
|
346 |
-
text('👧',
|
347 |
-
characterPos.x * CELL_SIZE + CELL_SIZE/2,
|
348 |
-
characterPos.y * CELL_SIZE + CELL_SIZE/2
|
349 |
-
);
|
350 |
-
}
|
351 |
-
}
|
352 |
-
|
353 |
-
function stringToHue(str) {
|
354 |
-
let hash = 0;
|
355 |
-
for (let i = 0; i < str.length; i++) {
|
356 |
-
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
357 |
-
}
|
358 |
-
return hash % 360;
|
359 |
-
}
|
360 |
-
|
361 |
-
// Simple BFS for pathfinding
|
362 |
-
function findPath(start, end) {
|
363 |
-
const queue = [[start]];
|
364 |
-
const visited = new Set();
|
365 |
-
const key = pos => `${pos.x},${pos.y}`;
|
366 |
-
visited.add(key(start));
|
367 |
-
|
368 |
-
while (queue.length > 0) {
|
369 |
-
const currentPath = queue.shift();
|
370 |
-
const current = currentPath[currentPath.length - 1];
|
371 |
-
|
372 |
-
if (current.x === end.x && current.y === end.y) {
|
373 |
-
return currentPath;
|
374 |
-
}
|
375 |
-
const neighbors = [
|
376 |
-
{ x: current.x, y: current.y - 1 },
|
377 |
-
{ x: current.x+1, y: current.y },
|
378 |
-
{ x: current.x, y: current.y+1 },
|
379 |
-
{ x: current.x-1, y: current.y }
|
380 |
-
];
|
381 |
-
for (const next of neighbors) {
|
382 |
-
if (next.x < 0 || next.x >= GRID_COLS || next.y < 0 || next.y >= GRID_ROWS) continue;
|
383 |
-
if (visited.has(key(next))) continue;
|
384 |
-
|
385 |
-
const cell = grid[next.y][next.x];
|
386 |
-
if (cell.type === 'wall') continue;
|
387 |
-
|
388 |
-
visited.add(key(next));
|
389 |
-
queue.push([...currentPath, next]);
|
390 |
-
}
|
391 |
-
}
|
392 |
-
return [];
|
393 |
-
}
|
394 |
-
|
395 |
-
function moveToRoom(roomName) {
|
396 |
-
const targetRoom = Array.from(rooms.entries())
|
397 |
-
.find(([name]) => name.toLowerCase() === roomName.toLowerCase());
|
398 |
-
if (!targetRoom || !characterPos) return false;
|
399 |
-
|
400 |
-
const [_, cells] = targetRoom;
|
401 |
-
if (cells.length === 0) return false;
|
402 |
-
path = findPath(characterPos, cells[0]);
|
403 |
-
if (path.length > 0) {
|
404 |
-
isMoving = true;
|
405 |
-
moveCharacterAlongPath();
|
406 |
-
return true;
|
407 |
-
}
|
408 |
-
return false;
|
409 |
-
}
|
410 |
-
|
411 |
-
function moveCharacterAlongPath() {
|
412 |
-
if (moveInterval) clearInterval(moveInterval);
|
413 |
-
moveInterval = setInterval(() => {
|
414 |
-
if (path.length === 0) {
|
415 |
-
isMoving = false;
|
416 |
-
clearInterval(moveInterval);
|
417 |
-
return;
|
418 |
-
}
|
419 |
-
const nextPos = path.shift();
|
420 |
-
characterPos = nextPos;
|
421 |
-
}, 200);
|
422 |
-
}
|
423 |
-
|
424 |
-
/* Chat / Mistral-related code */
|
425 |
-
function createLoadingIndicator() {
|
426 |
-
const loadingDiv = document.createElement('div');
|
427 |
-
loadingDiv.className = 'chat-message assistant-message';
|
428 |
-
loadingDiv.innerHTML = `
|
429 |
-
<div class="loading-dots">
|
430 |
-
<div class="dot"></div>
|
431 |
-
<div class="dot"></div>
|
432 |
-
<div class="dot"></div>
|
433 |
-
</div>
|
434 |
-
`;
|
435 |
-
return loadingDiv;
|
436 |
-
}
|
437 |
-
|
438 |
-
function addMessageToChat(role, content) {
|
439 |
-
const chatHistory = document.getElementById('chatHistory');
|
440 |
-
const messageDiv = document.createElement('div');
|
441 |
-
messageDiv.className = `chat-message ${role}-message`;
|
442 |
-
messageDiv.textContent = content;
|
443 |
-
chatHistory.appendChild(messageDiv);
|
444 |
-
chatHistory.scrollTop = chatHistory.scrollHeight;
|
445 |
-
}
|
446 |
-
|
447 |
-
async function sendMessage() {
|
448 |
-
const prompt = document.getElementById('prompt').value.trim();
|
449 |
-
if (!prompt) return;
|
450 |
-
|
451 |
-
if (!apiKey) {
|
452 |
-
alert('Please enter your Mistral API key first');
|
453 |
-
document.getElementById('apiKeyModal').style.display = 'flex';
|
454 |
-
return;
|
455 |
-
}
|
456 |
-
|
457 |
-
// Add user message to the chat
|
458 |
-
addMessageToChat('user', prompt);
|
459 |
-
document.getElementById('prompt').value = '';
|
460 |
-
|
461 |
-
// Add loading indicator
|
462 |
-
const chatHistory = document.getElementById('chatHistory');
|
463 |
-
const loadingIndicator = createLoadingIndicator();
|
464 |
-
chatHistory.appendChild(loadingIndicator);
|
465 |
-
|
466 |
-
try {
|
467 |
-
// Make call to Mistral or any AI endpoint
|
468 |
-
const response = await fetch('https://api.mistral.ai/v1/chat/completions', {
|
469 |
-
method: 'POST',
|
470 |
-
headers: {
|
471 |
-
'Content-Type': 'application/json',
|
472 |
-
'Authorization': `Bearer ${apiKey}`
|
473 |
-
},
|
474 |
-
body: JSON.stringify({
|
475 |
-
model: 'mistral-large-latest',
|
476 |
-
messages: [
|
477 |
-
{ role: 'system', content: systemPrompt },
|
478 |
-
{ role: 'user', content: prompt }
|
479 |
-
]
|
480 |
-
})
|
481 |
-
});
|
482 |
-
|
483 |
-
if (!response.ok) {
|
484 |
-
throw new Error(`HTTP error! status: ${response.status}`);
|
485 |
-
}
|
486 |
-
|
487 |
-
const data = await response.json();
|
488 |
-
const assistantResponse = data.choices[0].message.content || "";
|
489 |
-
|
490 |
-
try {
|
491 |
-
const jsonStart = assistantResponse.indexOf('{');
|
492 |
-
const jsonEnd = assistantResponse.lastIndexOf('}') + 1;
|
493 |
-
const jsonContent = assistantResponse.substring(jsonStart, jsonEnd);
|
494 |
-
const jsonResponse = JSON.parse(jsonContent);
|
495 |
-
|
496 |
-
if (jsonResponse.textMessage) {
|
497 |
-
addMessageToChat('assistant', jsonResponse.textMessage);
|
498 |
-
}
|
499 |
-
if (jsonResponse.action === 'go' && jsonResponse.to) {
|
500 |
-
moveToRoom(jsonResponse.to);
|
501 |
-
}
|
502 |
-
} catch (e) {
|
503 |
-
console.error('Error parsing JSON from response:', e);
|
504 |
-
addMessageToChat('assistant', 'Sorry, I had trouble understanding that response.');
|
505 |
-
}
|
506 |
-
} catch (error) {
|
507 |
-
addMessageToChat('assistant', `Error: ${error.message}`);
|
508 |
-
} finally {
|
509 |
-
loadingIndicator.remove();
|
510 |
-
}
|
511 |
-
}
|
512 |
-
|
513 |
-
function saveApiKey() {
|
514 |
-
const key = document.getElementById('apiKey').value.trim();
|
515 |
-
if (key) {
|
516 |
-
apiKey = key;
|
517 |
-
localStorage.setItem('mistralApiKey', key);
|
518 |
-
document.getElementById('apiKeyModal').style.display = 'none';
|
519 |
-
}
|
520 |
-
}
|
521 |
-
|
522 |
-
// Allow sending message with Enter
|
523 |
-
document.addEventListener('DOMContentLoaded', () => {
|
524 |
-
document.getElementById('prompt').addEventListener('keypress', function(e) {
|
525 |
-
if (e.key === 'Enter' && !e.shiftKey) {
|
526 |
-
e.preventDefault();
|
527 |
-
sendMessage();
|
528 |
-
}
|
529 |
-
});
|
530 |
-
|
531 |
-
if (!apiKey) {
|
532 |
-
document.getElementById('apiKeyModal').style.display = 'flex';
|
533 |
-
}
|
534 |
-
});
|
535 |
-
</script>
|
536 |
-
</body>
|
537 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/game/gameState.js
ADDED
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
// Game state management
|
3 |
+
class GameState {
|
4 |
+
constructor() {
|
5 |
+
// Initialize with rooms from apt.json
|
6 |
+
this.rooms = [
|
7 |
+
"Kichen",
|
8 |
+
"Storage",
|
9 |
+
"My Bedroom",
|
10 |
+
"Bathroom",
|
11 |
+
"Main Hallway",
|
12 |
+
"Living Room",
|
13 |
+
"Guest Bedroom",
|
14 |
+
"Dining Room",
|
15 |
+
"Office",
|
16 |
+
"North Hallway"
|
17 |
+
];
|
18 |
+
|
19 |
+
this.hideTargets = ["bed", "coat closet", "table", "shower"];
|
20 |
+
this.searchTargets = ["dresser", "stove", "desk", "cabinet", "fridge", "bookcase"];
|
21 |
+
this.items = ["lock pick", "flashlight", "knife", "remote"];
|
22 |
+
this.useTargets = ["bedroom door", "main door", "TV"];
|
23 |
+
|
24 |
+
// Track current game state
|
25 |
+
this.currentRoom = this.rooms[0];
|
26 |
+
this.inventory = [];
|
27 |
+
this.isHiding = false;
|
28 |
+
}
|
29 |
+
|
30 |
+
handleAction(action, target) {
|
31 |
+
switch (action) {
|
32 |
+
case 'go':
|
33 |
+
this.currentRoom = target;
|
34 |
+
break;
|
35 |
+
case 'hide':
|
36 |
+
this.isHiding = true;
|
37 |
+
break;
|
38 |
+
case 'search':
|
39 |
+
// Could add found items to inventory
|
40 |
+
break;
|
41 |
+
case 'use':
|
42 |
+
// Handle item usage
|
43 |
+
break;
|
44 |
+
}
|
45 |
+
}
|
46 |
+
|
47 |
+
getPrompt() {
|
48 |
+
return `You are the game master of "Get Me Out!", a single-player text-based survival game. The scenario: a player must guide their girlfriend to safety via text messages while she's being hunted by a murderous clown in their apartment. The player has access to security cameras and can give instructions through text.
|
49 |
+
|
50 |
+
Current state:
|
51 |
+
- Room: ${this.currentRoom}
|
52 |
+
- Hiding: ${this.isHiding}
|
53 |
+
- Inventory: ${this.inventory.join(", ") || "empty"}
|
54 |
+
|
55 |
+
RESPONSE FORMAT:
|
56 |
+
You must ALWAYS respond with a JSON object. The response should reflect the girlfriend's reaction to the player's message.
|
57 |
+
|
58 |
+
1. For movement instructions ("go" action):
|
59 |
+
{
|
60 |
+
"action": "go",
|
61 |
+
"to": "[room name]",
|
62 |
+
"textMessage": "[girlfriend's response]"
|
63 |
+
}
|
64 |
+
|
65 |
+
2. For any other input or unclear instructions:
|
66 |
+
{
|
67 |
+
"textMessage": "[girlfriend's response]"
|
68 |
+
}
|
69 |
+
|
70 |
+
VALID ROOMS:
|
71 |
+
Only these rooms are recognized for movement:
|
72 |
+
${this.rooms.join('\n')}
|
73 |
+
|
74 |
+
CHARACTER BEHAVIOR:
|
75 |
+
The girlfriend is aware of the danger and extremely distressed. Her text responses should be:
|
76 |
+
- Brief and urgent
|
77 |
+
- Reflect genuine fear and panic
|
78 |
+
- Written like real text messages (short, quick responses)
|
79 |
+
- No time for pleasantries or long explanations
|
80 |
+
- May include typos or rushed writing due to stress`;
|
81 |
+
}
|
82 |
+
|
83 |
+
}
|
static/game/index.html
CHANGED
@@ -3,6 +3,7 @@
|
|
3 |
<head>
|
4 |
<title>Get Me Out! - Apartment Escape Game with Chat</title>
|
5 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script>
|
|
|
6 |
<style>
|
7 |
/* Layout */
|
8 |
body {
|
@@ -47,6 +48,8 @@
|
|
47 |
padding: 20px;
|
48 |
border-top: 1px solid #ddd;
|
49 |
background: white;
|
|
|
|
|
50 |
}
|
51 |
.chat-message {
|
52 |
padding: 10px;
|
@@ -67,13 +70,11 @@
|
|
67 |
margin-right: auto;
|
68 |
border-radius: 16px 16px 16px 4px;
|
69 |
}
|
70 |
-
|
71 |
-
|
72 |
padding: 10px;
|
73 |
border: 1px solid #ddd;
|
74 |
border-radius: 4px;
|
75 |
-
margin-bottom: 10px;
|
76 |
-
resize: none;
|
77 |
font-family: inherit;
|
78 |
}
|
79 |
button {
|
@@ -140,8 +141,8 @@
|
|
140 |
<div id="chatSection">
|
141 |
<div id="chatHistory"></div>
|
142 |
<div id="chatControls">
|
143 |
-
<
|
144 |
-
<button onclick="
|
145 |
</div>
|
146 |
</div>
|
147 |
</div>
|
@@ -170,45 +171,12 @@
|
|
170 |
let isMoving = false;
|
171 |
let moveInterval = null;
|
172 |
|
|
|
|
|
|
|
173 |
// Chat variables
|
174 |
let apiKey = localStorage.getItem('mistralApiKey');
|
175 |
-
|
176 |
-
|
177 |
-
RESPONSE FORMAT:
|
178 |
-
You must ALWAYS respond with a JSON object. The response should reflect the girlfriend's reaction to the player's message.
|
179 |
-
|
180 |
-
1. For movement instructions ("go" action):
|
181 |
-
{
|
182 |
-
"action": "go",
|
183 |
-
"to": "[room name]",
|
184 |
-
"textMessage": "[girlfriend's response]"
|
185 |
-
}
|
186 |
-
|
187 |
-
2. For any other input or unclear instructions:
|
188 |
-
{
|
189 |
-
"textMessage": "[girlfriend's response]"
|
190 |
-
}
|
191 |
-
|
192 |
-
VALID ROOMS:
|
193 |
-
Only these rooms are recognized for movement:
|
194 |
-
- Main Bathroom
|
195 |
-
- Guest Toilet
|
196 |
-
- Dining Room
|
197 |
-
- Kitchen
|
198 |
-
- TV Room
|
199 |
-
- Living Room
|
200 |
-
- Hallway
|
201 |
-
- Office
|
202 |
-
- Bedroom
|
203 |
-
|
204 |
-
CHARACTER BEHAVIOR:
|
205 |
-
The girlfriend is aware of the danger and extremely distressed. Her text responses should be:
|
206 |
-
- Brief and urgent
|
207 |
-
- Reflect genuine fear and panic
|
208 |
-
- Written like real text messages (short, quick responses)
|
209 |
-
- No time for pleasantries or long explanations
|
210 |
-
- May include typos or rushed writing due to stress
|
211 |
-
`;
|
212 |
|
213 |
// Load apt.json upon page start
|
214 |
async function loadAptJson() {
|
@@ -473,9 +441,12 @@ The girlfriend is aware of the danger and extremely distressed. Her text respons
|
|
473 |
messageDiv.textContent = content;
|
474 |
chatHistory.appendChild(messageDiv);
|
475 |
chatHistory.scrollTop = chatHistory.scrollHeight;
|
|
|
|
|
|
|
476 |
}
|
477 |
|
478 |
-
async function
|
479 |
const prompt = document.getElementById('prompt').value.trim();
|
480 |
if (!prompt) return;
|
481 |
|
@@ -485,17 +456,24 @@ The girlfriend is aware of the danger and extremely distressed. Her text respons
|
|
485 |
return;
|
486 |
}
|
487 |
|
488 |
-
// Add user message to the chat
|
489 |
addMessageToChat('user', prompt);
|
490 |
document.getElementById('prompt').value = '';
|
491 |
|
492 |
-
// Add loading indicator
|
493 |
const chatHistory = document.getElementById('chatHistory');
|
494 |
const loadingIndicator = createLoadingIndicator();
|
495 |
chatHistory.appendChild(loadingIndicator);
|
496 |
|
497 |
try {
|
498 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
499 |
const response = await fetch('https://api.mistral.ai/v1/chat/completions', {
|
500 |
method: 'POST',
|
501 |
headers: {
|
@@ -504,10 +482,7 @@ The girlfriend is aware of the danger and extremely distressed. Her text respons
|
|
504 |
},
|
505 |
body: JSON.stringify({
|
506 |
model: 'mistral-large-latest',
|
507 |
-
messages:
|
508 |
-
{ role: 'system', content: systemPrompt },
|
509 |
-
{ role: 'user', content: prompt }
|
510 |
-
]
|
511 |
})
|
512 |
});
|
513 |
|
@@ -516,6 +491,7 @@ The girlfriend is aware of the danger and extremely distressed. Her text respons
|
|
516 |
}
|
517 |
|
518 |
const data = await response.json();
|
|
|
519 |
const assistantResponse = data.choices[0].message.content || "";
|
520 |
|
521 |
try {
|
@@ -523,6 +499,7 @@ The girlfriend is aware of the danger and extremely distressed. Her text respons
|
|
523 |
const jsonEnd = assistantResponse.lastIndexOf('}') + 1;
|
524 |
const jsonContent = assistantResponse.substring(jsonStart, jsonEnd);
|
525 |
const jsonResponse = JSON.parse(jsonContent);
|
|
|
526 |
|
527 |
if (jsonResponse.textMessage) {
|
528 |
addMessageToChat('assistant', jsonResponse.textMessage);
|
@@ -550,12 +527,12 @@ The girlfriend is aware of the danger and extremely distressed. Her text respons
|
|
550 |
}
|
551 |
}
|
552 |
|
553 |
-
// Allow
|
554 |
document.addEventListener('DOMContentLoaded', () => {
|
555 |
document.getElementById('prompt').addEventListener('keypress', function(e) {
|
556 |
-
if (e.key === 'Enter'
|
557 |
e.preventDefault();
|
558 |
-
|
559 |
}
|
560 |
});
|
561 |
|
|
|
3 |
<head>
|
4 |
<title>Get Me Out! - Apartment Escape Game with Chat</title>
|
5 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script>
|
6 |
+
<script src="/static/game/gameState.js"></script>
|
7 |
<style>
|
8 |
/* Layout */
|
9 |
body {
|
|
|
48 |
padding: 20px;
|
49 |
border-top: 1px solid #ddd;
|
50 |
background: white;
|
51 |
+
display: flex;
|
52 |
+
gap: 10px;
|
53 |
}
|
54 |
.chat-message {
|
55 |
padding: 10px;
|
|
|
70 |
margin-right: auto;
|
71 |
border-radius: 16px 16px 16px 4px;
|
72 |
}
|
73 |
+
input {
|
74 |
+
flex: 1;
|
75 |
padding: 10px;
|
76 |
border: 1px solid #ddd;
|
77 |
border-radius: 4px;
|
|
|
|
|
78 |
font-family: inherit;
|
79 |
}
|
80 |
button {
|
|
|
141 |
<div id="chatSection">
|
142 |
<div id="chatHistory"></div>
|
143 |
<div id="chatControls">
|
144 |
+
<input type="text" id="prompt" placeholder="Type your message...">
|
145 |
+
<button onclick="Message()">▶</button>
|
146 |
</div>
|
147 |
</div>
|
148 |
</div>
|
|
|
171 |
let isMoving = false;
|
172 |
let moveInterval = null;
|
173 |
|
174 |
+
// Initialize game state
|
175 |
+
const gameState = new GameState();
|
176 |
+
|
177 |
// Chat variables
|
178 |
let apiKey = localStorage.getItem('mistralApiKey');
|
179 |
+
let chatMessages = []; // Array to store chat history
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
180 |
|
181 |
// Load apt.json upon page start
|
182 |
async function loadAptJson() {
|
|
|
441 |
messageDiv.textContent = content;
|
442 |
chatHistory.appendChild(messageDiv);
|
443 |
chatHistory.scrollTop = chatHistory.scrollHeight;
|
444 |
+
|
445 |
+
// Store message in chat history
|
446 |
+
chatMessages.push({ role, content });
|
447 |
}
|
448 |
|
449 |
+
async function Message() {
|
450 |
const prompt = document.getElementById('prompt').value.trim();
|
451 |
if (!prompt) return;
|
452 |
|
|
|
456 |
return;
|
457 |
}
|
458 |
|
|
|
459 |
addMessageToChat('user', prompt);
|
460 |
document.getElementById('prompt').value = '';
|
461 |
|
|
|
462 |
const chatHistory = document.getElementById('chatHistory');
|
463 |
const loadingIndicator = createLoadingIndicator();
|
464 |
chatHistory.appendChild(loadingIndicator);
|
465 |
|
466 |
try {
|
467 |
+
// Get last 5 messages from chat history
|
468 |
+
const recentMessages = chatMessages.slice(-5);
|
469 |
+
|
470 |
+
// Prepare messages array for Mistral
|
471 |
+
const messages = [
|
472 |
+
{ role: 'system', content: gameState.getPrompt() },
|
473 |
+
...recentMessages,
|
474 |
+
{ role: 'user', content: prompt }
|
475 |
+
];
|
476 |
+
|
477 |
const response = await fetch('https://api.mistral.ai/v1/chat/completions', {
|
478 |
method: 'POST',
|
479 |
headers: {
|
|
|
482 |
},
|
483 |
body: JSON.stringify({
|
484 |
model: 'mistral-large-latest',
|
485 |
+
messages: messages
|
|
|
|
|
|
|
486 |
})
|
487 |
});
|
488 |
|
|
|
491 |
}
|
492 |
|
493 |
const data = await response.json();
|
494 |
+
console.log('Mistral response:', data.choices[0].message.content);
|
495 |
const assistantResponse = data.choices[0].message.content || "";
|
496 |
|
497 |
try {
|
|
|
499 |
const jsonEnd = assistantResponse.lastIndexOf('}') + 1;
|
500 |
const jsonContent = assistantResponse.substring(jsonStart, jsonEnd);
|
501 |
const jsonResponse = JSON.parse(jsonContent);
|
502 |
+
console.log('Parsed JSON response:', jsonResponse);
|
503 |
|
504 |
if (jsonResponse.textMessage) {
|
505 |
addMessageToChat('assistant', jsonResponse.textMessage);
|
|
|
527 |
}
|
528 |
}
|
529 |
|
530 |
+
// Allow ing message with Enter
|
531 |
document.addEventListener('DOMContentLoaded', () => {
|
532 |
document.getElementById('prompt').addEventListener('keypress', function(e) {
|
533 |
+
if (e.key === 'Enter') {
|
534 |
e.preventDefault();
|
535 |
+
Message();
|
536 |
}
|
537 |
});
|
538 |
|
static/game/test prompt.txt
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
You are the game master of "Get Me Out!", a single-player text-based survival game. The scenario: a player must guide their girlfriend to safety via text messages while she's being hunted by a murderous clown in their apartment. The player has access to security cameras and can instructions through text.
|
2 |
+
|
3 |
+
RESPONSE FORMAT:
|
4 |
+
You must ALWAYS respond with a JSON object. The response should reflect the girlfriend's reaction to the player's message.
|
5 |
+
|
6 |
+
1. For movement instructions ("go" action):
|
7 |
+
{
|
8 |
+
"action": "go",
|
9 |
+
"to": "[room name]",
|
10 |
+
"textMessage": "[girlfriend's response]"
|
11 |
+
}
|
12 |
+
|
13 |
+
2. For any other input or unclear instructions:
|
14 |
+
{
|
15 |
+
"textMessage": "[girlfriend's response]"
|
16 |
+
}
|
17 |
+
|
18 |
+
VALID ROOMS:
|
19 |
+
Only these rooms are recognized for movement:
|
20 |
+
- Main Bathroom
|
21 |
+
- Guest Toilet
|
22 |
+
- Dining Room
|
23 |
+
- Kitchen
|
24 |
+
- TV Room
|
25 |
+
- Living Room
|
26 |
+
- Hallway
|
27 |
+
- Office
|
28 |
+
- Bedroom
|
29 |
+
|
30 |
+
CHARACTER BEHAVIOR:
|
31 |
+
The girlfriend is aware of the danger and extremely distressed. Her text responses should be:
|
32 |
+
- Brief and urgent
|
33 |
+
- Reflect genuine fear and panic
|
34 |
+
- Written like real text messages (short, quick responses)
|
35 |
+
- No time for pleasantries or long explanations
|
36 |
+
- May include typos or rushed writing due to stress
|
37 |
+
|
38 |
+
|
39 |
+
|
static/how-to-play.html
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="fr">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8" />
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
6 |
+
<title>Through Their Eyes - How to Play</title>
|
7 |
+
<link rel="stylesheet" href="/src/style.css" />
|
8 |
+
</head>
|
9 |
+
<body>
|
10 |
+
<div class="led-bar">
|
11 |
+
<div class="light-beam"></div>
|
12 |
+
</div>
|
13 |
+
|
14 |
+
<div class="background-elements">
|
15 |
+
<img src="public/blood-2.png" alt="" class="blood blood-top-left" />
|
16 |
+
<img src="public/help.png" alt="" class="blood blood-top-right" />
|
17 |
+
<img src="public/splatter.png" alt="" class="blood splatter" />
|
18 |
+
<img src="public/hand.png" alt="" class="blood blood-bottom-right" />
|
19 |
+
</div>
|
20 |
+
|
21 |
+
<main>
|
22 |
+
<div class="content">
|
23 |
+
<h1>How to Play</h1>
|
24 |
+
<!-- Contenu à ajouter -->
|
25 |
+
</div>
|
26 |
+
|
27 |
+
<a href="index.html" class="back-button">Back to Menu</a>
|
28 |
+
</main>
|
29 |
+
|
30 |
+
<script type="module" src="/src/main.js"></script>
|
31 |
+
</body>
|
32 |
+
</html>
|
static/index.html
CHANGED
@@ -1,150 +1,35 @@
|
|
1 |
<!DOCTYPE html>
|
2 |
-
<html>
|
3 |
-
<head>
|
4 |
-
<
|
5 |
-
<
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
}
|
19 |
-
|
20 |
-
.container {
|
21 |
-
text-align: center;
|
22 |
-
padding: 2rem;
|
23 |
-
max-width: 800px;
|
24 |
-
position: relative;
|
25 |
-
z-index: 1;
|
26 |
-
}
|
27 |
-
|
28 |
-
h1 {
|
29 |
-
font-size: 4rem;
|
30 |
-
margin-bottom: 1rem;
|
31 |
-
text-shadow: 0 0 10px #ff0000, 0 0 20px #ff0000;
|
32 |
-
animation: flicker 4s infinite;
|
33 |
-
letter-spacing: 2px;
|
34 |
-
}
|
35 |
-
|
36 |
-
.subtitle {
|
37 |
-
font-size: 1.2rem;
|
38 |
-
color: #bb0000;
|
39 |
-
margin-bottom: 3rem;
|
40 |
-
text-shadow: 0 0 5px #ff0000;
|
41 |
-
}
|
42 |
-
|
43 |
-
.menu {
|
44 |
-
display: grid;
|
45 |
-
gap: 2rem;
|
46 |
-
margin-top: 2rem;
|
47 |
-
}
|
48 |
-
|
49 |
-
.menu-item {
|
50 |
-
background: rgba(20, 20, 20, 0.8);
|
51 |
-
padding: 1.5rem;
|
52 |
-
border-radius: 5px;
|
53 |
-
cursor: pointer;
|
54 |
-
transition: all 0.3s ease;
|
55 |
-
text-decoration: none;
|
56 |
-
color: white;
|
57 |
-
border: 1px solid #440000;
|
58 |
-
box-shadow: 0 0 10px rgba(255, 0, 0, 0.2);
|
59 |
-
}
|
60 |
-
|
61 |
-
.menu-item:hover {
|
62 |
-
transform: scale(1.05);
|
63 |
-
background: rgba(40, 0, 0, 0.8);
|
64 |
-
border-color: #ff0000;
|
65 |
-
box-shadow: 0 0 20px rgba(255, 0, 0, 0.4);
|
66 |
-
}
|
67 |
-
|
68 |
-
.menu-item h2 {
|
69 |
-
margin: 0 0 0.5rem 0;
|
70 |
-
font-size: 1.8rem;
|
71 |
-
color: #ff0000;
|
72 |
-
}
|
73 |
-
|
74 |
-
.menu-item p {
|
75 |
-
margin: 0;
|
76 |
-
color: #cccccc;
|
77 |
-
font-size: 1rem;
|
78 |
-
}
|
79 |
-
|
80 |
-
@keyframes flicker {
|
81 |
-
0%, 100% { opacity: 1; }
|
82 |
-
92% { opacity: 1; }
|
83 |
-
93% { opacity: 0.8; }
|
84 |
-
94% { opacity: 1; }
|
85 |
-
95% { opacity: 0.9; }
|
86 |
-
96% { opacity: 1; }
|
87 |
-
}
|
88 |
-
|
89 |
-
/* Blood drips */
|
90 |
-
.blood-drip {
|
91 |
-
position: fixed;
|
92 |
-
top: 0;
|
93 |
-
width: 3px;
|
94 |
-
height: 60px;
|
95 |
-
background: linear-gradient(to bottom, transparent, rgba(255, 0, 0, 0.8));
|
96 |
-
animation: drip 4s infinite;
|
97 |
-
opacity: 0.7;
|
98 |
-
}
|
99 |
-
|
100 |
-
@keyframes drip {
|
101 |
-
0% {
|
102 |
-
transform: translateY(-60px);
|
103 |
-
opacity: 0;
|
104 |
-
}
|
105 |
-
50% {
|
106 |
-
opacity: 0.7;
|
107 |
-
}
|
108 |
-
100% {
|
109 |
-
transform: translateY(100vh);
|
110 |
-
opacity: 0;
|
111 |
-
}
|
112 |
-
}
|
113 |
-
|
114 |
-
|
115 |
-
</style>
|
116 |
-
</head>
|
117 |
-
<body>
|
118 |
-
<!-- Blood drip effects -->
|
119 |
-
<div class="blood-drip" style="left: 15%"></div>
|
120 |
-
<div class="blood-drip" style="left: 35%; animation-delay: 1.5s"></div>
|
121 |
-
<div class="blood-drip" style="left: 55%; animation-delay: 2.5s"></div>
|
122 |
-
<div class="blood-drip" style="left: 75%; animation-delay: 1s"></div>
|
123 |
-
<div class="blood-drip" style="left: 95%; animation-delay: 2s"></div>
|
124 |
-
|
125 |
-
<div class="container">
|
126 |
-
<h1>THE LAST MESSAGE</h1>
|
127 |
-
<div class="subtitle">Will your final words save them?</div>
|
128 |
-
|
129 |
-
<div class="menu">
|
130 |
-
<a href="game/" class="menu-item">
|
131 |
-
<h2>📱 PLAY THE STORY</h2>
|
132 |
-
<p>Experience the original nightmare</p>
|
133 |
-
</a>
|
134 |
-
|
135 |
-
<a href="maker/" class="menu-item">
|
136 |
-
<h2>🏗️ CREATE & PLAY</h2>
|
137 |
-
<p>Design and play your own horror scenario</p>
|
138 |
-
</a>
|
139 |
-
</div>
|
140 |
-
|
141 |
</div>
|
142 |
|
143 |
-
<
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
</
|
150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
<!DOCTYPE html>
|
2 |
+
<html lang="fr">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8" />
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
6 |
+
<title>Through Their Eyes</title>
|
7 |
+
<link rel="stylesheet" href="assets/css/style.css">
|
8 |
+
</head>
|
9 |
+
<body>
|
10 |
+
<div class="led-bar">
|
11 |
+
<div class="light-beam"></div>
|
12 |
+
</div>
|
13 |
+
<div class="background-elements">
|
14 |
+
<img src="assets/img/blood-2.png" alt="" class="blood blood-top-left" />
|
15 |
+
<img src="assets/img/help.png" alt="" class="blood blood-top-right" />
|
16 |
+
<img src="assets/img/splatter.png" alt="" class="blood splatter" />
|
17 |
+
<img src="assets/img/hand.png" alt="" class="blood blood-bottom-right" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
</div>
|
19 |
|
20 |
+
<main>
|
21 |
+
<div class="logo">
|
22 |
+
<img src="assets/img/logo.png" alt="Through Their Eyes" />
|
23 |
+
</div>
|
24 |
+
|
25 |
+
<nav class="menu">
|
26 |
+
<a href="game/" class="menu-item new-game">NEW GAME</a>
|
27 |
+
<a href="how-to-play.html" class="menu-item how-to-play">HOW TO PLAY</a>
|
28 |
+
<a href="maker/" class="menu-item level-maker">LEVEL MAKER</a>
|
29 |
+
</nav>
|
30 |
+
<div class="character">
|
31 |
+
<img src="assets/img/perso.png" alt="Character" />
|
32 |
+
</div>
|
33 |
+
</main>
|
34 |
+
</body>
|
35 |
+
</html>
|
static/mistral/index.html
ADDED
@@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Mistral Chat Test Bed</title>
|
7 |
+
<style>
|
8 |
+
body {
|
9 |
+
font-family: Arial, sans-serif;
|
10 |
+
max-width: 800px;
|
11 |
+
margin: 20px auto;
|
12 |
+
padding: 0 20px;
|
13 |
+
}
|
14 |
+
|
15 |
+
/* System Prompt Section */
|
16 |
+
#systemPromptSection {
|
17 |
+
margin-bottom: 20px;
|
18 |
+
padding: 10px;
|
19 |
+
background: #f8f9fa;
|
20 |
+
border-radius: 4px;
|
21 |
+
}
|
22 |
+
|
23 |
+
textarea {
|
24 |
+
width: 100%;
|
25 |
+
height: 150px;
|
26 |
+
margin: 10px 0;
|
27 |
+
padding: 8px;
|
28 |
+
font-family: monospace;
|
29 |
+
}
|
30 |
+
|
31 |
+
/* Chat Section */
|
32 |
+
#chatHistory {
|
33 |
+
max-height: 400px;
|
34 |
+
overflow-y: auto;
|
35 |
+
margin: 20px 0;
|
36 |
+
padding: 10px;
|
37 |
+
border: 1px solid #ddd;
|
38 |
+
border-radius: 4px;
|
39 |
+
}
|
40 |
+
|
41 |
+
.chat-message {
|
42 |
+
padding: 10px;
|
43 |
+
margin: 5px 0;
|
44 |
+
border-radius: 4px;
|
45 |
+
}
|
46 |
+
|
47 |
+
.user-message {
|
48 |
+
background: #e3f2fd;
|
49 |
+
}
|
50 |
+
|
51 |
+
.assistant-message {
|
52 |
+
background: #f5f5f5;
|
53 |
+
white-space: pre-wrap;
|
54 |
+
}
|
55 |
+
|
56 |
+
/* Raw JSON Section */
|
57 |
+
.raw-json {
|
58 |
+
font-family: monospace;
|
59 |
+
font-size: 0.9em;
|
60 |
+
color: #666;
|
61 |
+
margin-top: 5px;
|
62 |
+
padding: 5px;
|
63 |
+
background: #f8f9fa;
|
64 |
+
border-left: 3px solid #007bff;
|
65 |
+
}
|
66 |
+
|
67 |
+
/* Controls */
|
68 |
+
#controls {
|
69 |
+
display: flex;
|
70 |
+
gap: 10px;
|
71 |
+
margin: 20px 0;
|
72 |
+
}
|
73 |
+
|
74 |
+
input[type="text"] {
|
75 |
+
flex: 1;
|
76 |
+
padding: 8px;
|
77 |
+
}
|
78 |
+
|
79 |
+
button {
|
80 |
+
padding: 8px 16px;
|
81 |
+
background: #007bff;
|
82 |
+
color: white;
|
83 |
+
border: none;
|
84 |
+
border-radius: 4px;
|
85 |
+
cursor: pointer;
|
86 |
+
}
|
87 |
+
|
88 |
+
button:hover {
|
89 |
+
background: #0056b3;
|
90 |
+
}
|
91 |
+
|
92 |
+
/* Display Options */
|
93 |
+
.display-options {
|
94 |
+
margin: 10px 0;
|
95 |
+
padding: 10px;
|
96 |
+
background: #f8f9fa;
|
97 |
+
border-radius: 4px;
|
98 |
+
}
|
99 |
+
</style>
|
100 |
+
</head>
|
101 |
+
<body>
|
102 |
+
<h1>Mistral Chat Test Bed</h1>
|
103 |
+
|
104 |
+
<div id="systemPromptSection">
|
105 |
+
<h3>System Prompt</h3>
|
106 |
+
<textarea id="systemPrompt">You are the game master of "Get Me Out!", a single-player text-based survival game. The scenario: a player must guide their girlfriend to safety via text messages while she's being hunted by a murderous clown in their apartment. The player has access to security cameras and can give instructions through text.
|
107 |
+
|
108 |
+
Current state:
|
109 |
+
- Room: Main Bathroom
|
110 |
+
- Hiding: false
|
111 |
+
- Inventory: empty
|
112 |
+
|
113 |
+
RESPONSE FORMAT:
|
114 |
+
You must ALWAYS respond with a JSON object. The response should reflect the girlfriend's reaction to the player's message.
|
115 |
+
|
116 |
+
1. For movement instructions ("go" action):
|
117 |
+
{
|
118 |
+
"action": "go",
|
119 |
+
"to": "[room name]",
|
120 |
+
"textMessage": "[girlfriend's response]"
|
121 |
+
}
|
122 |
+
|
123 |
+
2. For any other input or unclear instructions:
|
124 |
+
{
|
125 |
+
"textMessage": "[girlfriend's response]"
|
126 |
+
}
|
127 |
+
|
128 |
+
VALID ROOMS:
|
129 |
+
Only these rooms are recognized for movement:
|
130 |
+
Main Bathroom
|
131 |
+
Guest Toilet
|
132 |
+
Dining Room
|
133 |
+
Kitchen
|
134 |
+
TV Room
|
135 |
+
Living Room
|
136 |
+
Hallway
|
137 |
+
Office
|
138 |
+
Bedroom
|
139 |
+
|
140 |
+
CHARACTER BEHAVIOR:
|
141 |
+
The girlfriend is aware of the danger and extremely distressed. Her text responses should be:
|
142 |
+
- Brief and urgent
|
143 |
+
- Reflect genuine fear and panic
|
144 |
+
- Written like real text messages (short, quick responses)
|
145 |
+
- No time for pleasantries or long explanations
|
146 |
+
- May include typos or rushed writing due to stress</textarea>
|
147 |
+
</div>
|
148 |
+
|
149 |
+
<div class="display-options">
|
150 |
+
<label>
|
151 |
+
<input type="checkbox" id="showRawJson" checked>
|
152 |
+
Show Raw JSON Response
|
153 |
+
</label>
|
154 |
+
</div>
|
155 |
+
|
156 |
+
<div id="chatHistory"></div>
|
157 |
+
|
158 |
+
<div id="controls">
|
159 |
+
<input type="text" id="userInput" placeholder="Type your message...">
|
160 |
+
<button onclick="sendMessage()">Send</button>
|
161 |
+
</div>
|
162 |
+
|
163 |
+
<div id="apiKeySection">
|
164 |
+
<h3>API Key</h3>
|
165 |
+
<input type="password" id="apiKey" placeholder="Enter Mistral API Key">
|
166 |
+
<button onclick="saveApiKey()">Save API Key</button>
|
167 |
+
</div>
|
168 |
+
|
169 |
+
<script>
|
170 |
+
let apiKey = localStorage.getItem('mistralApiKey');
|
171 |
+
|
172 |
+
if (apiKey) {
|
173 |
+
document.getElementById('apiKey').value = apiKey;
|
174 |
+
}
|
175 |
+
|
176 |
+
function saveApiKey() {
|
177 |
+
const key = document.getElementById('apiKey').value.trim();
|
178 |
+
if (key) {
|
179 |
+
apiKey = key;
|
180 |
+
localStorage.setItem('mistralApiKey', key);
|
181 |
+
alert('API key saved!');
|
182 |
+
}
|
183 |
+
}
|
184 |
+
|
185 |
+
function addMessage(role, content, rawJson = null) {
|
186 |
+
const chatHistory = document.getElementById('chatHistory');
|
187 |
+
const messageDiv = document.createElement('div');
|
188 |
+
messageDiv.className = `chat-message ${role}-message`;
|
189 |
+
messageDiv.textContent = content;
|
190 |
+
|
191 |
+
if (rawJson && document.getElementById('showRawJson').checked) {
|
192 |
+
const jsonDiv = document.createElement('div');
|
193 |
+
jsonDiv.className = 'raw-json';
|
194 |
+
jsonDiv.textContent = JSON.stringify(rawJson, null, 2);
|
195 |
+
messageDiv.appendChild(jsonDiv);
|
196 |
+
}
|
197 |
+
|
198 |
+
chatHistory.appendChild(messageDiv);
|
199 |
+
chatHistory.scrollTop = chatHistory.scrollHeight;
|
200 |
+
}
|
201 |
+
|
202 |
+
async function sendMessage() {
|
203 |
+
const userInput = document.getElementById('userInput');
|
204 |
+
const message = userInput.value.trim();
|
205 |
+
|
206 |
+
if (!message) return;
|
207 |
+
if (!apiKey) {
|
208 |
+
alert('Please enter your Mistral API key first');
|
209 |
+
return;
|
210 |
+
}
|
211 |
+
|
212 |
+
// Add user message to chat
|
213 |
+
addMessage('user', message);
|
214 |
+
userInput.value = '';
|
215 |
+
|
216 |
+
try {
|
217 |
+
const response = await fetch('https://api.mistral.ai/v1/chat/completions', {
|
218 |
+
method: 'POST',
|
219 |
+
headers: {
|
220 |
+
'Content-Type': 'application/json',
|
221 |
+
'Authorization': `Bearer ${apiKey}`
|
222 |
+
},
|
223 |
+
body: JSON.stringify({
|
224 |
+
model: 'mistral-large-latest',
|
225 |
+
messages: [
|
226 |
+
{
|
227 |
+
role: 'system',
|
228 |
+
content: document.getElementById('systemPrompt').value
|
229 |
+
},
|
230 |
+
{
|
231 |
+
role: 'user',
|
232 |
+
content: message
|
233 |
+
}
|
234 |
+
]
|
235 |
+
})
|
236 |
+
});
|
237 |
+
|
238 |
+
if (!response.ok) {
|
239 |
+
throw new Error(`HTTP error! status: ${response.status}`);
|
240 |
+
}
|
241 |
+
|
242 |
+
const data = await response.json();
|
243 |
+
const assistantResponse = data.choices[0].message.content;
|
244 |
+
|
245 |
+
try {
|
246 |
+
// Extract JSON from response
|
247 |
+
const jsonStart = assistantResponse.indexOf('{');
|
248 |
+
const jsonEnd = assistantResponse.lastIndexOf('}') + 1;
|
249 |
+
const jsonContent = assistantResponse.substring(jsonStart, jsonEnd);
|
250 |
+
const jsonResponse = JSON.parse(jsonContent);
|
251 |
+
|
252 |
+
// Add assistant message with both text and raw JSON
|
253 |
+
addMessage('assistant', jsonResponse.textMessage, jsonResponse);
|
254 |
+
|
255 |
+
} catch (e) {
|
256 |
+
console.error('Error parsing JSON from response:', e);
|
257 |
+
addMessage('assistant', assistantResponse);
|
258 |
+
}
|
259 |
+
|
260 |
+
} catch (error) {
|
261 |
+
addMessage('assistant', `Error: ${error.message}`);
|
262 |
+
}
|
263 |
+
}
|
264 |
+
|
265 |
+
// Allow sending message with Enter key
|
266 |
+
document.getElementById('userInput').addEventListener('keypress', function(e) {
|
267 |
+
if (e.key === 'Enter') {
|
268 |
+
e.preventDefault();
|
269 |
+
sendMessage();
|
270 |
+
}
|
271 |
+
});
|
272 |
+
</script>
|
273 |
+
</body>
|
274 |
+
</html>
|
test/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|