Weltkueche Teil B: Consumer-Anker (Herkunft folgt dem User-Land) — Pilot Ei/Kartoffel/Semmelbroesel
Grundzutaten, die im Land des Users heimisch sind, kommen jetzt aus dem Land
des USERS statt aus dem Gericht-Land (Carbonara-Ei in AT = AT-Ei). Opt-in pro
Zutat ueber neues Feld `heimisch`; Signaturzutaten bleiben am Gericht.
game.html:
- buildTaskFor: neuer Consumer-Zweig (vor Heimat-Regel). correct = [User-Land,
...market], Distraktoren explizit (`distractors`) oder berechnet. Immer 5 Karten.
- _wkUser(): WK_COUNTRY (AT/DE/CH/LI) -> Laender-Slug, Fallback AT.
- isWorldIngredient: Zutat mit `heimisch` wird immer abgefragt (z.B. Semmelbroesel).
- ING-Build: heimisch/market/distractors werden aus der Matrix mitkopiert
(wurden vorher weggelassen -> Felder kamen nie im Spiel an).
- Aufloesungstext: Consumer-Fall ("kaufst du, wo du einkaufst - im eigenen Land").
Daten (production-matrix.json), Thomas-Vorgaben:
- eier: heimisch AT/DE/CH, market DE+NL -> AT-User: AT+DE+NL, DE-User: DE+NL
- kartoffel: heimisch AT/DE/CH, market DE -> AT+DE (alle Gerichte)
- semmelbroesel: heimisch AT/DE, market DE, distractors GR/TR/EG/TN/IE; jetzt abgefragt
admin-weltkueche.html: Consumer-Zweig gespiegelt + AT/DE/CH-Umschalter.
Nebenbefund (NICHT angefasst): correct_for wird im Spiel nie gelesen (nur Admin
las es) -> mehl/majoran/safran/feta-lamm-Kuratierung wirkte bisher nur im Admin.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,12 @@
|
||||
<span class="chip" data-cat="europa" onclick="setCat(this,'europa')">Europa</span>
|
||||
<span class="chip" data-cat="exotisch" onclick="setCat(this,'exotisch')">Exotisch</span>
|
||||
<label style="font-size:.72rem;color:#6a6a6a;margin-left:.5rem"><input type="checkbox" id="onlyWarn" onchange="render()"> nur Auffällige ⚠</label>
|
||||
<label style="font-size:.72rem;color:#6a6a6a;margin-left:.5rem">🛒 Land des Users:
|
||||
<select id="userCty" onchange="setUser(this.value)" style="font-size:.72rem;padding:.15rem .3rem">
|
||||
<option value="oesterreich">🇦🇹 Österreich</option>
|
||||
<option value="deutschland">🇩🇪 Deutschland</option>
|
||||
<option value="schweiz">🇨🇭 Schweiz</option>
|
||||
</select></label>
|
||||
</div>
|
||||
<div id="status">Lade Daten…</div>
|
||||
<div id="list"></div>
|
||||
@@ -74,6 +80,8 @@ var BASE = location.pathname.replace(/\/admin-weltkueche\.html$/, '');
|
||||
var D = 'sims/weltkueche/assets/data/';
|
||||
var C = {}, PM = {}, DISHES = [];
|
||||
var _cat = '';
|
||||
var WK_USER = 'oesterreich'; // Teil B: Land des Users (Consumer-Anker) — via Umschalter
|
||||
function setUser(v){ WK_USER = v || 'oesterreich'; render(); }
|
||||
|
||||
// ---- Spiel-Logik gespiegelt (siehe game.html buildTaskFor) ----
|
||||
function mrank(m){ return m === 'eu' ? 0 : (m === 'world-far' || m === 'local') ? 2 : 1; }
|
||||
@@ -87,10 +95,22 @@ function wkDist(a, b){
|
||||
}
|
||||
// Klassifiziert alle Produzenten einer Zutat für ein Gericht.
|
||||
// Gibt { correct:[keys], distractors:[keys], branch } zurück (Pools, ohne Zufalls-Ziehung).
|
||||
function classify(ingKey, heimat){
|
||||
function classify(ingKey, heimat, isSig){
|
||||
var ing = PM[ingKey]; if (!ing) return null;
|
||||
var producers = ing.producers || {};
|
||||
var keys = Object.keys(producers).filter(function(c){ return C[c]; });
|
||||
// ── CONSUMER-REGEL (Teil B): Grundzutat, im Land des Users heimisch → kommt aus
|
||||
// dem Land des Users (unabhängig vom Gericht-Land). Signaturzutaten ausgenommen.
|
||||
if (!isSig && ing.heimisch && ing.heimisch.indexOf(WK_USER) >= 0 && C[WK_USER]) {
|
||||
var cmarket = (ing.market||[]).filter(function(c){ return C[c] && c !== WK_USER; });
|
||||
var ccorrect = [WK_USER].concat(cmarket).slice(0,3);
|
||||
if (ccorrect.length < 2){ for (var hi=0; hi<(ing.heimisch||[]).length; hi++){ var hh=ing.heimisch[hi]; if(C[hh]&&ccorrect.indexOf(hh)<0){ccorrect.push(hh);break;} } }
|
||||
var cdistr;
|
||||
if (ing.distractors && ing.distractors.length){ cdistr = ing.distractors.filter(function(c){ return C[c] && ccorrect.indexOf(c)<0; }); }
|
||||
else { var cfar = keys.filter(function(c){ var mm=producers[c].market; return (mm==='world-far'||mm==='local') && ccorrect.indexOf(c)<0; }).sort(function(a,b){ return (producers[b].t||0)-(producers[a].t||0); });
|
||||
var cclim = (ing.climate_possible||[]).filter(function(c){ return C[c] && ccorrect.indexOf(c)<0; }); cdistr = cfar.concat(cclim); }
|
||||
return { correct: ccorrect, distractors: cdistr, branch:'consumer', grav:function(){return 0;} };
|
||||
}
|
||||
// Heimat-Zweig: Heimat erzeugt die Zutat selbst
|
||||
if (heimat && C[heimat] && producers[heimat]) {
|
||||
var grav = function(c){ return (producers[c].t || 0) / wkDist(heimat, c); };
|
||||
@@ -143,7 +163,7 @@ function render(){
|
||||
var rows = ''; var dishHasWarn = false;
|
||||
ings.forEach(function(k){
|
||||
if (q && (d.name||'').toLowerCase().indexOf(q)<0 && (PM[k].nm||k).toLowerCase().indexOf(q)<0) return;
|
||||
var cl = classify(k, heimat); if(!cl) return;
|
||||
var cl = classify(k, heimat, !!(d.signature && d.signature[k])); if(!cl) return;
|
||||
// Auffällig NUR im Heimat-Zweig: die Zutat ist lokal erzeugbar (Heimat baut
|
||||
// sie an), trotzdem ist ein „richtiges" Land > 1500 km weg → echter Ausreißer.
|
||||
// Welt-Zutaten (Pfeffer aus Vietnam etc.) sind zu Recht fern → keine Warnung.
|
||||
@@ -155,7 +175,7 @@ function render(){
|
||||
if (onlyWarn && !warn) return;
|
||||
var okChips = cl.correct.map(function(c){ return ctyChip(c,'ok',k,heimat,true); }).join('');
|
||||
var distChips = cl.distractors.slice(0,6).map(function(c){ return ctyChip(c,'dist',k,heimat,false); }).join('');
|
||||
rows += '<tr><td class="ing-nm">'+(PM[k].ic||'')+' '+(PM[k].nm||k)+'<small>'+(cl.branch==='heimat'?'Heimat-Regel':'Welt-Zutat')+'</small></td>'
|
||||
rows += '<tr><td class="ing-nm">'+(PM[k].ic||'')+' '+(PM[k].nm||k)+'<small>'+(cl.branch==='consumer'?'🛒 Consumer-Regel (User-Land)':(cl.branch==='heimat'?'Heimat-Regel':'Welt-Zutat'))+'</small></td>'
|
||||
+ '<td><div class="colhead">richtig</div>'+(okChips||'—')+warn+'</td>'
|
||||
+ '<td><div class="colhead">Distraktoren (Pool)</div>'+(distChips||'—')+'</td></tr>';
|
||||
});
|
||||
|
||||
@@ -3485,12 +3485,15 @@
|
||||
"kenia"
|
||||
],
|
||||
"fact": "Eier können prinzipiell überall produziert werden — Hühner sind extrem anpassungsfähig. Die meisten Eier in Europa werden lokal gegessen.",
|
||||
"correct_for": {
|
||||
"oesterreich": [
|
||||
"deutschland",
|
||||
"niederlande"
|
||||
]
|
||||
}
|
||||
"heimisch": [
|
||||
"oesterreich",
|
||||
"deutschland",
|
||||
"schweiz"
|
||||
],
|
||||
"market": [
|
||||
"deutschland",
|
||||
"niederlande"
|
||||
]
|
||||
},
|
||||
"fischsauce": {
|
||||
"nm": "Fischsauce",
|
||||
@@ -3989,11 +3992,14 @@
|
||||
"kolumbien"
|
||||
],
|
||||
"fact": "Die Kartoffel kommt ursprünglich aus den Anden und wurde erst nach 1565 nach Europa gebracht. In Belarus isst eine Person ~180 kg pro Jahr — Welt-Spitze.",
|
||||
"correct_for": {
|
||||
"oesterreich": [
|
||||
"schweiz"
|
||||
]
|
||||
}
|
||||
"heimisch": [
|
||||
"oesterreich",
|
||||
"deutschland",
|
||||
"schweiz"
|
||||
],
|
||||
"market": [
|
||||
"deutschland"
|
||||
]
|
||||
},
|
||||
"zwiebel": {
|
||||
"nm": "Zwiebel",
|
||||
@@ -4602,6 +4608,20 @@
|
||||
"market": "eu"
|
||||
}
|
||||
},
|
||||
"heimisch": [
|
||||
"oesterreich",
|
||||
"deutschland"
|
||||
],
|
||||
"market": [
|
||||
"deutschland"
|
||||
],
|
||||
"distractors": [
|
||||
"griechenland",
|
||||
"tuerkei",
|
||||
"aegypten",
|
||||
"tunesien",
|
||||
"irland"
|
||||
],
|
||||
"fact": "Getrocknetes, geriebenes Weißbrot — ein lokales Nebenprodukt aus der Bäckerei."
|
||||
},
|
||||
"oel": {
|
||||
|
||||
@@ -534,7 +534,10 @@ async function loadWeltkuecheData() {
|
||||
producers: v.producers || {},
|
||||
climate_possible: v.climate_possible || [],
|
||||
fact: v.fact || '',
|
||||
more: v.more || ''
|
||||
more: v.more || '',
|
||||
heimisch: v.heimisch || null, // Teil B: Länder mit Eigenanteil ≥ ⅓ (Consumer-Anker)
|
||||
market: v.market || null, // Teil B: kuratierte Markt-Zweitlieferanten
|
||||
distractors: v.distractors || null // Teil B: explizite Distraktor-Länder
|
||||
};
|
||||
});
|
||||
DISHES = Object.entries(dishesJ)
|
||||
@@ -975,10 +978,19 @@ function worldIngCount(dish){
|
||||
* wird im Game als "lokal verfügbar" angezeigt, aber nicht erforscht. */
|
||||
function isWorldIngredient(key) {
|
||||
const ing = ING[key]; if (!ing) return false;
|
||||
if (ing.heimisch && ing.heimisch.length) return true; // Consumer-Grundzutat (Teil B): wird immer abgefragt
|
||||
const real = Object.values(ing.producers || {}).filter(p => p.t && p.t > 0);
|
||||
return real.length >= 3;
|
||||
}
|
||||
|
||||
// Land des Users (aus teachers.country → WK_COUNTRY) auf den Länder-Slug mappen.
|
||||
// Fallback AT. LI nutzt die Schweizer Markt-Logik (Zollunion, CHF).
|
||||
function _wkUser() {
|
||||
const map = { AT: 'oesterreich', DE: 'deutschland', CH: 'schweiz', LI: 'schweiz' };
|
||||
const raw = (typeof window !== 'undefined' && window.WK_COUNTRY) ? window.WK_COUNTRY : 'AT';
|
||||
return map[raw] || (C[raw] ? raw : 'oesterreich');
|
||||
}
|
||||
|
||||
/* ====================== AUFGABEN-GENERIERUNG ======================
|
||||
* Neue Architektur (ab production-matrix.json):
|
||||
*
|
||||
@@ -1055,6 +1067,47 @@ function buildTaskFor(ingKey) {
|
||||
if (_taskCache[ingKey]) return _taskCache[ingKey];
|
||||
const ing = ING[ingKey];
|
||||
if (!ing) return null;
|
||||
// ── CONSUMER-REGEL (Teil B): Eine Grundzutat, die im Land des USERS heimisch
|
||||
// ist (Eigenanteil ≥ ~⅓), kauft man dort, wo man einkauft — im eigenen Land,
|
||||
// UNABHÄNGIG davon, aus welchem Land das Gericht stammt. (Das Ei in der
|
||||
// Carbonara ist in Österreich ein AT-Ei, kein italienisches.) Opt-in per
|
||||
// `heimisch`-Feld; SIGNATURzutaten (dish.signature) sind ausgenommen und
|
||||
// bleiben an der Herkunftsregion des Gerichts (bewusster Original-Kauf).
|
||||
const _cDish = current && current.dish;
|
||||
const _isSig = !!(_cDish && _cDish.signature && _cDish.signature[ingKey]);
|
||||
const _user = _wkUser();
|
||||
if (!_isSig && ing.heimisch && ing.heimisch.includes(_user) && C[_user]) {
|
||||
// Lösung 1 = Land des Users; Lösung 2(+3) = kuratierte Markt-Zweitlieferanten (`market`).
|
||||
const market = (ing.market || []).filter(c => C[c] && c !== _user);
|
||||
const correct = [_user, ...market].slice(0, 3);
|
||||
if (correct.length < 2) { // Garantie: es gibt IMMER eine zweite Wahl
|
||||
for (const c of (ing.heimisch || [])) { if (C[c] && !correct.includes(c)) { correct.push(c); break; } }
|
||||
}
|
||||
// Distraktoren: explizit kuratiert (`distractors`) ODER berechnet (fern + klimafremd).
|
||||
let pool;
|
||||
if (ing.distractors && ing.distractors.length) {
|
||||
pool = ing.distractors.filter(c => C[c] && !correct.includes(c));
|
||||
} else {
|
||||
const far = Object.entries(ing.producers || {})
|
||||
.filter(([c, i]) => C[c] && !correct.includes(c) && (i.market === 'world-far' || i.market === 'local'))
|
||||
.sort((a, b) => (b[1].t || 0) - (a[1].t || 0)).map(([c]) => c);
|
||||
const climate = (ing.climate_possible || []).filter(c => C[c] && !correct.includes(c));
|
||||
const imposs = impossibleCountries(correct, [...far, ...climate, ...Object.keys(ing.producers || {})]);
|
||||
pool = [...far, ...climate, ...imposs];
|
||||
}
|
||||
shuffleInPlace(pool);
|
||||
const target = Math.max(0, 5 - correct.length);
|
||||
const distr = [];
|
||||
for (const c of pool) { if (distr.length >= target) break; if (!distr.includes(c) && !correct.includes(c)) distr.push(c); }
|
||||
const choices = [...correct, ...distr];
|
||||
shuffleInPlace(choices);
|
||||
_taskCache[ingKey] = {
|
||||
correct, choices,
|
||||
possible: new Set(distr.filter(c => (ing.climate_possible || []).includes(c))),
|
||||
consumer: true, user: _user
|
||||
};
|
||||
return _taskCache[ingKey];
|
||||
}
|
||||
// HEIMAT-REGEL: In einem regionalen Gericht kommt jede Zutat, die das
|
||||
// Herkunftsland selbst erzeugt, von dort — logisch nachvollziehbar (ein
|
||||
// Tiroler Gröstl nutzt Tiroler Speck, Tiroler Kartoffeln …). Der Test
|
||||
@@ -1533,7 +1586,10 @@ function showSolved(ing,pts,bonus){
|
||||
card.className="result-card ok show";
|
||||
const factLine = ing.fact ? `<div class="more-line"><b>Wusstest du?</b> ${esc(ing.fact)}</div>` : '';
|
||||
// Heimat-Regel: erklären, warum die Region die Antwort ist (nicht der größte Produzent)
|
||||
const sigLine = (task.signature && C[task.heimat])
|
||||
const _userN = task.user && C[task.user] ? (C[task.user].f + ' ' + C[task.user].n) : 'bei uns';
|
||||
const sigLine = task.consumer
|
||||
? `<div class="more-line" style="background:#eef5ec;border-left:3px solid #4a7c4e">🛒 Eine <b>Grundzutat</b> — die kaufst du <b>dort, wo du einkaufst</b>: im eigenen Land (${_userN}). Reicht die heimische Menge nicht, kommt ein naher Nachbar dazu. Nur was bei uns gar nicht wächst, reist von weit her — und was ein Gericht <b>besonders</b> macht, holt man aus der Herkunftsregion.</div>`
|
||||
: (task.signature && C[task.heimat])
|
||||
? (task.label
|
||||
? `<div class="more-line" style="background:#eef5ec;border-left:3px solid #4a7c4e">🍽️ <b>${task.label}</b> gehört typisch zu diesem Gericht — solche <b>charakteristischen</b> Zutaten holt man von dort. Grundzutaten dagegen kauft man einfach in der Nähe.</div>`
|
||||
: `<div class="more-line" style="background:#eef5ec;border-left:3px solid #4a7c4e">🍽️ Eine <b>Grundzutat</b>: was auch bei uns wächst, kommt aus der <b>Nähe</b> — heimisch, dazu ein naher europäischer Lieferant, wenn es allein nicht reicht. Nur was bei uns gar nicht wächst, reist von weit her.</div>`)
|
||||
|
||||
Reference in New Issue
Block a user