awacke1 commited on
Commit
8b30964
·
verified ·
1 Parent(s): ead82da

Create game.js

Browse files
Files changed (1) hide show
  1. game.js +390 -0
game.js ADDED
@@ -0,0 +1,390 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as THREE from 'three';
2
+ // Optional: Add OrbitControls for debugging/viewing scene
3
+ // import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
4
+
5
+ // --- DOM Elements ---
6
+ const sceneContainer = document.getElementById('scene-container');
7
+ const storyTitleElement = document.getElementById('story-title');
8
+ const storyContentElement = document.getElementById('story-content');
9
+ const choicesElement = document.getElementById('choices');
10
+ const statsElement = document.getElementById('stats-display');
11
+ const inventoryElement = document.getElementById('inventory-display');
12
+
13
+ // --- Three.js Setup ---
14
+ let scene, camera, renderer, cube; // Basic scene object
15
+ // let controls; // Optional OrbitControls
16
+
17
+ function initThreeJS() {
18
+ // Scene
19
+ scene = new THREE.Scene();
20
+ scene.background = new THREE.Color(0x222222); // Match body background
21
+
22
+ // Camera
23
+ camera = new THREE.PerspectiveCamera(75, sceneContainer.clientWidth / sceneContainer.clientHeight, 0.1, 1000);
24
+ camera.position.z = 5;
25
+
26
+ // Renderer
27
+ renderer = new THREE.WebGLRenderer({ antialias: true });
28
+ renderer.setSize(sceneContainer.clientWidth, sceneContainer.clientHeight);
29
+ sceneContainer.appendChild(renderer.domElement);
30
+
31
+ // Basic Lighting
32
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); // Soft white light
33
+ scene.add(ambientLight);
34
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
35
+ directionalLight.position.set(5, 10, 7.5);
36
+ scene.add(directionalLight);
37
+
38
+ // Basic Object (Placeholder for scene illustration)
39
+ const geometry = new THREE.BoxGeometry(1, 1, 1);
40
+ const material = new THREE.MeshStandardMaterial({ color: 0xcccccc }); // Default color
41
+ cube = new THREE.Mesh(geometry, material);
42
+ scene.add(cube);
43
+
44
+ // Optional Controls
45
+ // controls = new OrbitControls(camera, renderer.domElement);
46
+ // controls.enableDamping = true;
47
+
48
+ // Handle Resize
49
+ window.addEventListener('resize', onWindowResize, false);
50
+
51
+ // Start Animation Loop
52
+ animate();
53
+ }
54
+
55
+ function onWindowResize() {
56
+ if (!renderer || !camera) return;
57
+ camera.aspect = sceneContainer.clientWidth / sceneContainer.clientHeight;
58
+ camera.updateProjectionMatrix();
59
+ renderer.setSize(sceneContainer.clientWidth, sceneContainer.clientHeight);
60
+ }
61
+
62
+ function animate() {
63
+ requestAnimationFrame(animate);
64
+
65
+ // Simple animation
66
+ if (cube) {
67
+ cube.rotation.x += 0.005;
68
+ cube.rotation.y += 0.005;
69
+ }
70
+
71
+ // if (controls) controls.update(); // If using OrbitControls
72
+
73
+ if (renderer && scene && camera) {
74
+ renderer.render(scene, camera);
75
+ }
76
+ }
77
+
78
+ // --- Game Data (Ported from Python, simplified for now) ---
79
+ const gameData = {
80
+ "1": {
81
+ title: "The Beginning",
82
+ content: `<p>The Evil Power Master has been terrorizing the land... You stand at the entrance to Silverhold, ready to begin your quest.</p><p>How will you prepare?</p>`,
83
+ options: [
84
+ { text: "Visit the local weaponsmith", next: 2, /* addItem: "..." */ },
85
+ { text: "Seek wisdom at the temple", next: 3, /* addItem: "..." */ },
86
+ { text: "Meet the resistance leader", next: 4, /* addItem: "..." */ }
87
+ ],
88
+ illustration: "city-gates" // Key for Three.js scene
89
+ },
90
+ "2": {
91
+ title: "The Weaponsmith",
92
+ content: `<p>Gorn the weaponsmith welcomes you. "You'll need more than common steel," he says, offering weapons.</p>`,
93
+ options: [
94
+ { text: "Take the Flaming Sword", next: 5, addItem: "Flaming Sword" },
95
+ { text: "Choose the Whispering Bow", next: 5, addItem: "Whispering Bow" },
96
+ { text: "Select the Guardian Shield", next: 5, addItem: "Guardian Shield" }
97
+ ],
98
+ illustration: "weaponsmith"
99
+ },
100
+ "3": {
101
+ title: "The Ancient Temple",
102
+ content: `<p>High Priestess Alara greets you. "Prepare your mind and spirit." She offers to teach you a secret art.</p>`,
103
+ options: [
104
+ { text: "Learn Healing Light", next: 5, addItem: "Healing Light Spell" },
105
+ { text: "Master Shield of Faith", next: 5, addItem: "Shield of Faith Spell" },
106
+ { text: "Study Binding Runes", next: 5, addItem: "Binding Runes Scroll" }
107
+ ],
108
+ illustration: "temple"
109
+ },
110
+ "4": {
111
+ title: "The Resistance Leader",
112
+ content: `<p>Lyra, the resistance leader, shows you a map. "His fortress has three possible entry points." She offers an item.</p>`,
113
+ options: [
114
+ { text: "Take the Secret Tunnel Map", next: 5, addItem: "Secret Tunnel Map" },
115
+ { text: "Accept Poison Daggers", next: 5, addItem: "Poison Daggers" },
116
+ { text: "Choose the Master Key", next: 5, addItem: "Master Key" }
117
+ ],
118
+ illustration: "resistance-meeting"
119
+ },
120
+ "5": {
121
+ title: "The Journey Begins",
122
+ content: `<p>You leave Silverhold and enter the corrupted Shadowwood Forest. Strange sounds echo. Which path will you take?</p>`,
123
+ options: [
124
+ { text: "Take the main road", next: 6 }, // Leads to page 6 (Ambush)
125
+ { text: "Follow the river path", next: 7 }, // Leads to page 7 (River Spirit)
126
+ { text: "Brave the ruins shortcut", next: 8 } // Leads to page 8 (Ruins)
127
+ ],
128
+ illustration: "shadowwood-forest" // Key for Three.js scene
129
+ // Add more pages here...
130
+ },
131
+ // Add placeholder pages 6, 7, 8 etc. to continue the story
132
+ "6": {
133
+ title: "Ambush!",
134
+ content: "<p>Scouts jump out! 'Surrender!'</p>",
135
+ options: [{ text: "Fight!", next: 9 }, { text: "Try to flee!", next: 10 }], // Example links
136
+ illustration: "road-ambush"
137
+ },
138
+ // ... Add many more pages based on your Python data ...
139
+ "9": { // Example continuation
140
+ title: "Victory!",
141
+ content: "<p>You defeat the scouts and continue.</p>",
142
+ options: [{ text: "Proceed to the fortress plains", next: 15 }],
143
+ illustration: "forest-edge"
144
+ },
145
+ "10": { // Example continuation
146
+ title: "Captured!",
147
+ content: "<p>You failed to escape and are captured!</p>",
148
+ options: [{ text: "Accept fate (for now)", next: 20 }], // Go to prison wagon page
149
+ illustration: "prisoner-cell"
150
+ },
151
+ // Game Over placeholder
152
+ "99": {
153
+ title: "Game Over",
154
+ content: "<p>Your adventure ends here.</p>",
155
+ options: [{ text: "Restart", next: 1 }], // Link back to start
156
+ illustration: "game-over",
157
+ gameOver: true
158
+ }
159
+ };
160
+
161
+ const itemsData = { // Simplified item data
162
+ "Flaming Sword": { type: "weapon", description: "A fiery blade" },
163
+ "Whispering Bow": { type: "weapon", description: "A silent bow" },
164
+ "Guardian Shield": { type: "armor", description: "A protective shield" },
165
+ "Healing Light Spell": { type: "spell", description: "Mends minor wounds" },
166
+ "Shield of Faith Spell": { type: "spell", description: "Temporary shield" },
167
+ "Binding Runes Scroll": { type: "spell", description: "Binds an enemy" },
168
+ "Secret Tunnel Map": { type: "quest", description: "Shows a hidden path" },
169
+ "Poison Daggers": { type: "weapon", description: "Daggers with poison" },
170
+ "Master Key": { type: "quest", description: "Unlocks many doors" },
171
+ // Add other items...
172
+ };
173
+
174
+ // --- Game State ---
175
+ let gameState = {
176
+ currentPageId: 1,
177
+ inventory: [],
178
+ stats: {
179
+ courage: 7,
180
+ wisdom: 5,
181
+ strength: 6,
182
+ hp: 30,
183
+ maxHp: 30
184
+ }
185
+ };
186
+
187
+ // --- Game Logic Functions ---
188
+
189
+ function startGame() {
190
+ gameState = { // Reset state
191
+ currentPageId: 1,
192
+ inventory: [],
193
+ stats: { courage: 7, wisdom: 5, strength: 6, hp: 30, maxHp: 30 }
194
+ };
195
+ renderPage(gameState.currentPageId);
196
+ }
197
+
198
+ function renderPage(pageId) {
199
+ const page = gameData[pageId];
200
+ if (!page) {
201
+ console.error(`Error: Page data not found for ID: ${pageId}`);
202
+ storyTitleElement.textContent = "Error";
203
+ storyContentElement.innerHTML = "<p>Could not load page data. Adventure halted.</p>";
204
+ choicesElement.innerHTML = '<button class="choice-button" onclick="handleChoice(1)">Restart</button>'; // Provide restart option
205
+ updateScene('error'); // Show error scene
206
+ return;
207
+ }
208
+
209
+ // Update UI
210
+ storyTitleElement.textContent = page.title || "Untitled Page";
211
+ storyContentElement.innerHTML = page.content || "<p>...</p>";
212
+ updateStatsDisplay();
213
+ updateInventoryDisplay();
214
+
215
+ // Update Choices
216
+ choicesElement.innerHTML = ''; // Clear old choices
217
+ if (page.options && page.options.length > 0) {
218
+ page.options.forEach(option => {
219
+ const button = document.createElement('button');
220
+ button.classList.add('choice-button');
221
+ button.textContent = option.text;
222
+
223
+ // Check requirements (basic check for now)
224
+ let requirementMet = true;
225
+ if (option.requireItem && !gameState.inventory.includes(option.requireItem)) {
226
+ requirementMet = false;
227
+ button.title = `Requires: ${option.requireItem}`; // Tooltip
228
+ button.disabled = true;
229
+ }
230
+ // Add requireAnyItem check here later if needed
231
+
232
+ if (requirementMet) {
233
+ // Store data needed for handling the choice
234
+ button.dataset.nextPage = option.next;
235
+ if (option.addItem) {
236
+ button.dataset.addItem = option.addItem;
237
+ }
238
+ // Add other potential effects as data attributes if needed
239
+
240
+ button.onclick = () => handleChoiceClick(button.dataset);
241
+ }
242
+
243
+ choicesElement.appendChild(button);
244
+ });
245
+ } else if (page.gameOver) {
246
+ const button = document.createElement('button');
247
+ button.classList.add('choice-button');
248
+ button.textContent = "Restart Adventure";
249
+ button.dataset.nextPage = 1; // Restart goes to page 1
250
+ button.onclick = () => handleChoiceClick(button.dataset);
251
+ choicesElement.appendChild(button);
252
+ } else {
253
+ choicesElement.innerHTML = '<p><i>No further options available from here.</i></p>';
254
+ const button = document.createElement('button');
255
+ button.classList.add('choice-button');
256
+ button.textContent = "Restart Adventure";
257
+ button.dataset.nextPage = 1; // Restart goes to page 1
258
+ button.onclick = () => handleChoiceClick(button.dataset);
259
+ choicesElement.appendChild(button);
260
+ }
261
+
262
+
263
+ // Update 3D Scene
264
+ updateScene(page.illustration || 'default');
265
+ }
266
+
267
+ function handleChoiceClick(dataset) {
268
+ const nextPageId = parseInt(dataset.nextPage); // Ensure it's a number
269
+ const itemToAdd = dataset.addItem;
270
+
271
+ if (isNaN(nextPageId)) {
272
+ console.error("Invalid nextPageId:", dataset.nextPage);
273
+ return;
274
+ }
275
+
276
+ // --- Process Effects of Making the Choice ---
277
+ // Add item if specified and not already present
278
+ if (itemToAdd && !gameState.inventory.includes(itemToAdd)) {
279
+ gameState.inventory.push(itemToAdd);
280
+ console.log("Added item:", itemToAdd);
281
+ }
282
+ // Add stat changes/hp loss *linked to the choice itself* here if needed
283
+
284
+ // --- Move to Next Page and Process Landing Effects ---
285
+ gameState.currentPageId = nextPageId;
286
+
287
+ const nextPageData = gameData[nextPageId];
288
+ if (nextPageData) {
289
+ // Apply HP loss defined on the *landing* page
290
+ if (nextPageData.hpLoss) {
291
+ gameState.stats.hp -= nextPageData.hpLoss;
292
+ console.log(`Lost ${nextPageData.hpLoss} HP.`);
293
+ if (gameState.stats.hp <= 0) {
294
+ console.log("Player died from HP loss!");
295
+ gameState.stats.hp = 0;
296
+ renderPage(99); // Go to a specific game over page ID
297
+ return; // Stop further processing
298
+ }
299
+ }
300
+ // Apply stat increase defined on the *landing* page
301
+ if (nextPageData.statIncrease) {
302
+ const stat = nextPageData.statIncrease.stat;
303
+ const amount = nextPageData.statIncrease.amount;
304
+ if (gameState.stats.hasOwnProperty(stat)) {
305
+ gameState.stats[stat] += amount;
306
+ console.log(`Stat ${stat} increased by ${amount}.`);
307
+ }
308
+ }
309
+ // Check if landing page is game over
310
+ if (nextPageData.gameOver) {
311
+ console.log("Reached Game Over page.");
312
+ renderPage(nextPageId);
313
+ return;
314
+ }
315
+
316
+ } else {
317
+ console.error(`Data for page ${nextPageId} not found!`);
318
+ // Optionally go to an error page or restart
319
+ renderPage(99); // Go to game over page as fallback
320
+ return;
321
+ }
322
+
323
+
324
+ // Render the new page
325
+ renderPage(nextPageId);
326
+ }
327
+
328
+
329
+ function updateStatsDisplay() {
330
+ let statsHTML = '<strong>Stats:</strong> ';
331
+ statsHTML += `<span>HP: ${gameState.stats.hp}/${gameState.stats.maxHp}</span>`;
332
+ statsHTML += `<span>Str: ${gameState.stats.strength}</span>`;
333
+ statsHTML += `<span>Wis: ${gameState.stats.wisdom}</span>`;
334
+ statsHTML += `<span>Cor: ${gameState.stats.courage}</span>`;
335
+ statsElement.innerHTML = statsHTML;
336
+ }
337
+
338
+ function updateInventoryDisplay() {
339
+ let inventoryHTML = '<strong>Inventory:</strong> ';
340
+ if (gameState.inventory.length === 0) {
341
+ inventoryHTML += '<em>Empty</em>';
342
+ } else {
343
+ gameState.inventory.forEach(item => {
344
+ const itemInfo = itemsData[item] || { type: 'unknown', description: '???' };
345
+ // Add class based on item type for styling
346
+ const itemClass = `item-${itemInfo.type || 'unknown'}`;
347
+ inventoryHTML += `<span class="${itemClass}" title="${itemInfo.description}">${item}</span>`;
348
+ });
349
+ }
350
+ inventoryElement.innerHTML = inventoryHTML;
351
+ }
352
+
353
+
354
+ function updateScene(illustrationKey) {
355
+ console.log("Updating scene for:", illustrationKey);
356
+ if (!cube) return; // Don't do anything if cube isn't initialized
357
+
358
+ // Simple scene update: Change cube color based on key
359
+ let color = 0xcccccc; // Default grey
360
+ switch (illustrationKey) {
361
+ case 'city-gates': color = 0xaaaaaa; break;
362
+ case 'weaponsmith': color = 0x8B4513; break; // Brown
363
+ case 'temple': color = 0xFFFFE0; break; // Light yellow
364
+ case 'resistance-meeting': color = 0x696969; break; // Dim grey
365
+ case 'shadowwood-forest': color = 0x228B22; break; // Forest green
366
+ case 'road-ambush': color = 0xD2691E; break; // Chocolate (dirt road)
367
+ case 'river-spirit': color = 0xADD8E6; break; // Light blue
368
+ case 'ancient-ruins': color = 0x778899; break; // Light slate grey
369
+ case 'forest-edge': color = 0x90EE90; break; // Light green
370
+ case 'prisoner-cell': color = 0x444444; break; // Dark grey
371
+ case 'game-over': color = 0xff0000; break; // Red
372
+ case 'error': color = 0xffa500; break; // Orange
373
+ default: color = 0xcccccc; break; // Default grey for unknown
374
+ }
375
+ cube.material.color.setHex(color);
376
+
377
+ // In a more complex setup, you would:
378
+ // 1. Remove old objects from the scene (scene.remove(object))
379
+ // 2. Load/create new objects based on illustrationKey
380
+ // 3. Add new objects to the scene (scene.add(newObject))
381
+ }
382
+
383
+
384
+ // --- Initialization ---
385
+ initThreeJS();
386
+ startGame(); // Start the game after setting up Three.js
387
+
388
+ // Make handleChoiceClick globally accessible IF using inline onclick
389
+ // If using addEventListener, this is not needed.
390
+ // window.handleChoiceClick = handleChoiceClick;