b06aa025c1
- login.html: Schueler-Login (Klassencode+User+PW) + Lehrer-Login/Register - teacher.html: Vollstaendiges Dashboard (Klassen, Schueler, Module, Stats) - stadt.html: Komplette Simulation mit 27 Kachel-Typen + Kennwerten - tile-test.html: Spital-Sackgassen gefixt - PHP-Pages fuer alle neuen HTML-Seiten Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
298 lines
22 KiB
HTML
298 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>GeoGraSim — Stadt & Raumplanung</title>
|
|
<link rel="icon" type="image/png" href="assets/img/logo.png">
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap" rel="stylesheet">
|
|
<style>
|
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
body{font-family:'Inter',sans-serif;background:#f0efe8;color:#1a1a1a;display:flex;height:100vh;overflow:hidden}
|
|
.sidebar{width:155px;background:#fff;border-right:1px solid rgba(0,0,0,.06);display:flex;flex-direction:column;overflow-y:auto}
|
|
.sidebar-section{padding:6px}
|
|
.sidebar h3{font-size:8px;color:#4a7c8a;text-transform:uppercase;letter-spacing:.05em;margin-bottom:3px;padding:0 4px}
|
|
.tb{display:flex;align-items:center;gap:3px;padding:3px 4px;border:1.5px solid transparent;border-radius:4px;cursor:pointer;font-size:8px;font-weight:600;margin-bottom:1px;transition:all .1s}
|
|
.tb:hover{border-color:#4a7c8a;background:#f0f6f8}
|
|
.tb.on{border-color:#4a7c8a;background:#4a7c8a;color:#fff}
|
|
.tb.off{opacity:.2;pointer-events:none}
|
|
.tb .te{font-size:13px}
|
|
.tb .tc{margin-left:auto;font-size:7px;color:#c4a35a}
|
|
.center{flex:1;display:flex;flex-direction:column;min-width:0}
|
|
.topbar{display:flex;align-items:center;gap:6px;padding:4px 8px;background:#fff;border-bottom:1px solid rgba(0,0,0,.05);flex-shrink:0}
|
|
.topbar .stat{font-size:10px;font-weight:700}
|
|
.topbar .gold{color:#c4a35a}
|
|
.topbar .rnd{font-size:9px;color:#4a7c8a;background:#dae8ec;padding:2px 6px;border-radius:4px;font-weight:700;margin-left:auto}
|
|
.topbar .btn{padding:2px 8px;border:none;border-radius:4px;background:#4a7c8a;color:#fff;font-size:9px;font-weight:700;cursor:pointer}
|
|
.canvas-area{flex:1;display:flex;align-items:center;justify-content:center;background:#c8d0b8;overflow:hidden}
|
|
.canvas-area canvas{max-width:100%;max-height:100%}
|
|
.right{width:155px;background:#fff;border-left:1px solid rgba(0,0,0,.06);display:flex;flex-direction:column;overflow-y:auto;padding:6px}
|
|
.right h3{font-size:8px;color:#4a7c8a;text-transform:uppercase;letter-spacing:.05em;margin-bottom:3px}
|
|
.param{display:flex;align-items:center;gap:3px;padding:2px 0;border-bottom:1px solid rgba(0,0,0,.02)}
|
|
.param:last-child{border:none}
|
|
.param-i{font-size:10px;width:14px;text-align:center}
|
|
.param-inf{flex:1}
|
|
.param-n{font-size:7px;color:#8a8a8a}
|
|
.param-bar{height:3px;background:#e8e5dc;border-radius:1.5px;margin-top:1px;overflow:hidden}
|
|
.param-bar-f{height:100%;border-radius:1.5px;transition:width .4s}
|
|
.param-v{font-size:8px;font-weight:700;min-width:14px;text-align:right}
|
|
.msg{font-size:7px;padding:2px 4px;border-radius:3px;margin-bottom:1px;background:#dae8ec;color:#3a6470;line-height:1.2}
|
|
.msg.warn{background:#f2ddd8;color:#c07a6b}
|
|
.msg.good{background:#dceadd;color:#5a8a5e}
|
|
.ov{position:fixed;inset:0;background:rgba(0,0,0,.4);display:flex;align-items:center;justify-content:center;z-index:100}
|
|
.ov-c{background:#fff;border-radius:12px;padding:1.2rem;max-width:360px;width:90%;text-align:center}
|
|
.ov-c h2{font-size:.9rem;font-weight:800;margin-bottom:.15rem}
|
|
.ov-c p{font-size:.7rem;color:#4a4a4a;margin-bottom:.4rem}
|
|
.ov-btn{padding:.3rem .8rem;border:none;border-radius:5px;background:#4a7c8a;color:#fff;font-size:.7rem;font-weight:700;cursor:pointer}
|
|
.hidden{display:none}
|
|
@media(max-width:700px){.sidebar,.right{display:none}.center{width:100%}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="sidebar">
|
|
<div class="sidebar-section">
|
|
<h3>Straßen</h3>
|
|
<div id="road-tools"></div>
|
|
</div>
|
|
<div class="sidebar-section">
|
|
<h3>Gebäude</h3>
|
|
<div id="building-tools"></div>
|
|
</div>
|
|
<div class="sidebar-section">
|
|
<h3>Natur</h3>
|
|
<div id="nature-tools"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="center">
|
|
<div class="topbar">
|
|
<span class="stat gold">💰 <span id="h-bud">50</span></span>
|
|
<span class="stat" style="font-size:8px;color:#5a8a5e" id="h-inc"></span>
|
|
<span class="rnd" id="h-rnd">1/10</span>
|
|
<button class="btn" id="btn-next">▶ Runde</button>
|
|
</div>
|
|
<div class="canvas-area"><canvas id="cv"></canvas></div>
|
|
</div>
|
|
|
|
<div class="right">
|
|
<h3>Kennwerte</h3>
|
|
<div id="params"></div>
|
|
<h3 style="margin-top:6px">Nachrichten</h3>
|
|
<div id="msgs"></div>
|
|
</div>
|
|
|
|
<div class="ov" id="ov-intro">
|
|
<div class="ov-c">
|
|
<h2>🏘️ Stadt & Raumplanung</h2>
|
|
<p>Baue eine Siedlung! Wähle links Kacheln und klicke ins Raster. Klicke oben-links/rechts auf platzierte Kacheln zum Drehen. Jede Runde bekommst du neues Budget.</p>
|
|
<button class="ov-btn" id="btn-go">Los geht's!</button>
|
|
</div>
|
|
</div>
|
|
<div class="ov hidden" id="ov-end">
|
|
<div class="ov-c"><h2 id="end-t"></h2><p id="end-p"></p><button class="ov-btn" id="btn-again">Nochmal</button></div>
|
|
</div>
|
|
|
|
<script>
|
|
var COLS=10, ROWS=10, TS=60
|
|
var GREEN='#b8ccaa', ROAD='#2e2e2e', WHITE='#fff'
|
|
|
|
// === ALLE KACHEL-TYPEN ===
|
|
// cat: road/building/nature, conn: [N,E,S,W], emoji: fuer Gebaeude
|
|
var TILES = [
|
|
// Strassen (cat=0)
|
|
{n:'Gerade │', cat:0, conn:[1,0,1,0], cost:2, emoji:'', eff:{}},
|
|
{n:'Gerade ─', cat:0, conn:[0,1,0,1], cost:2, emoji:'', eff:{}},
|
|
{n:'Kurve ╯', cat:0, conn:[1,1,0,0], cost:2, emoji:'', eff:{}},
|
|
{n:'Kurve ╮', cat:0, conn:[0,1,1,0], cost:2, emoji:'', eff:{}},
|
|
{n:'Kurve ╭', cat:0, conn:[0,0,1,1], cost:2, emoji:'', eff:{}},
|
|
{n:'Kurve ╰', cat:0, conn:[1,0,0,1], cost:2, emoji:'', eff:{}},
|
|
{n:'T ┴', cat:0, conn:[1,1,0,1], cost:3, emoji:'', eff:{}},
|
|
{n:'T ├', cat:0, conn:[1,1,1,0], cost:3, emoji:'', eff:{}},
|
|
{n:'T ┬', cat:0, conn:[0,1,1,1], cost:3, emoji:'', eff:{}},
|
|
{n:'T ┤', cat:0, conn:[1,0,1,1], cost:3, emoji:'', eff:{}},
|
|
{n:'Kreuzung', cat:0, conn:[1,1,1,1], cost:4, emoji:'', eff:{}},
|
|
// Gebaeude (cat=1) — mit Strassen + Emojis
|
|
{n:'Wohngebiet', cat:1, conn:[1,0,1,0], cost:6, emoji:'🏠🏡🏠', emojis:[{e:'🏠',x:-0.32,y:-0.2,s:0.15},{e:'🏡',x:0.32,y:0,s:0.15},{e:'🏠',x:-0.3,y:0.2,s:0.13},{e:'🏠',x:0.3,y:0.25,s:0.12}], eff:{eco:2,sat:2,env:-1,traf:1}},
|
|
{n:'Laden', cat:1, conn:[1,0,1,0], cost:7, emoji:'🏪', emojis:[{e:'🏪',x:0.3,y:0,s:0.22}], eff:{eco:4,sat:2,env:-1,traf:2,sup:5}},
|
|
{n:'Schule', cat:1, conn:[0,1,0,1], cost:9, emoji:'🏫', emojis:[{e:'🏫',x:0,y:-0.25,s:0.22}], eff:{eco:1,sat:5,traf:1,sup:4}},
|
|
{n:'Kirche', cat:1, conn:[0,0,1,0], cost:8, emoji:'⛪', emojis:[{e:'⛪',x:0,y:-0.05,s:0.4}], eff:{eco:1,sat:4,env:1,sup:2}},
|
|
{n:'Fabrik', cat:1, conn:[0,0,1,0], cost:8, emoji:'🏭', emojis:[{e:'🏭',x:0,y:-0.05,s:0.35}], eff:{eco:6,sat:-3,env:-6,traf:4}},
|
|
{n:'Spital', cat:1, conn:[1,0,0,0], cost:11,emoji:'🏥', emojis:[{e:'🏥',x:0,y:0.1,s:0.28},{e:'🚑',x:0,y:0.35,s:0.12}], eff:{eco:2,sat:5,traf:2,sup:5}},
|
|
{n:'Feuerwehr', cat:1, conn:[0,0,1,0], cost:8, emoji:'🚒', emojis:[{e:'🏠',x:0,y:-0.15,s:0.25},{e:'🚒',x:-0.15,y:0.2,s:0.18},{e:'🚒',x:0.15,y:0.2,s:0.18}], eff:{eco:1,sat:3,sup:3}},
|
|
{n:'Polizei', cat:1, conn:[1,0,0,0], cost:7, emoji:'🚔', emojis:[{e:'🏢',x:0,y:0.1,s:0.25},{e:'🚔',x:0,y:0.32,s:0.15}], eff:{eco:1,sat:3,sup:2}},
|
|
{n:'Supermarkt', cat:1, conn:[1,0,1,0], cost:9, emoji:'🏪', emojis:[{e:'🏪',x:0.3,y:-0.1,s:0.22},{e:'🚗',x:0.28,y:0.25,s:0.1},{e:'🚙',x:0.36,y:0.15,s:0.1}], eff:{eco:5,sat:3,env:-2,traf:3,sup:8}},
|
|
{n:'Hochhäuser', cat:1, conn:[1,0,1,0], cost:10,emoji:'🏢', emojis:[{e:'🏢',x:0.25,y:-0.15,s:0.16},{e:'🏢',x:0.33,y:-0.15,s:0.16},{e:'🏢',x:0.25,y:0.12,s:0.16},{e:'🏢',x:0.33,y:0.12,s:0.16}], eff:{eco:4,env:-2,traf:3}},
|
|
{n:'Rathaus', cat:1, conn:[0,0,1,0], cost:12,emoji:'🏛️', emojis:[{e:'🏛️',x:0,y:-0.05,s:0.35}], eff:{eco:3,sat:4,sup:3}},
|
|
{n:'Tankstelle', cat:1, conn:[1,0,1,0], cost:5, emoji:'⛽', emojis:[{e:'⛽',x:0.3,y:-0.15,s:0.14},{e:'⛽',x:0.3,y:0,s:0.14},{e:'⛽',x:0.3,y:0.15,s:0.14},{e:'🚗',x:0.38,y:0.05,s:0.1}], eff:{eco:2,env:-2,traf:1}},
|
|
{n:'Bauernhof', cat:1, conn:[0,1,0,0], cost:5, emoji:'🌾', emojis:[{e:'🏠',x:0,y:0,s:0.22},{e:'🌾',x:-0.3,y:-0.25,s:0.1},{e:'🌾',x:-0.15,y:-0.25,s:0.1},{e:'🌾',x:0,y:-0.25,s:0.1},{e:'🌾',x:0.15,y:-0.25,s:0.1},{e:'🐄',x:-0.2,y:0.25,s:0.13}], eff:{eco:3,sat:1,env:2,sup:3}},
|
|
// Natur (cat=2)
|
|
{n:'Park', cat:2, conn:[1,0,0,0], cost:4, emoji:'🌳', emojis:[{e:'🌳',x:-0.25,y:0.05,s:0.14},{e:'🌳',x:-0.05,y:0.05,s:0.14},{e:'🌳',x:0.15,y:0.05,s:0.14},{e:'🌳',x:0.3,y:0.05,s:0.13},{e:'🌷',x:-0.2,y:0.3,s:0.07},{e:'🌷',x:0,y:0.3,s:0.07},{e:'🌷',x:0.2,y:0.3,s:0.07}], eff:{sat:4,env:6,sup:1}},
|
|
{n:'Spielplatz', cat:2, conn:[1,0,0,0], cost:4, emoji:'🛝', emojis:[{e:'🛝',x:-0.15,y:0.08,s:0.18},{e:'🎠',x:0.18,y:0.08,s:0.15},{e:'⚽',x:0,y:0.3,s:0.08},{e:'🌳',x:-0.35,y:0.3,s:0.1},{e:'🌳',x:0.35,y:0.3,s:0.1}], eff:{sat:5,env:3}},
|
|
]
|
|
|
|
var grid = []
|
|
for(var r=0;r<ROWS;r++){grid[r]=[];for(var c=0;c<COLS;c++)grid[r][c]=null}
|
|
|
|
// Start: ein paar Strassen + 2 Haeuser
|
|
grid[4][4]={t:1,r:0};grid[4][5]={t:1,r:0};grid[4][6]={t:1,r:0} // Gerade E-W
|
|
grid[3][5]={t:11,r:0} // Wohngebiet
|
|
|
|
var sel=-1, budget=50, round=1, maxRounds=10, income=6, gameOver=false
|
|
var scores={eco:40,sat:40,env:55,traf:15,sup:15,reach:25}
|
|
var messages=[{text:'Willkommen! Baue Straßen und Gebäude.',type:''}]
|
|
|
|
function rotConn(c,n){var r=c.slice();for(var i=0;i<n;i++)r=[r[3],r[0],r[1],r[2]];return r}
|
|
|
|
// === DRAW TILE ===
|
|
var cv=document.getElementById('cv'), ctx=cv.getContext('2d')
|
|
cv.width=COLS*TS;cv.height=ROWS*TS
|
|
|
|
function drawTile(px,py,conn,alpha){
|
|
var S=TS,RW=S*0.4,M=S/2,HR=RW/2,lw=Math.max(1,S/50)
|
|
var N=conn[0],E=conn[1],So=conn[2],W=conn[3],sum=N+E+So+W
|
|
if(alpha<1)ctx.globalAlpha=alpha
|
|
ctx.fillStyle=GREEN;ctx.fillRect(px,py,S,S)
|
|
if(sum===1){ctx.fillStyle=ROAD;var st=Math.max(2,S*0.02)
|
|
if(N){ctx.fillRect(px+M-HR,py,RW,st);ctx.beginPath();ctx.arc(px+M,py+st,HR,0,Math.PI);ctx.fill()}
|
|
if(So){ctx.fillRect(px+M-HR,py+S-st,RW,st);ctx.beginPath();ctx.arc(px+M,py+S-st,HR,Math.PI,0);ctx.fill()}
|
|
if(E){ctx.fillRect(px+S-st,py+M-HR,st,RW);ctx.beginPath();ctx.arc(px+S-st,py+M,HR,Math.PI*1.5,Math.PI*0.5);ctx.fill()}
|
|
if(W){ctx.fillRect(px,py+M-HR,st,RW);ctx.beginPath();ctx.arc(px+st,py+M,HR,Math.PI*0.5,Math.PI*1.5);ctx.fill()}
|
|
ctx.strokeStyle=WHITE;ctx.lineWidth=lw
|
|
if(N){ctx.beginPath();ctx.moveTo(px+M-HR,py);ctx.lineTo(px+M-HR,py+st);ctx.stroke();ctx.beginPath();ctx.moveTo(px+M+HR,py);ctx.lineTo(px+M+HR,py+st);ctx.stroke();ctx.beginPath();ctx.arc(px+M,py+st,HR,0,Math.PI);ctx.stroke()}
|
|
if(So){ctx.beginPath();ctx.moveTo(px+M-HR,py+S);ctx.lineTo(px+M-HR,py+S-st);ctx.stroke();ctx.beginPath();ctx.moveTo(px+M+HR,py+S);ctx.lineTo(px+M+HR,py+S-st);ctx.stroke();ctx.beginPath();ctx.arc(px+M,py+S-st,HR,Math.PI,0);ctx.stroke()}
|
|
if(E){ctx.beginPath();ctx.moveTo(px+S,py+M-HR);ctx.lineTo(px+S-st,py+M-HR);ctx.stroke();ctx.beginPath();ctx.moveTo(px+S,py+M+HR);ctx.lineTo(px+S-st,py+M+HR);ctx.stroke();ctx.beginPath();ctx.arc(px+S-st,py+M,HR,Math.PI*1.5,Math.PI*0.5);ctx.stroke()}
|
|
if(W){ctx.beginPath();ctx.moveTo(px,py+M-HR);ctx.lineTo(px+st,py+M-HR);ctx.stroke();ctx.beginPath();ctx.moveTo(px,py+M+HR);ctx.lineTo(px+st,py+M+HR);ctx.stroke();ctx.beginPath();ctx.arc(px+st,py+M,HR,Math.PI*0.5,Math.PI*1.5);ctx.stroke()}
|
|
ctx.globalAlpha=1;return}
|
|
var isCurve=sum===2&&((N&&E)||(E&&So)||(So&&W)||(W&&N))
|
|
if(isCurve){var cx_,cy_,sa,ea
|
|
if(N&&E){cx_=px+M+HR;cy_=py+M-HR;sa=Math.PI;ea=Math.PI*0.5}
|
|
if(E&&So){cx_=px+M+HR;cy_=py+M+HR;sa=Math.PI*1.5;ea=Math.PI}
|
|
if(So&&W){cx_=px+M-HR;cy_=py+M+HR;sa=0;ea=Math.PI*1.5}
|
|
if(W&&N){cx_=px+M-HR;cy_=py+M-HR;sa=Math.PI*0.5;ea=0}
|
|
ctx.fillStyle=ROAD
|
|
if(N)ctx.fillRect(px+M-HR,py,RW,M-HR);if(So)ctx.fillRect(px+M-HR,py+M+HR,RW,M-HR)
|
|
if(E)ctx.fillRect(px+M+HR,py+M-HR,M-HR,RW);if(W)ctx.fillRect(px,py+M-HR,M-HR,RW)
|
|
ctx.beginPath();ctx.moveTo(cx_,cy_);ctx.arc(cx_,cy_,RW,sa,ea,true);ctx.closePath();ctx.fill()
|
|
ctx.strokeStyle=WHITE;ctx.lineWidth=lw;ctx.beginPath();ctx.arc(cx_,cy_,RW,sa,ea,true);ctx.stroke()
|
|
if(N){ctx.beginPath();ctx.moveTo(px+M-HR,py);ctx.lineTo(px+M-HR,cy_);ctx.stroke();ctx.beginPath();ctx.moveTo(px+M+HR,py);ctx.lineTo(px+M+HR,cy_);ctx.stroke()}
|
|
if(So){ctx.beginPath();ctx.moveTo(px+M-HR,cy_);ctx.lineTo(px+M-HR,py+S);ctx.stroke();ctx.beginPath();ctx.moveTo(px+M+HR,cy_);ctx.lineTo(px+M+HR,py+S);ctx.stroke()}
|
|
if(E){ctx.beginPath();ctx.moveTo(cx_,py+M-HR);ctx.lineTo(px+S,py+M-HR);ctx.stroke();ctx.beginPath();ctx.moveTo(cx_,py+M+HR);ctx.lineTo(px+S,py+M+HR);ctx.stroke()}
|
|
if(W){ctx.beginPath();ctx.moveTo(px,py+M-HR);ctx.lineTo(cx_,py+M-HR);ctx.stroke();ctx.beginPath();ctx.moveTo(px,py+M+HR);ctx.lineTo(cx_,py+M+HR);ctx.stroke()}
|
|
ctx.setLineDash([S*0.07,S*0.05]);ctx.lineDashOffset=-S*0.035;ctx.beginPath()
|
|
if(N&&E){ctx.moveTo(px+M,py);ctx.lineTo(px+M,cy_);ctx.arc(cx_,cy_,HR,Math.PI,Math.PI*0.5,true);ctx.lineTo(px+S,py+M)}
|
|
if(E&&So){ctx.moveTo(px+S,py+M);ctx.lineTo(cx_,py+M);ctx.arc(cx_,cy_,HR,Math.PI*1.5,Math.PI,true);ctx.lineTo(px+M,py+S)}
|
|
if(So&&W){ctx.moveTo(px+M,py+S);ctx.lineTo(px+M,cy_);ctx.arc(cx_,cy_,HR,0,Math.PI*1.5,true);ctx.lineTo(px,py+M)}
|
|
if(W&&N){ctx.moveTo(px,py+M);ctx.lineTo(cx_,py+M);ctx.arc(cx_,cy_,HR,Math.PI*0.5,0,true);ctx.lineTo(px+M,py)}
|
|
ctx.stroke();ctx.setLineDash([]);ctx.lineDashOffset=0;ctx.globalAlpha=1;return}
|
|
ctx.fillStyle=ROAD
|
|
if(N)ctx.fillRect(px+M-HR,py,RW,M+HR);if(So)ctx.fillRect(px+M-HR,py+M-HR,RW,M+HR)
|
|
if(E)ctx.fillRect(px+M-HR,py+M-HR,M+HR,RW);if(W)ctx.fillRect(px,py+M-HR,M+HR,RW)
|
|
ctx.strokeStyle=WHITE;ctx.lineWidth=lw
|
|
if(N||So){ctx.beginPath();ctx.moveTo(px+M-HR,N?py:py+M-HR);ctx.lineTo(px+M-HR,So?py+S:py+M+HR);ctx.stroke();ctx.beginPath();ctx.moveTo(px+M+HR,N?py:py+M-HR);ctx.lineTo(px+M+HR,So?py+S:py+M+HR);ctx.stroke()}
|
|
if(E||W){ctx.beginPath();ctx.moveTo(W?px:px+M-HR,py+M-HR);ctx.lineTo(E?px+S:px+M+HR,py+M-HR);ctx.stroke();ctx.beginPath();ctx.moveTo(W?px:px+M-HR,py+M+HR);ctx.lineTo(E?px+S:px+M+HR,py+M+HR);ctx.stroke()}
|
|
if((N||So)&&(E||W)){ctx.fillStyle=ROAD;ctx.fillRect(px+M-HR+1,py+M-HR-1,RW-2,lw+2);ctx.fillRect(px+M-HR+1,py+M+HR-lw,RW-2,lw+2);ctx.fillRect(px+M-HR-1,py+M-HR+1,lw+2,RW-2);ctx.fillRect(px+M+HR-lw,py+M-HR+1,lw+2,RW-2)}
|
|
ctx.strokeStyle=WHITE;ctx.lineWidth=lw;ctx.setLineDash([S*0.07,S*0.05]);ctx.lineDashOffset=-S*0.035
|
|
if(sum===2){if(N||So){ctx.beginPath();ctx.moveTo(px+M,py);ctx.lineTo(px+M,py+S);ctx.stroke()};if(E||W){ctx.beginPath();ctx.moveTo(px,py+M);ctx.lineTo(px+S,py+M);ctx.stroke()}}
|
|
else if(sum===3){if(!N){ctx.beginPath();ctx.moveTo(px,py+M);ctx.lineTo(px+S,py+M);ctx.stroke();ctx.beginPath();ctx.moveTo(px+M,py+M+HR);ctx.lineTo(px+M,py+S);ctx.stroke();ctx.setLineDash([]);ctx.lineWidth=lw*2;ctx.beginPath();ctx.moveTo(px+M-HR+3,py+M+HR+6);ctx.lineTo(px+M,py+M+HR+6);ctx.stroke();ctx.lineWidth=lw}
|
|
if(!So){ctx.beginPath();ctx.moveTo(px,py+M);ctx.lineTo(px+S,py+M);ctx.stroke();ctx.beginPath();ctx.moveTo(px+M,py);ctx.lineTo(px+M,py+M-HR);ctx.stroke();ctx.setLineDash([]);ctx.lineWidth=lw*2;ctx.beginPath();ctx.moveTo(px+M,py+M-HR-6);ctx.lineTo(px+M+HR-3,py+M-HR-6);ctx.stroke();ctx.lineWidth=lw}
|
|
if(!W){ctx.beginPath();ctx.moveTo(px+M,py);ctx.lineTo(px+M,py+S);ctx.stroke();ctx.beginPath();ctx.moveTo(px+M+HR,py+M);ctx.lineTo(px+S,py+M);ctx.stroke();ctx.setLineDash([]);ctx.lineWidth=lw*2;ctx.beginPath();ctx.moveTo(px+M+HR+6,py+M);ctx.lineTo(px+M+HR+6,py+M+HR-3);ctx.stroke();ctx.lineWidth=lw}
|
|
if(!E){ctx.beginPath();ctx.moveTo(px+M,py);ctx.lineTo(px+M,py+S);ctx.stroke();ctx.beginPath();ctx.moveTo(px,py+M);ctx.lineTo(px+M-HR,py+M);ctx.stroke();ctx.setLineDash([]);ctx.lineWidth=lw*2;ctx.beginPath();ctx.moveTo(px+M-HR-6,py+M-HR+3);ctx.lineTo(px+M-HR-6,py+M);ctx.stroke();ctx.lineWidth=lw}}
|
|
else if(sum===4){ctx.beginPath();ctx.moveTo(px+M,py);ctx.lineTo(px+M,py+S);ctx.stroke();ctx.beginPath();ctx.moveTo(px,py+M);ctx.lineTo(px+M-HR,py+M);ctx.stroke();ctx.beginPath();ctx.moveTo(px+M+HR,py+M);ctx.lineTo(px+S,py+M);ctx.stroke();ctx.setLineDash([]);ctx.lineWidth=lw*2;ctx.beginPath();ctx.moveTo(px+M-HR+6,py+M);ctx.lineTo(px+M-HR+6,py+M+HR-3);ctx.stroke();ctx.beginPath();ctx.moveTo(px+M+HR-6,py+M-HR+3);ctx.lineTo(px+M+HR-6,py+M);ctx.stroke();ctx.lineWidth=lw}
|
|
ctx.setLineDash([]);ctx.lineDashOffset=0;ctx.globalAlpha=1
|
|
}
|
|
|
|
function drawEmojis(px,py,tile){
|
|
if(!tile.emojis)return
|
|
ctx.textAlign='center';ctx.textBaseline='middle'
|
|
tile.emojis.forEach(function(em){
|
|
ctx.font=Math.round(TS*em.s)+'px serif'
|
|
ctx.fillText(em.e, px+TS/2+em.x*TS, py+TS/2+em.y*TS)
|
|
})
|
|
}
|
|
|
|
// === SIMULATION ===
|
|
function recalc(){
|
|
var eco=30,sat=35,env=55,traf=10,sup=10,reach=20
|
|
for(var r=0;r<ROWS;r++)for(var c=0;c<COLS;c++){
|
|
if(!grid[r][c])continue
|
|
var tile=TILES[grid[r][c].t],ef=tile.eff||{}
|
|
eco+=ef.eco||0;sat+=ef.sat||0;env+=ef.env||0;traf+=ef.traf||0;sup+=ef.sup||0
|
|
// Connection check
|
|
var conn=rotConn(tile.conn,grid[r][c].r)
|
|
var dirs=[[-1,0,0,2],[0,1,1,3],[1,0,2,0],[0,-1,3,1]],any=false
|
|
for(var d=0;d<dirs.length;d++){if(!conn[dirs[d][2]])continue;var nr=r+dirs[d][0],nc=c+dirs[d][1]
|
|
if(nr>=0&&nr<ROWS&&nc>=0&&nc<COLS&&grid[nr][nc]){var nb=rotConn(TILES[grid[nr][nc].t].conn,grid[nr][nc].r);if(nb[dirs[d][3]])any=true;else traf++}else traf++}
|
|
if(tile.cat===1&&!any){reach-=4}else if(tile.cat===1)reach+=3
|
|
// Neighbors
|
|
if(tile.n==='Wohngebiet'||tile.n==='Hochhäuser'){var nb2=[];if(r>0&&grid[r-1][c])nb2.push(TILES[grid[r-1][c].t]);if(c<COLS-1&&grid[r][c+1])nb2.push(TILES[grid[r][c+1].t]);if(r<ROWS-1&&grid[r+1][c])nb2.push(TILES[grid[r+1][c].t]);if(c>0&&grid[r][c-1])nb2.push(TILES[grid[r][c-1].t])
|
|
nb2.forEach(function(n){if(n.n==='Park'||n.n==='Spielplatz'){sat+=2;env+=1};if(n.n==='Fabrik'){sat-=4;env-=3};if(n.n.indexOf('Laden')>=0||n.n.indexOf('Supermarkt')>=0){sup+=2;sat+=1}})}
|
|
}
|
|
var cl=function(v){return Math.min(100,Math.max(0,Math.round(v)))}
|
|
scores={eco:cl(eco),sat:cl(sat),env:cl(env),traf:cl(traf),sup:cl(sup),reach:cl(reach)}
|
|
}
|
|
|
|
function nextRound(){
|
|
if(gameOver)return;round++;budget+=income;recalc()
|
|
if(round>maxRounds){gameOver=true
|
|
var total=Math.round(0.22*scores.eco+0.24*scores.sat+0.18*scores.env+0.16*scores.sup+0.12*scores.reach+0.08*(100-scores.traf))
|
|
document.getElementById('end-t').textContent=total>=50?'🎉 Geschafft!':'😔'
|
|
document.getElementById('end-p').textContent='Score: '+total
|
|
document.getElementById('ov-end').classList.remove('hidden')
|
|
return}
|
|
renderUI()
|
|
}
|
|
|
|
// === RENDER ===
|
|
var hoverR=-1,hoverC=-1
|
|
|
|
function render(){
|
|
ctx.fillStyle='#c8d0b8';ctx.fillRect(0,0,cv.width,cv.height)
|
|
for(var r=0;r<ROWS;r++)for(var c=0;c<COLS;c++){
|
|
var px=c*TS,py=r*TS,cell=grid[r][c]
|
|
if(cell){var conn=rotConn(TILES[cell.t].conn,cell.r);drawTile(px,py,conn,1);drawEmojis(px,py,TILES[cell.t])}
|
|
else{ctx.fillStyle=GREEN;ctx.fillRect(px,py,TS,TS)}
|
|
ctx.strokeStyle='rgba(0,0,0,0.06)';ctx.lineWidth=0.4;ctx.strokeRect(px,py,TS,TS)
|
|
if(r===hoverR&&c===hoverC&&sel>=0&&!cell){drawTile(px,py,TILES[sel].conn,0.3)}
|
|
}
|
|
requestAnimationFrame(render)
|
|
}
|
|
|
|
function renderUI(){
|
|
document.getElementById('h-bud').textContent=budget
|
|
document.getElementById('h-rnd').textContent=round+'/'+maxRounds
|
|
document.getElementById('h-inc').textContent='+'+income+'/Runde'
|
|
if(sel>=0&&budget<TILES[sel].cost)sel=-1
|
|
// Params
|
|
var ps=[{n:'Wirtschaft',i:'💰',v:scores.eco},{n:'Zufriedenheit',i:'😊',v:scores.sat},{n:'Umwelt',i:'🌿',v:scores.env},{n:'Verkehr',i:'🚗',v:scores.traf,inv:1},{n:'Versorgung',i:'🛒',v:scores.sup},{n:'Erreichbarkeit',i:'🔗',v:scores.reach}]
|
|
document.getElementById('params').innerHTML=ps.map(function(p){var g=p.inv?p.v<40:p.v>55,b=p.inv?p.v>65:p.v<30,col=b?'#c07a6b':g?'#5a8a5e':'#c4a35a';return'<div class="param"><div class="param-i">'+p.i+'</div><div class="param-inf"><div class="param-n">'+p.n+'</div><div class="param-bar"><div class="param-bar-f" style="width:'+p.v+'%;background:'+col+'"></div></div></div><div class="param-v" style="color:'+col+'">'+p.v+'</div></div>'}).join('')
|
|
document.getElementById('msgs').innerHTML=messages.slice(-5).reverse().map(function(m){return'<div class="msg '+m.type+'">'+m.text+'</div>'}).join('')
|
|
// Tools
|
|
;[['road-tools',0],['building-tools',1],['nature-tools',2]].forEach(function(pair){
|
|
var html='';TILES.forEach(function(t,i){if(t.cat!==pair[1])return;var af=budget>=t.cost
|
|
html+='<div class="tb'+(sel===i?' on':'')+(af?'':' off')+'" data-t="'+i+'"><span class="te">'+(t.emoji||'🛣️')+'</span>'+t.n+'<span class="tc">💰'+t.cost+'</span></div>'})
|
|
document.getElementById(pair[0]).innerHTML=html})
|
|
document.querySelectorAll('.tb').forEach(function(b){b.addEventListener('click',function(){var i=parseInt(this.dataset.t);sel=sel===i?-1:i;renderUI()})})
|
|
}
|
|
|
|
// === INPUT ===
|
|
cv.addEventListener('click',function(e){
|
|
if(gameOver)return;var rect=cv.getBoundingClientRect(),mx=(e.clientX-rect.left)*(cv.width/rect.width),my=(e.clientY-rect.top)*(cv.height/rect.height)
|
|
var gc=Math.floor(mx/TS),gr=Math.floor(my/TS);if(gr<0||gr>=ROWS||gc<0||gc>=COLS)return
|
|
var cell=grid[gr][gc]
|
|
if(cell){var rx=mx-gc*TS,ry=my-gr*TS;if(ry<TS*0.35){if(rx<TS*0.4){cell.r=(cell.r+3)%4;return};if(rx>TS*0.6){cell.r=(cell.r+1)%4;return}};return}
|
|
if(sel>=0&&budget>=TILES[sel].cost){grid[gr][gc]={t:sel,r:0};budget-=TILES[sel].cost;recalc();renderUI()}
|
|
})
|
|
cv.addEventListener('mousemove',function(e){var rect=cv.getBoundingClientRect();hoverC=Math.floor((e.clientX-rect.left)*(cv.width/rect.width)/TS);hoverR=Math.floor((e.clientY-rect.top)*(cv.height/rect.height)/TS);if(hoverR<0||hoverR>=ROWS||hoverC<0||hoverC>=COLS){hoverR=-1;hoverC=-1}})
|
|
cv.addEventListener('mouseleave',function(){hoverR=-1;hoverC=-1})
|
|
document.addEventListener('keydown',function(e){if(e.key==='Escape'){sel=-1;renderUI()}})
|
|
|
|
document.getElementById('btn-next').addEventListener('click',nextRound)
|
|
document.getElementById('btn-go').addEventListener('click',function(){document.getElementById('ov-intro').classList.add('hidden');renderUI()})
|
|
document.getElementById('btn-again').addEventListener('click',function(){document.getElementById('ov-end').classList.add('hidden');location.reload()})
|
|
|
|
recalc();renderUI();render()
|
|
</script>
|
|
</body>
|
|
</html>
|