Files
kaltaquise-gamification/sde/js/world.js
2026-04-16 08:14:20 +02:00

192 lines
5.7 KiB
JavaScript

"use strict";
// ─── Path Network (orthogonal) ─────────
function genNet(li){
var cfg=LEVELS[li];
var cols=6+li*2, rows=8+li*3;
var gapX=100+rng(-10,10), gapY=80+rng(-10,10);
var offX=80, offY=80;
var mapW=offX*2+cols*gapX, mapH=offY*2+rows*gapY;
var grid=[];
var nodes=[];
var nid=0;
for(var r=0;r<=rows;r++){
grid[r]=[];
for(var c=0;c<=cols;c++){
var keep = r===0||r===rows||c===0||c===cols||
(r===rows&&c===Math.floor(cols/2))||
(r===0&&c===Math.floor(cols/2))||
(r===rows-1&&c<=1)||
(r===Math.floor(rows*0.4)&&c>=cols-1);
if(!keep && Math.random()<0.35){
grid[r][c]=null; continue;
}
var jx=(r>0&&r<rows&&c>0&&c<cols)?rng(-12,12):0;
var jy=(r>0&&r<rows&&c>0&&c<cols)?rng(-8,8):0;
var nd={x:offX+c*gapX+jx, y:offY+r*gapY+jy, id:nid, r:r, c:c, role:"path"};
nodes.push(nd);
grid[r][c]=nd;
nid++;
}
}
var edges=[];
var edgeSet={};
function addEdge(a,b){
if(!a||!b)return;
var k=Math.min(a.id,b.id)+"-"+Math.max(a.id,b.id);
if(edgeSet[k])return;
edgeSet[k]=true;
edges.push([a.id,b.id]);
}
for(var r=0;r<=rows;r++){
for(var c=0;c<=cols;c++){
if(!grid[r][c])continue;
if(c<cols&&grid[r][c+1]) addEdge(grid[r][c],grid[r][c+1]);
if(r<rows&&grid[r+1]&&grid[r+1][c]) addEdge(grid[r][c],grid[r+1][c]);
}
}
for(var d=0;d<2+li;d++){
var src=nodes[Math.floor(Math.random()*nodes.length)];
var dir=Math.floor(Math.random()*4);
var dx=[gapX,0,-gapX,0][dir], dy=[0,gapY,0,-gapY][dir];
var nx=src.x+dx+rng(-8,8), ny=src.y+dy+rng(-8,8);
if(nx>40&&nx<mapW-40&&ny>40&&ny<mapH-40){
var dn={x:nx,y:ny,id:nid,role:"deadend"};
nodes.push(dn); nid++;
edges.push([src.id,dn.id]);
}
}
var startC=Math.floor(cols/2);
var startN=grid[rows][startC];
if(!startN){
for(var cc=0;cc<=cols;cc++){
if(grid[rows][cc]){startN=grid[rows][cc];break;}
}
}
var visited={};
var queue=[startN.id];
visited[startN.id]=true;
var adj={};
edges.forEach(function(e){
if(!adj[e[0]])adj[e[0]]=[];
if(!adj[e[1]])adj[e[1]]=[];
adj[e[0]].push(e[1]);
adj[e[1]].push(e[0]);
});
while(queue.length){
var cur=queue.shift();
(adj[cur]||[]).forEach(function(nb){
if(!visited[nb]){visited[nb]=true;queue.push(nb);}
});
}
nodes.forEach(function(n){
if(visited[n.id])return;
var best=null,bd=Infinity;
nodes.forEach(function(m){
if(!visited[m.id])return;
var d=dist(n,m);
if(d<bd){bd=d;best=m;}
});
if(best){
edges.push([n.id,best.id]);
if(!adj[n.id])adj[n.id]=[];
if(!adj[best.id])adj[best.id]=[];
adj[n.id].push(best.id);
adj[best.id].push(n.id);
visited[n.id]=true;
}
});
var peakC=Math.floor(cols/2);
var peakN=grid[0][peakC]||nodes[0];
var labN=grid[rows-1]?grid[rows-1][0]||grid[rows][0]||nodes[1]:nodes[1];
if(labN.id===startN.id){
for(var ci=0;ci<=cols;ci++){
if(grid[rows]&&grid[rows][ci]&&grid[rows][ci].id!==startN.id){labN=grid[rows][ci];break;}
}
}
var apoRow=Math.floor(rows*0.4);
var apoN=grid[apoRow]?grid[apoRow][cols]||grid[apoRow][cols-1]||nodes[3]:nodes[3];
peakN.role="peak"; startN.role="start"; labN.role="lab"; apoN.role="apo";
return {nodes:nodes,edges:edges,mapW:mapW,mapH:mapH,
peak:peakN,start:startN,lab:labN,apo:apoN};
}
// ─── Vegetation ─────────
function genVeg(net){
var trees=[],bushes=[];
var segs=net.edges.map(function(e){return{a:net.nodes[e[0]],b:net.nodes[e[1]]};});
function nearPath(x,y,minD){
for(var i=0;i<segs.length;i++){
var s=segs[i],ax=s.a.x,ay=s.a.y,bx=s.b.x,by=s.b.y;
var dx=bx-ax,dy=by-ay,l2=dx*dx+dy*dy;
var t=l2===0?0:clamp(((x-ax)*dx+(y-ay)*dy)/l2,0,1);
var d=Math.sqrt((x-(ax+t*dx))**2+(y-(ay+t*dy))**2);
if(d<minD)return true;
}
return false;
}
for(var i=0;i<500;i++){
var vx=rng(15,net.mapW-15), vy=rng(15,net.mapH-15);
if(nearPath(vx,vy,45))continue;
if(Math.random()<0.5){
trees.push({x:vx,y:vy,sz:rng(14,30),shade:Math.floor(rng(0,4)),trunk:rng(10,18)});
}else{
bushes.push({x:vx,y:vy,sz:rng(6,16),shade:Math.floor(rng(0,3))});
}
}
return{trees:trees,bushes:bushes};
}
// ─── Stones on paths ────
function genStones(net,count,li){
var stones=[];
var cIdx=Math.floor(Math.random()*count);
var segs=net.edges.map(function(e){return{a:net.nodes[e[0]],b:net.nodes[e[1]]};});
for(var i=0;i<count;i++){
var pt,tries=0;
do{
var s=segs[Math.floor(Math.random()*segs.length)];
var t=rng(0.2,0.8);
pt={x:lerp(s.a.x,s.b.x,t),y:lerp(s.a.y,s.b.y,t)};
tries++;
}while(tries<100&&(
dist(pt,net.lab)<70||dist(pt,net.start)<60||dist(pt,net.peak)<70||dist(pt,net.apo)<70||
stones.some(function(s2){return dist(pt,s2)<55;})
));
var isC=i===cIdx;
stones.push({id:i,x:pt.x,y:pt.y,isCystine:isC,
color:isC?CYSTINE_COL:STONE_COLS[i%STONE_COLS.length],
scanned:false,rolling:false,shape:Math.floor(Math.random()*3),
rollAngle:0});
}
var cs=stones[cIdx];
var hints=[];
for(var h=0;h<3+li;h++){
hints.push({x:cs.x+rng(-65,65),y:cs.y+rng(-65,65),phase:rng(0,Math.PI*2)});
}
return{stones:stones,hints:hints};
}
// ─── Closest point on path network ────
function closestOnPaths(px,py,edges,nodes){
var best=Infinity,bx=px,by=py;
for(var i=0;i<edges.length;i++){
var a=nodes[edges[i][0]],b=nodes[edges[i][1]];
var dx=b.x-a.x,dy=b.y-a.y,l2=dx*dx+dy*dy;
var t=l2===0?0:clamp(((px-a.x)*dx+(py-a.y)*dy)/l2,0,1);
var cx=a.x+t*dx,cy=a.y+t*dy;
var d=(px-cx)**2+(py-cy)**2;
if(d<best){best=d;bx=cx;by=cy;}
}
return{x:bx,y:by,d:Math.sqrt(best)};
}