Weltkueche Punktesystem: neue Formel (25 x verbleibende Falsche) + immer 5 Karten
- Auswahl auf IMMER 2 richtig + 3 Distraktoren (5 Karten) genormt (Welt-Zweig wuerfelte 2-4). Damit ist die Bewertung fair & vergleichbar. - Punkte pro richtigem Klick = 25 x (noch verbleibende falsche Optionen). Frueh gefunden = mehr; jeder Fehlklick vorher senkt den Wert. Max 2x75 = 150, 0 moeglich (alle falsch zuerst). 50er-Bonus entfaellt (150 = 75+75). - Live-Feedback: schwebendes +75/+50/+25/+0 (gruen) bzw. "Fehlversuch - erreichbare Punkte sinken" (rot) bei jedem Klick. - Banner/Interim/Onboarding-Text angepasst; trade-Abzeichen 3000 -> 2000 (da Max jetzt 150 statt ~250 pro Zutat). - BUGFIX: dasselbe richtige Land liess sich doppelt anklicken/zaehlen -> Guard. Playwright-verifiziert: 5 Karten, 75+0(Guard)+75=150, 0 JS-Fehler. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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 <i>viel</i>, 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 <b>Punkte</b>. Doppel-Treffer (zweites Herkunftsland) zählen extra.<br><br>'
|
||||
html: 'Jedes richtige Herkunftsland bringt <b>Punkte</b> — und je <b>früher</b> du es findest, desto mehr. Jeder Fehlversuch senkt die erreichbaren Punkte.<br><br>'
|
||||
+ 'Es gibt 10 <b>Abzeichen</b> zum Sammeln — z. B. <b>Volltreffer-Koch</b> (Gericht ohne Fehler), <b>Geografie-Genie</b> (erkennen, dass ein Binnenland kein Meer hat) oder <b>Sterne-Koch</b> (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 <b>zwei</b> Herkunftsländer aus dieser Auswahl'
|
||||
: (task && task.signature
|
||||
? 'aus welchem Land stammt diese Zutat in <b>diesem Gericht</b>?'
|
||||
: '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: <b>${ing.nm}</b> – ${frage}`
|
||||
+ ` <span style="opacity:.75;font-weight:600;white-space:nowrap">· richtig <b>+100</b>, jeder Fehlklick <b>−${_per}</b> (alle falsch zuerst = 0)</span>`;
|
||||
+ ` <span style="opacity:.75;font-weight:600;white-space:nowrap">· je früher du richtig liegst, desto mehr Punkte – Fehlversuche senken sie</span>`;
|
||||
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=`<div class="rc-head">✓ ${c.f} ${c.n} stimmt! (+${pts})${amount}</div>
|
||||
<div class="note">${c.info}</div>
|
||||
${marketCaveatFor(info)}
|
||||
<div class="more-line">🔎 Noch <b>${remaining}</b> weiteres Herkunftsland gesucht — finde es für <b>Bonuspunkte</b>!</div>`;
|
||||
<div class="more-line">🔎 Noch <b>${remaining}</b> weiteres Herkunftsland gesucht — finde es für <b>mehr Punkte</b>!</div>`;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user