diff --git a/App/sims/weltkueche/game.html b/App/sims/weltkueche/game.html index b441fc6..0e5b378 100644 --- a/App/sims/weltkueche/game.html +++ b/App/sims/weltkueche/game.html @@ -170,6 +170,15 @@ .modal .big-emoji{font-size:3.4rem} .modal h2{font-size:1.6rem;font-weight:800;margin:8px 0} .modal .scoreline{font-size:1.05rem;color:var(--green-d);font-weight:700;margin:4px 0 14px} + /* Live-Punkte-Feedback (schwebt kurz auf und verblasst) */ + .wk-float{position:fixed;left:50%;top:20%;transform:translateX(-50%);z-index:9999;pointer-events:none; + font-weight:800;font-size:1.5rem;padding:.4rem 1rem;border-radius:14px;color:#fff; + box-shadow:0 10px 30px rgba(0,0,0,.28);animation:wkFloatAnim 1.4s ease-out forwards;white-space:nowrap} + .wk-float.pos{background:#4a7c4e} + .wk-float.neg{background:#c0674f;font-size:1rem} + @keyframes wkFloatAnim{0%{opacity:0;transform:translate(-50%,12px) scale(.9)} + 15%{opacity:1;transform:translate(-50%,0) scale(1)}70%{opacity:1;transform:translate(-50%,-6px) scale(1)} + 100%{opacity:0;transform:translate(-50%,-34px) scale(1)}} .fact{background:var(--gold-soft);border-radius:14px;padding:14px;font-size:.92rem;text-align:left;margin-bottom:14px} .fact b{color:#8a6516} .earned-badges{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-bottom:14px} @@ -612,7 +621,7 @@ const BADGES = [ {id:"perfect",icon:"🎯",name:"Volltreffer-Koch", desc:"Ein Gericht ganz ohne Fehlklick gelöst."}, {id:"dish", icon:"🍽️",name:"Weltkoch", desc:"Dein erstes Gericht abgeschlossen."}, {id:"landlk", icon:"🗺️",name:"Geografie-Genie", desc:"Erkannt: Binnenland hat kein Meer (Nori)."}, - {id:"trade", icon:"📈",name:"Welthandels-Profi",desc:"Über 3000 Punkte gesammelt."}, + {id:"trade", icon:"📈",name:"Welthandels-Profi",desc:"Über 2000 Punkte gesammelt."}, {id:"explorer",icon:"🌐",name:"Kontinent-Hopper",desc:"5 Gerichte abgeschlossen."}, {id:"star", icon:"⭐",name:"Sterne-Koch", desc:"Alle Gerichte abgeschlossen."}, ]; @@ -703,7 +712,7 @@ function buildTutorialSteps() { + 'Manche Länder produzieren viel, sind bei uns aber nicht im Regal — etwa weil sie alles selbst verbrauchen oder weil der Transport zu lang ist.' }, { img: imgBase + 'punkte.webp', title: 'Punkte & Abzeichen', - html: 'Jedes richtig zugeordnete Land bringt Punkte. Doppel-Treffer (zweites Herkunftsland) zählen extra.

' + html: 'Jedes richtige Herkunftsland bringt Punkte — und je früher du es findest, desto mehr. Jeder Fehlversuch senkt die erreichbaren Punkte.

' + 'Es gibt 10 Abzeichen zum Sammeln — z. B. Volltreffer-Koch (Gericht ohne Fehler), Geografie-Genie (erkennen, dass ein Binnenland kein Meer hat) oder Sterne-Koch (alle 30 Gerichte).' }, { img: imgBase + 'klima.webp', title: 'Klimabilanz im Blick', @@ -1154,7 +1163,7 @@ function buildTaskFor(ingKey) { // 2-4 Distraktoren: 1-2 aus climate_possible (für didaktisches Feedback), // Rest aus klar-unmöglichen - const distrCount = 2 + Math.floor(Math.random() * 3); // 2 bis 4 + const distrCount = 3; // NORM: immer 3 Distraktoren → 5 Karten (2 richtig + 3 falsch), faire & vergleichbare Punkte const possiblePool = climatePossible.filter(c => !correct.includes(c)); const possiblePicks = [...possiblePool]; shuffleInPlace(possiblePicks); @@ -1247,18 +1256,16 @@ function loadIngredient(){ // deterministisch: Heimat-Regel bzw. echte Hauptlieferanten nach Menge. delete _taskCache[ingKey]; const task = buildTaskFor(ingKey); - current.found=[]; current.wrongHere=0; current.wrongStreak=0; current.attempted=new Set(); + current.found=[]; current.wrongHere=0; current.wrongStreak=0; current.attempted=new Set(); current.ingPts=0; const two = task && task.correct.length > 1; const frage = two ? 'finde die zwei Herkunftsländer aus dieser Auswahl' : (task && task.signature ? 'aus welchem Land stammt diese Zutat in diesem Gericht?' : 'aus welchem Land kommt diese Zutat häufig zu uns auf den Markt?'); - const _nd = Math.max(1, task ? (task.choices.length - task.correct.length) : 4); - const _per = Math.round(100 / _nd); document.getElementById('taskBanner').innerHTML= `🔎 Suche: ${ing.nm} – ${frage}` - + ` · richtig +100, jeder Fehlklick −${_per} (alle falsch zuerst = 0)`; + + ` · je früher du richtig liegst, desto mehr Punkte – Fehlversuche senken sie`; document.getElementById('resultCard').className="result-card"; drawMarkers(ing); renderIngList(); } @@ -1284,6 +1291,15 @@ function drawMarkers(ing){ } function btnFor(key){const m=current.markers[key];return(m&&m.getElement())?m.getElement().querySelector('.country-btn'):null;} +// Kurzes schwebendes Punkte-Feedback (Live) bei jedem Klick. +function wkFloat(txt, kind){ + const el=document.createElement('div'); + el.className='wk-float '+(kind==='neg'?'neg':'pos'); + el.textContent=txt; + document.body.appendChild(el); + setTimeout(()=>{ el.remove(); }, 1400); +} + function guess(key){ const b=btnFor(key); if(!b||b.disabled) return; const ingKey = current.dish.ings[current.idx]; @@ -1294,23 +1310,25 @@ function guess(key){ if (current.attempted) current.attempted.add(key); playSfx('click-country'); if (cls === 'correct') { + if (current.found.includes(key)) return; // schon gefunden → nicht doppelt zählen b.classList.add('correct'); - // Strafe pro Fehlklick so, dass „alle Falschen zuerst" IMMER 0 ergibt: - // 100 ÷ Anzahl Distraktoren. Bei 4 Distraktoren = −25 (¼), bei 2 = −50. - const numDistr = Math.max(1, task ? (task.choices.length - task.correct.length) : 4); - const pts = Math.max(0, Math.round(100 - (100 / numDistr) * (current.wrongHere || 0))); - state.score+=pts; current.dishScore+=pts; + // Punkte pro richtigem Klick = 25 × noch verbleibende FALSCHE Optionen. + // Je früher gefunden, desto mehr; jeder Fehlklick vorher senkt den Wert. + // Bei 3 Distraktoren: max 2×75 = 150 pro Zutat; „alle falsch zuerst" = 0. + const totalWrong = task ? (task.choices.length - task.correct.length) : 3; + const pts = 25 * Math.max(0, totalWrong - (current.wrongHere || 0)); + state.score+=pts; current.dishScore+=pts; current.ingPts+=pts; + wkFloat('+' + pts, 'pos'); state.totalCorrect++; current.wrongStreak=0; current.found.push(key); map.flyTo([c.lat,c.lng],3,{duration:.7}); const targetCount = task.correct.length; if(current.found.length >= targetCount){ - let bonus=0; - if(targetCount > 1){ bonus=50; state.score+=bonus; current.dishScore+=bonus; awardLive(['pair']); } + if(targetCount > 1){ awardLive(['pair']); } // Doppel-Treffer-Abzeichen (kein Extra-Punkt: 150 = 75+75) if(current.wrongHere===0) state.cleanIngredients++; current.solved.push(current.idx); Object.keys(current.markers).forEach(k=>{const x=btnFor(k);if(x)x.disabled=true;}); checkLiveBadges(ing,key); - showSolved(ing,pts,bonus); + showSolved(ing,current.ingPts,0); // Sound nach dem korrekten Render-Frame, damit click-country abgeklungen ist setTimeout(()=>playSfx('ingredient-solved'), 120); } else { @@ -1323,6 +1341,7 @@ function guess(key){ } else { b.classList.add('wrong'); b.disabled=true; current.wrongStreak++; current.wrongHere++; current.mistakesInDish++; state.totalWrong++; + wkFloat('Fehlversuch – erreichbare Punkte sinken', 'neg'); showWrong(key, cls, ingKey); setTimeout(()=>playSfx('ingredient-wrong'), 120); } @@ -1503,7 +1522,7 @@ function showInterim(ing,key,pts,remaining){ card.innerHTML=`
✓ ${c.f} ${c.n} stimmt! (+${pts})${amount}
${c.info}
${marketCaveatFor(info)} -
🔎 Noch ${remaining} weiteres Herkunftsland gesucht — finde es für Bonuspunkte!
`; +
🔎 Noch ${remaining} weiteres Herkunftsland gesucht — finde es für mehr Punkte!
`; } function showSolved(ing,pts,bonus){ const card=document.getElementById('resultCard'); @@ -1636,7 +1655,7 @@ function checkLiveBadges(ing,key){ if(state.totalCorrect>=1) ids.push('first'); if(state.totalCorrect>=8) ids.push('spice'); if(state.cleanIngredients>=3) ids.push('climate'); - if(state.score>=3000) ids.push('trade'); + if(state.score>=2000) ids.push('trade'); if(ing.nm.indexOf('Nori')===0) ids.push('landlk'); awardLive(ids); } @@ -1664,7 +1683,7 @@ function finishDish(){ newly.push(...awardLive(['dish'])); if(state.completed.length>=5) newly.push(...awardLive(['explorer'])); if(state.completed.length>=DISHES.length) newly.push(...awardLive(['star'])); - if(state.score>=3000) newly.push(...awardLive(['trade'])); + if(state.score>=2000) newly.push(...awardLive(['trade'])); updateHUD(); renderDishes(); showRecipeFullScreen(d, perfect, newly); }