ChooseYourOwnAdventure / backup2.game.js
awacke1's picture
Rename game.js to backup2.game.js
362ecb9 verified
import * as THREE from 'three';
// Optional: Add OrbitControls for debugging/viewing scene
// import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// --- DOM Elements ---
const sceneContainer = document.getElementById('scene-container');
const storyTitleElement = document.getElementById('story-title');
const storyContentElement = document.getElementById('story-content');
const choicesElement = document.getElementById('choices');
const statsElement = document.getElementById('stats-display');
const inventoryElement = document.getElementById('inventory-display');
// --- Three.js Setup ---
let scene, camera, renderer, cube; // Basic scene object
// let controls; // Optional OrbitControls
function initThreeJS() {
// Scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x222222); // Match body background
// Camera
camera = new THREE.PerspectiveCamera(75, sceneContainer.clientWidth / sceneContainer.clientHeight, 0.1, 1000);
camera.position.z = 5;
// Renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(sceneContainer.clientWidth, sceneContainer.clientHeight);
sceneContainer.appendChild(renderer.domElement);
// Basic Lighting
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); // Soft white light
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
directionalLight.position.set(5, 10, 7.5);
scene.add(directionalLight);
// Basic Object (Placeholder for scene illustration)
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0xcccccc }); // Default color
cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// Optional Controls
// controls = new OrbitControls(camera, renderer.domElement);
// controls.enableDamping = true;
// Handle Resize
window.addEventListener('resize', onWindowResize, false);
// Start Animation Loop
animate();
}
function onWindowResize() {
if (!renderer || !camera) return;
camera.aspect = sceneContainer.clientWidth / sceneContainer.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(sceneContainer.clientWidth, sceneContainer.clientHeight);
}
function animate() {
requestAnimationFrame(animate);
// Simple animation
if (cube) {
cube.rotation.x += 0.005;
cube.rotation.y += 0.005;
}
// if (controls) controls.update(); // If using OrbitControls
if (renderer && scene && camera) {
renderer.render(scene, camera);
}
}
// --- Game Data (Ported from Python, simplified for now) ---
const gameData = {
"1": {
title: "The Beginning",
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>`,
options: [
{ text: "Visit the local weaponsmith", next: 2, /* addItem: "..." */ },
{ text: "Seek wisdom at the temple", next: 3, /* addItem: "..." */ },
{ text: "Meet the resistance leader", next: 4, /* addItem: "..." */ }
],
illustration: "city-gates" // Key for Three.js scene
},
"2": {
title: "The Weaponsmith",
content: `<p>Gorn the weaponsmith welcomes you. "You'll need more than common steel," he says, offering weapons.</p>`,
options: [
{ text: "Take the Flaming Sword", next: 5, addItem: "Flaming Sword" },
{ text: "Choose the Whispering Bow", next: 5, addItem: "Whispering Bow" },
{ text: "Select the Guardian Shield", next: 5, addItem: "Guardian Shield" }
],
illustration: "weaponsmith"
},
"3": {
title: "The Ancient Temple",
content: `<p>High Priestess Alara greets you. "Prepare your mind and spirit." She offers to teach you a secret art.</p>`,
options: [
{ text: "Learn Healing Light", next: 5, addItem: "Healing Light Spell" },
{ text: "Master Shield of Faith", next: 5, addItem: "Shield of Faith Spell" },
{ text: "Study Binding Runes", next: 5, addItem: "Binding Runes Scroll" }
],
illustration: "temple"
},
"4": {
title: "The Resistance Leader",
content: `<p>Lyra, the resistance leader, shows you a map. "His fortress has three possible entry points." She offers an item.</p>`,
options: [
{ text: "Take the Secret Tunnel Map", next: 5, addItem: "Secret Tunnel Map" },
{ text: "Accept Poison Daggers", next: 5, addItem: "Poison Daggers" },
{ text: "Choose the Master Key", next: 5, addItem: "Master Key" }
],
illustration: "resistance-meeting"
},
"5": {
title: "The Journey Begins",
content: `<p>You leave Silverhold and enter the corrupted Shadowwood Forest. Strange sounds echo. Which path will you take?</p>`,
options: [
{ text: "Take the main road", next: 6 }, // Leads to page 6 (Ambush)
{ text: "Follow the river path", next: 7 }, // Leads to page 7 (River Spirit)
{ text: "Brave the ruins shortcut", next: 8 } // Leads to page 8 (Ruins)
],
illustration: "shadowwood-forest" // Key for Three.js scene
// Add more pages here...
},
// Add placeholder pages 6, 7, 8 etc. to continue the story
"6": {
title: "Ambush!",
content: "<p>Scouts jump out! 'Surrender!'</p>",
options: [{ text: "Fight!", next: 9 }, { text: "Try to flee!", next: 10 }], // Example links
illustration: "road-ambush"
},
// ... Add many more pages based on your Python data ...
"9": { // Example continuation
title: "Victory!",
content: "<p>You defeat the scouts and continue.</p>",
options: [{ text: "Proceed to the fortress plains", next: 15 }],
illustration: "forest-edge"
},
"10": { // Example continuation
title: "Captured!",
content: "<p>You failed to escape and are captured!</p>",
options: [{ text: "Accept fate (for now)", next: 20 }], // Go to prison wagon page
illustration: "prisoner-cell"
},
// Game Over placeholder
"99": {
title: "Game Over",
content: "<p>Your adventure ends here.</p>",
options: [{ text: "Restart", next: 1 }], // Link back to start
illustration: "game-over",
gameOver: true
}
};
const itemsData = { // Simplified item data
"Flaming Sword": { type: "weapon", description: "A fiery blade" },
"Whispering Bow": { type: "weapon", description: "A silent bow" },
"Guardian Shield": { type: "armor", description: "A protective shield" },
"Healing Light Spell": { type: "spell", description: "Mends minor wounds" },
"Shield of Faith Spell": { type: "spell", description: "Temporary shield" },
"Binding Runes Scroll": { type: "spell", description: "Binds an enemy" },
"Secret Tunnel Map": { type: "quest", description: "Shows a hidden path" },
"Poison Daggers": { type: "weapon", description: "Daggers with poison" },
"Master Key": { type: "quest", description: "Unlocks many doors" },
// Add other items...
};
// --- Game State ---
let gameState = {
currentPageId: 1,
inventory: [],
stats: {
courage: 7,
wisdom: 5,
strength: 6,
hp: 30,
maxHp: 30
}
};
// --- Game Logic Functions ---
function startGame() {
gameState = { // Reset state
currentPageId: 1,
inventory: [],
stats: { courage: 7, wisdom: 5, strength: 6, hp: 30, maxHp: 30 }
};
renderPage(gameState.currentPageId);
}
function renderPage(pageId) {
const page = gameData[pageId];
if (!page) {
console.error(`Error: Page data not found for ID: ${pageId}`);
storyTitleElement.textContent = "Error";
storyContentElement.innerHTML = "<p>Could not load page data. Adventure halted.</p>";
choicesElement.innerHTML = '<button class="choice-button" onclick="handleChoice(1)">Restart</button>'; // Provide restart option
updateScene('error'); // Show error scene
return;
}
// Update UI
storyTitleElement.textContent = page.title || "Untitled Page";
storyContentElement.innerHTML = page.content || "<p>...</p>";
updateStatsDisplay();
updateInventoryDisplay();
// Update Choices
choicesElement.innerHTML = ''; // Clear old choices
if (page.options && page.options.length > 0) {
page.options.forEach(option => {
const button = document.createElement('button');
button.classList.add('choice-button');
button.textContent = option.text;
// Check requirements (basic check for now)
let requirementMet = true;
if (option.requireItem && !gameState.inventory.includes(option.requireItem)) {
requirementMet = false;
button.title = `Requires: ${option.requireItem}`; // Tooltip
button.disabled = true;
}
// Add requireAnyItem check here later if needed
if (requirementMet) {
// Store data needed for handling the choice
button.dataset.nextPage = option.next;
if (option.addItem) {
button.dataset.addItem = option.addItem;
}
// Add other potential effects as data attributes if needed
button.onclick = () => handleChoiceClick(button.dataset);
}
choicesElement.appendChild(button);
});
} else if (page.gameOver) {
const button = document.createElement('button');
button.classList.add('choice-button');
button.textContent = "Restart Adventure";
button.dataset.nextPage = 1; // Restart goes to page 1
button.onclick = () => handleChoiceClick(button.dataset);
choicesElement.appendChild(button);
} else {
choicesElement.innerHTML = '<p><i>No further options available from here.</i></p>';
const button = document.createElement('button');
button.classList.add('choice-button');
button.textContent = "Restart Adventure";
button.dataset.nextPage = 1; // Restart goes to page 1
button.onclick = () => handleChoiceClick(button.dataset);
choicesElement.appendChild(button);
}
// Update 3D Scene
updateScene(page.illustration || 'default');
}
function handleChoiceClick(dataset) {
const nextPageId = parseInt(dataset.nextPage); // Ensure it's a number
const itemToAdd = dataset.addItem;
if (isNaN(nextPageId)) {
console.error("Invalid nextPageId:", dataset.nextPage);
return;
}
// --- Process Effects of Making the Choice ---
// Add item if specified and not already present
if (itemToAdd && !gameState.inventory.includes(itemToAdd)) {
gameState.inventory.push(itemToAdd);
console.log("Added item:", itemToAdd);
}
// Add stat changes/hp loss *linked to the choice itself* here if needed
// --- Move to Next Page and Process Landing Effects ---
gameState.currentPageId = nextPageId;
const nextPageData = gameData[nextPageId];
if (nextPageData) {
// Apply HP loss defined on the *landing* page
if (nextPageData.hpLoss) {
gameState.stats.hp -= nextPageData.hpLoss;
console.log(`Lost ${nextPageData.hpLoss} HP.`);
if (gameState.stats.hp <= 0) {
console.log("Player died from HP loss!");
gameState.stats.hp = 0;
renderPage(99); // Go to a specific game over page ID
return; // Stop further processing
}
}
// Apply stat increase defined on the *landing* page
if (nextPageData.statIncrease) {
const stat = nextPageData.statIncrease.stat;
const amount = nextPageData.statIncrease.amount;
if (gameState.stats.hasOwnProperty(stat)) {
gameState.stats[stat] += amount;
console.log(`Stat ${stat} increased by ${amount}.`);
}
}
// Check if landing page is game over
if (nextPageData.gameOver) {
console.log("Reached Game Over page.");
renderPage(nextPageId);
return;
}
} else {
console.error(`Data for page ${nextPageId} not found!`);
// Optionally go to an error page or restart
renderPage(99); // Go to game over page as fallback
return;
}
// Render the new page
renderPage(nextPageId);
}
function updateStatsDisplay() {
let statsHTML = '<strong>Stats:</strong> ';
statsHTML += `<span>HP: ${gameState.stats.hp}/${gameState.stats.maxHp}</span>`;
statsHTML += `<span>Str: ${gameState.stats.strength}</span>`;
statsHTML += `<span>Wis: ${gameState.stats.wisdom}</span>`;
statsHTML += `<span>Cor: ${gameState.stats.courage}</span>`;
statsElement.innerHTML = statsHTML;
}
function updateInventoryDisplay() {
let inventoryHTML = '<strong>Inventory:</strong> ';
if (gameState.inventory.length === 0) {
inventoryHTML += '<em>Empty</em>';
} else {
gameState.inventory.forEach(item => {
const itemInfo = itemsData[item] || { type: 'unknown', description: '???' };
// Add class based on item type for styling
const itemClass = `item-${itemInfo.type || 'unknown'}`;
inventoryHTML += `<span class="${itemClass}" title="${itemInfo.description}">${item}</span>`;
});
}
inventoryElement.innerHTML = inventoryHTML;
}
function updateScene(illustrationKey) {
console.log("Updating scene for:", illustrationKey);
if (!cube) return; // Don't do anything if cube isn't initialized
// Simple scene update: Change cube color based on key
let color = 0xcccccc; // Default grey
switch (illustrationKey) {
case 'city-gates': color = 0xaaaaaa; break;
case 'weaponsmith': color = 0x8B4513; break; // Brown
case 'temple': color = 0xFFFFE0; break; // Light yellow
case 'resistance-meeting': color = 0x696969; break; // Dim grey
case 'shadowwood-forest': color = 0x228B22; break; // Forest green
case 'road-ambush': color = 0xD2691E; break; // Chocolate (dirt road)
case 'river-spirit': color = 0xADD8E6; break; // Light blue
case 'ancient-ruins': color = 0x778899; break; // Light slate grey
case 'forest-edge': color = 0x90EE90; break; // Light green
case 'prisoner-cell': color = 0x444444; break; // Dark grey
case 'game-over': color = 0xff0000; break; // Red
case 'error': color = 0xffa500; break; // Orange
default: color = 0xcccccc; break; // Default grey for unknown
}
cube.material.color.setHex(color);
// In a more complex setup, you would:
// 1. Remove old objects from the scene (scene.remove(object))
// 2. Load/create new objects based on illustrationKey
// 3. Add new objects to the scene (scene.add(newObject))
}
// --- Initialization ---
initThreeJS();
startGame(); // Start the game after setting up Three.js
// Make handleChoiceClick globally accessible IF using inline onclick
// If using addEventListener, this is not needed.
// window.handleChoiceClick = handleChoiceClick;