Spaces:
Running
Running
Create game.js
Browse files
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;
|