awacke1's picture
Update index.html
90305a5 verified
raw
history blame
13.3 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Choose Your Own Procedural Adventure</title>
<style>
body {
font-family: 'Courier New', monospace;
background-color: #222;
color: #eee;
margin: 0;
padding: 0;
overflow: hidden;
display: flex;
flex-direction: column;
height: 100vh;
}
#game-container { display: flex; flex-grow: 1; overflow: hidden; }
#scene-container {
flex-grow: 3; position: relative; border-right: 2px solid #555;
min-width: 200px; background-color: #1a1a1a; height: 100%; box-sizing: border-box;
}
#ui-container {
flex-grow: 2; padding: 20px; overflow-y: auto;
background-color: #333; min-width: 280px; height: 100%;
box-sizing: border-box; display: flex; flex-direction: column;
}
#scene-container canvas { display: block; }
#story-title {
color: #ffcc66; margin-top: 0; margin-bottom: 15px;
border-bottom: 1px solid #555; padding-bottom: 10px; font-size: 1.4em;
}
#story-content { margin-bottom: 20px; line-height: 1.6; flex-grow: 1; }
#story-content p { margin-bottom: 1em; }
#story-content p:last-child { margin-bottom: 0; }
#stats-inventory-container {
margin-bottom: 20px; padding-bottom: 15px;
border-bottom: 1px solid #555; font-size: 0.9em;
}
#stats-display, #inventory-display {
margin-bottom: 10px; line-height: 1.8;
}
#stats-display span, #inventory-display span {
display: inline-block; background-color: #444;
padding: 3px 8px; border-radius: 15px;
margin-right: 8px; margin-bottom: 5px;
border: 1px solid #666; white-space: nowrap;
}
#stats-display strong, #inventory-display strong {
color: #aaa; margin-right: 5px;
}
#inventory-display em { color: #888; font-style: normal; }
#inventory-display .item-quest { background-color: #666030; border-color: #999048;}
#inventory-display .item-weapon { background-color: #663030; border-color: #994848;}
#inventory-display .item-armor { background-color: #306630; border-color: #489948;}
#inventory-display .item-spell { background-color: #303066; border-color: #484899;}
#inventory-display .item-unknown { background-color: #555; border-color: #777;}
#choices-container {
margin-top: auto; padding-top: 15px; border-top: 1px solid #555;
}
#choices-container h3 { margin-top: 0; margin-bottom: 10px; color: #aaa; }
#choices { display: flex; flex-direction: column; gap: 10px; }
.choice-button {
display: block; width: 100%; padding: 10px 12px;
background-color: #555; color: #eee; border: 1px solid #777;
border-radius: 5px; cursor: pointer; text-align: left;
font-family: 'Courier New', monospace; font-size: 1em;
transition: background-color 0.2s, border-color 0.2s;
box-sizing: border-box;
}
.choice-button:hover:not(:disabled) {
background-color: #d4a017; color: #222; border-color: #b8860b;
}
.choice-button:disabled {
background-color: #444; color: #888; cursor: not-allowed;
border-color: #666; opacity: 0.7;
}
.roll-success {
color: #7f7; border-left: 3px solid #4a4;
padding-left: 8px; margin-bottom: 1em; font-size: 0.9em;
}
.roll-failure {
color: #f77; border-left: 3px solid #a44;
padding-left: 8px; margin-bottom: 1em; font-size: 0.9em;
}
</style>
</head>
<body>
<div id="game-container">
<div id="scene-container"></div>
<div id="ui-container">
<h2 id="story-title">Loading Adventure...</h2>
<div id="story-content">
<p>Please wait while the adventure loads.</p>
</div>
<div id="stats-inventory-container">
<div id="stats-display"></div>
<div id="inventory-display"></div>
</div>
<div id="choices-container">
<h3>What will you do?</h3>
<div id="choices"></div>
</div>
</div>
</div>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/",
"simplex-noise": "https://unpkg.com/[email protected]/simplex-noise.js"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import SimplexNoise from 'simplex-noise';
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');
let scene, camera, renderer, currentAssemblyGroup = null;
const stoneMaterial = new THREE.MeshStandardMaterial({ color: 0x888888, roughness: 0.8, metalness: 0.1 });
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x556B2F, roughness: 0.9 });
const templeMaterial = new THREE.MeshStandardMaterial({ color: 0xA99B78, roughness: 0.7, metalness: 0.1 });
const dirtMaterial = new THREE.MeshStandardMaterial({ color: 0x8B5E3C, roughness: 0.9 });
function initThreeJS() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x222222);
const w = sceneContainer.clientWidth, h = sceneContainer.clientHeight;
camera = new THREE.PerspectiveCamera(75, w/h || 1, 0.1, 1000);
camera.position.set(0,2.5,7); camera.lookAt(0,0.5,0);
renderer = new THREE.WebGLRenderer({ antialias:true });
renderer.setSize(w||400, h||300);
renderer.shadowMap.enabled = true;
sceneContainer.appendChild(renderer.domElement);
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
window.addEventListener('resize', onWindowResize);
animate();
}
function onWindowResize() {
const w = sceneContainer.clientWidth, h = sceneContainer.clientHeight;
camera.aspect = w/h; camera.updateProjectionMatrix();
renderer.setSize(w, h);
}
function animate() {
requestAnimationFrame(animate);
const t = performance.now()*0.001;
scene.traverse(o => o.userData.update && o.userData.update(t));
renderer.render(scene, camera);
}
function createMesh(geo, mat, pos={x:0,y:0,z:0}, rot={x:0,y:0,z:0}, scl={x:1,y:1,z:1}) {
const m = new THREE.Mesh(geo, mat);
m.position.set(pos.x,pos.y,pos.z);
m.rotation.set(rot.x,rot.y,rot.z);
m.scale.set(scl.x,scl.y,scl.z);
m.castShadow = m.receiveShadow = true;
return m;
}
function createProceduralTerrainAssembly({ size=80, segments=128, heightScale=8, seed=123 }) {
const noise = new SimplexNoise(seed);
const geo = new THREE.PlaneGeometry(size, size, segments, segments);
const pos = geo.attributes.position;
for (let i=0; i<pos.count; i++) {
const x=pos.getX(i), z=pos.getZ(i);
pos.setY(i, noise.noise2D(x/10,z/10)*heightScale);
}
geo.computeVertexNormals();
const mat = new THREE.MeshStandardMaterial({ color:0x556B2F, flatShading:true });
const mesh = new THREE.Mesh(geo, mat);
mesh.rotation.x = -Math.PI/2;
mesh.receiveShadow = true;
return mesh;
}
function generateDungeonGrid(cols, rows, minRoom=3) {
function split(r, d=0) {
if (d>3 || r.width<minRoom*2 || r.height<minRoom*2) return [r];
const horiz = r.width>r.height;
const max = (horiz?r.width:r.height)-minRoom;
const sc = Math.floor(Math.random()*(max-minRoom)+minRoom);
let a,b;
if(horiz){
a={x:r.x,y:r.y,width:sc,height:r.height};
b={x:r.x+sc,y:r.y,width:r.width-sc,height:r.height};
} else {
a={x:r.x,y:r.y,width:r.width,height:sc};
b={x:r.x,y:r.y+sc,width:r.width,height:r.height-sc};
}
return [...split(a,d+1),...split(b,d+1)];
}
const regions = split({x:0,y:0,width:cols,height:rows});
const grid = Array.from({length:rows},()=>Array(cols).fill(1));
regions.forEach(r=>{
const w=Math.max(2,Math.floor(r.width*0.8)), h=Math.max(2,Math.floor(r.height*0.8));
const ox=r.x+Math.floor((r.width-w)/2), oy=r.y+Math.floor((r.height-h)/2);
for(let y=oy;y<oy+h;y++)for(let x=ox;x<ox+w;x++)grid[y][x]=0;
});
return grid;
}
function createDungeonAssembly({ cols=40, rows=30, cellSize=0.8 }) {
const grid = generateDungeonGrid(cols, rows);
const g = new THREE.Group();
const wallGeo = new THREE.BoxGeometry(cellSize,cellSize,cellSize);
const floorGeo = new THREE.PlaneGeometry(cellSize,cellSize);
grid.forEach((row,y)=>{
row.forEach((c,x)=>{
const px=(x-cols/2)*cellSize, pz=(y-rows/2)*cellSize;
if(c===1) g.add(createMesh(wallGeo,stoneMaterial,{x:px,y:0.5,z:pz}));
else g.add(createMesh(floorGeo,groundMaterial,{x:px,y:0,z:pz},{x:-Math.PI/2}));
});
});
return g;
}
function createCityAssembly({ blocksX=6, blocksZ=6, blockSize=5, streetWidth=1 }) {
const g = new THREE.Group();
const buildingMat = templeMaterial.clone(), roadMat = dirtMaterial.clone();
const total = blocksX*blockSize+(blocksX+1)*streetWidth;
for(let i=0;i<=blocksX;i++){
g.add(createMesh(new THREE.PlaneGeometry(streetWidth,total),roadMat,
{x:-total/2+i*(blockSize+streetWidth),y:0.01,z:0},{x:-Math.PI/2}));
}
for(let j=0;j<=blocksZ;j++){
g.add(createMesh(new THREE.PlaneGeometry(total,streetWidth),roadMat,
{x:0,y:0.01,z:-total/2+j*(blockSize+streetWidth)},{x:-Math.PI/2}));
}
for(let i=0;i<blocksX;i++)for(let j=0;j<blocksZ;j++){
const w=Math.random()*(blockSize*0.6)+blockSize*0.2;
const d=Math.random()*(blockSize*0.6)+blockSize*0.2;
const h=Math.random()*5+2;
const bx=-total/2+streetWidth+i*(blockSize+streetWidth)+blockSize/2;
const bz=-total/2+streetWidth+j*(blockSize+streetWidth)+blockSize/2;
g.add(createMesh(new THREE.BoxGeometry(w,h,d),buildingMat,
{x:bx,y:h/2,z:bz}));
}
return g;
}
function updateScene(key) {
if (currentAssemblyGroup) scene.remove(currentAssemblyGroup);
scene.fog = null; scene.background.set(0x222222);
camera.position.set(0,2.5,7); camera.lookAt(0,0.5,0);
let fn = () => { const g=new THREE.Group(); g.add(createMesh(new THREE.SphereGeometry(0.5,16,16),stoneMaterial,{y:0.5})); return g; };
switch(key) {
case 'procedural-terrain':
fn = () => createProceduralTerrainAssembly({size:80,segments:128,heightScale:8,seed:123});
camera.position.set(0,10,20); camera.lookAt(0,0,0);
scene.background.set(0x444444);
break;
case 'dungeon-procedural':
fn = () => createDungeonAssembly({cols:40,rows:30,cellSize:0.8});
camera.position.set(0,20,20); camera.lookAt(0,0,0);
scene.background.set(0x111111);
break;
case 'city-procedural':
fn = () => createCityAssembly({blocksX:6,blocksZ:6,blockSize:5,streetWidth:1});
camera.position.set(0,20,20); camera.lookAt(0,0,0);
scene.background.set(0x333333);
break;
default:
break;
}
currentAssemblyGroup = fn();
scene.add(currentAssemblyGroup);
}
document.addEventListener('DOMContentLoaded',()=>{
initThreeJS();
updateScene('procedural-terrain');
});
</script>
</body>
</html>