// Felder, Mauer und Nachbarn (Teil von index.html): Die Karte zerfaellt in Parzellen von 4x4
// Kacheln. Das Dorf nimmt Parzellen ein (E), gebaut wird nur auf eigenem Grund, und die Mauer
// setzt man Stueck fuer Stueck an die Grenze. Die Nachbardoerfer wachsen langsam mit.

const PARZ = 4;                                            // Kacheln je Parzelle
const parzBreite = () => Math.floor(KARTE.breite / PARZ);
const parzHoehe = () => Math.floor(KARTE.hoehe / PARZ);
const parzAnzahl = () => parzBreite() * parzHoehe();
const parzVon = (x, y) => `${Math.floor(x / PARZ)},${Math.floor(y / PARZ)}`;
const parzTeile = p => String(p).split(",").map(Number);
// Ist das überhaupt eine Parzelle auf dieser Karte?
//
// Ein beschädigter Spielstand kann "a,b" oder "-1,-1" in felder() stehen haben. parzTeile()
// macht daraus NaN, und die Schleifen rechneten fröhlich weiter, bis der Zugriff auf
// KARTE.fest[NaN] das Spiel zerlegte - gemessen im Testfall "unmoeglich". Alles, was über
// die Feldliste läuft, prüft jetzt vorher.
const parzGueltig = p => {
  const [x, y] = parzTeile(p);
  return Number.isInteger(x) && Number.isInteger(y)
    && x >= 0 && y >= 0 && x < parzBreite() && y < parzHoehe();
};

const START_FELDER = 16;                                   // 4x4 Parzellen = 16x16 Kacheln zum Anfangen
function felder() {
  const d = dorf();
  if (!d) return [];
  if (!d.felder) {                                         // Startgrund rund um den Anfangspunkt
    const [sx, sy] = [Math.floor(KARTE.start.x / PARZ), Math.floor(KARTE.start.y / PARZ)];
    d.felder = [];
    for (let dy = -1; dy <= 2; dy++) for (let dx = -1; dx <= 2; dx++) {
      const px = Math.min(parzBreite() - 1, Math.max(0, sx + dx)), py = Math.min(parzHoehe() - 1, Math.max(0, sy + dy));
      if (!d.felder.includes(`${px},${py}`)) d.felder.push(`${px},${py}`);
    }
  }
  if (d.felder.length < START_FELDER) auffuellen(d);        // aeltere Laeufe bekommen den groesseren Anfang nach
  return d.felder;
}
// Nachfuellen ohne Umweg ueber besitzer() - das rief sonst felder() und damit sich selbst
function auffuellen(d) {
  const fremd = (d.nachbarn || []).flatMap(n => n.felder);
  let schutz = 0;
  while (d.felder.length < START_FELDER && schutz++ < 200) {
    const dazu = [...new Set(d.felder.flatMap(anliegend))].find(p => !d.felder.includes(p) && !fremd.includes(p));
    if (!dazu) break;
    d.felder.push(dazu);
  }
}
const istDorf = (x, y) => felder().includes(parzVon(x, y));
// Wem gehoert eine Parzelle? "dorf", die Id eines Nachbardorfs oder null
function besitzer(p) {
  if (felder().includes(p)) return "dorf";
  const n = (dorf().nachbarn || []).find(q => q.felder.includes(p));
  return n ? n.id : null;
}
const anliegend = p => {
  const [x, y] = parzTeile(p);
  return [[1, 0], [-1, 0], [0, 1], [0, -1]].map(([dx, dy]) => `${x + dx},${y + dy}`)
    .filter(q => { const [a, b] = parzTeile(q); return a >= 0 && b >= 0 && a < parzBreite() && b < parzHoehe(); });
};
const erweiterbar = () => [...new Set(felder().flatMap(anliegend))].filter(p => !besitzer(p));

// ---------------------------------------------------------------------------
// Mauer: eigene Stuecke auf Grenzkacheln, Tore sind begehbar
// ---------------------------------------------------------------------------
function mauerteile() {
  const d = dorf();
  if (!d) return {};
  return (d.mauerteile = d.mauerteile || {});
}
// ---------------------------------------------------------------------------
// Was heute begonnen wurde, steht erst morgen. Bis dahin ein Geruest aus Latten: es haelt
// nichts auf, zaehlt nicht fuer die Deckung und kostet trotzdem schon das Material - eine
// Mauer entsteht nicht zwischen zwei Schritten. Angefangenes liegt in einer EIGENEN Liste,
// damit alles, was mauerteile() liest (Deckung, Zeichnung, alte Staende), unveraendert bleibt.
// ---------------------------------------------------------------------------
function mauerRoh() {
  const d = dorf();
  if (!d) return {};
  return (d.mauerRoh = d.mauerRoh || {});
}
// Am Morgen wird fertig, was gestern begonnen wurde
function mauerFertigstellen() {
  const d = dorf();
  if (!d) return 0;
  const roh = mauerRoh(), teile = mauerteile();
  let fertig = 0;
  for (const [k, e] of Object.entries(roh)) {
    if (!e || e.tag >= d.tag) continue;                     // heute erst begonnen: morgen wieder
    teile[k] = e.art;
    delete roh[k];
    fertig++;
  }
  if (fertig) d.bericht.push(fertig === 1
    ? bz("Das Mauerstück von gestern steht.")
    : bz("{0} Mauerstücke von gestern stehen.", fertig));
  return fertig;
}
// Kacheln am Rand des Dorfes - dort gehoert die Mauer hin (gemerkt, bis sich das Dorf aendert)
let grenzMerk = { schluessel: "", liste: [] };
function grenzKacheln() {
  const schluessel = felder().join("|") + "#" + (dorf().nachbarn || []).map(n => n.felder.join(",")).join("#");
  if (grenzMerk.schluessel === schluessel) return grenzMerk.liste;
  const raus = [];
  for (const p of felder()) {
    if (!parzGueltig(p)) continue;                          // Unsinn aus einem alten Stand
    const [px, py] = parzTeile(p);
    for (let i = 0; i < PARZ; i++) {
      const kanten = [
        [px * PARZ + i, py * PARZ, 0, -1], [px * PARZ + i, (py + 1) * PARZ - 1, 0, 1],
        [px * PARZ, py * PARZ + i, -1, 0], [(px + 1) * PARZ - 1, py * PARZ + i, 1, 0],
      ];
      for (const [x, y, dx, dy] of kanten) {
        const nachbar = parzVon(x + dx * PARZ, y + dy * PARZ);
        const drin = x + dx >= 0 && y + dy >= 0 && x + dx < KARTE.breite && y + dy < KARTE.hoehe;
        if (!drin || besitzer(nachbar) !== "dorf") raus.push(`${x},${y}`);
      }
    }
  }
  grenzMerk = { schluessel, liste: [...new Set(raus)] };
  return grenzMerk.liste;
}
// Wasser, Fels und dichter Wald halten von allein - dort braucht es keine Mauer
function natuerlich(k) {
  const [x, y] = k.split(",").map(Number);
  if (x < 0 || y < 0 || x >= KARTE.breite || y >= KARTE.hoehe) return true;
  return !!(KARTE.wasser[y][x] || KARTE.grund[y][x]);
}
// Die Grenze in vier Abschnitte teilen (Norden, Osten, Sueden, Westen). Die Resilienz richtet
// sich nach dem schwaechsten: eine Luecke ist kein halbes Loch - sie ist das Loch. (Konzept)
function abschnitte() {
  const grenze = grenzKacheln();
  if (!grenze.length) return [0];
  const xs = grenze.map(k => +k.split(",")[0]), ys = grenze.map(k => +k.split(",")[1]);
  const mx = (Math.min(...xs) + Math.max(...xs)) / 2, my = (Math.min(...ys) + Math.max(...ys)) / 2;
  const teile = { n: [], o: [], s: [], w: [] };
  for (const k of grenze) {
    const [x, y] = k.split(",").map(Number);
    const dx = x - mx, dy = y - my;
    teile[Math.abs(dx) > Math.abs(dy) ? (dx > 0 ? "o" : "w") : (dy > 0 ? "s" : "n")].push(k);
  }
  const t = mauerteile();
  const wert1 = k => natuerlich(k) ? 1 : mauerWert(t[k]);
  return Object.values(teile).filter(l => l.length).map(l => l.reduce((sum, k) => sum + wert1(k), 0) / l.length);
}
const schwaechsterAbschnitt = () => Math.min(...abschnitte());
// Dieselbe Rechnung, aber mit Namen und Zahlen - fuer den Bericht am Morgen danach.
const HIMMELSRICHTUNG = { n: "Norden", o: "Osten", s: "Süden", w: "Westen" };
function schwaechsteSeite() {
  const grenze = grenzKacheln();
  if (!grenze.length) return null;
  const xs = grenze.map(k => +k.split(",")[0]), ys = grenze.map(k => +k.split(",")[1]);
  const mx = (Math.min(...xs) + Math.max(...xs)) / 2, my = (Math.min(...ys) + Math.max(...ys)) / 2;
  const teile = { n: [], o: [], s: [], w: [] };
  for (const k of grenze) {
    const [x, y] = k.split(",").map(Number);
    const dx = x - mx, dy = y - my;
    teile[Math.abs(dx) > Math.abs(dy) ? (dx > 0 ? "o" : "w") : (dy > 0 ? "s" : "n")].push(k);
  }
  const gesetzt = mauerteile();
  const gedeckt = k => natuerlich(k) || !!gesetzt[k];
  let schlechteste = null;
  for (const [ri, liste] of Object.entries(teile)) {
    if (!liste.length) continue;
    const zahl = liste.filter(gedeckt).length;
    const anteil = zahl / liste.length;
    if (!schlechteste || anteil < schlechteste.anteil)
      schlechteste = { richtung: t(HIMMELSRICHTUNG[ri]), gedeckt: zahl, gesamt: liste.length, anteil };
  }
  return schlechteste;
}
// Offene Stellen der Grenze: dort kommen sie herein, und dort stehen Haeuser ungeschuetzt
function offeneGrenze() {
  const t = mauerteile();
  return grenzKacheln().filter(k => !natuerlich(k) && !t[k]);
}

// Tore: tagsüber offen, damit man hinauskommt - nachts geschlossen, dann zählen sie wie Mauer.
// Geschlossen wird am Tor selbst (P davor) oder für alle zusammen im Dorfbuch.
const toreZu = () => !!(dorf() && dorf().toreZu);
function toreSchalten(zu = !toreZu()) {
  const d = dorf();
  const tore = Object.entries(mauerteile()).filter(([, art]) => art === "tor").length;
  if (!tore) return meldung(t("Ihr habt noch kein Tor."), 2200);
  d.toreZu = zu;
  geraeusch(zu ? "tuerZu" : "tuerAuf");
  meldung(zu ? (tore === 1 ? t("Das Tor ist verriegelt – niemand kommt mehr durch, auch du nicht.")
                           : t("Alle {0} Tore sind verriegelt – niemand kommt mehr durch, auch du nicht.", tore))
             : t("Die Tore stehen offen."), 3400);
  if (typeof bauAufbauen === "function") bauAufbauen();
  speichernBald();
}

// Steht ueberhaupt schon ein Stueck Mauer? Solange nicht, ist im Dorf auch keine Rede davon
const hatMauer = () => Object.keys(mauerteile()).length > 0;
// Wie dicht ist die Grenze? Tore zaehlen halb - daraus kommt die Resilienz
function deckung() {
  const grenze = grenzKacheln();
  if (!grenze.length) return 0;
  const teile = mauerteile();
  const gedeckt = grenze.reduce((s, k) => s + (natuerlich(k) ? 1 : mauerWert(teile[k])), 0);
  // Auf 1 gedeckelt. Steinmauer zaehlt doppelt, Wehrgang dreifach - ein Drittel Wehrgang und
  // zwei Drittel offene Grenze ergaben rechnerisch 1,0. Der Erfolg "Die Grenze ganz gedeckt"
  // und die Mauer-Aufgabe galten damit als erfuellt, waehrend die Monster durch zwei Drittel
  // offene Kacheln hereinspazierten. Die Anzeigen deckelten laengst, die Pruefungen nicht.
  return Math.min(1, gedeckt / grenze.length);
}
// Mauer setzen: wie das Abstecken ein eigener Zustand, ← → waehlt Mauer oder Tor
let mauerModus = null;                                     // { art: "mauer" | "tor" }
// Palisade aus Holz, Tor aus beidem, Steinmauer aus Stein: die zweite Stufe haelt doppelt so viel
// Der Wehrgang war seit der ersten Fragerunde beschlossen und nie gebaut - das Blatt dafuer
// liegt seit Anfang an im Zaunpaket (Assets/15_zaeune_mauern/timber fort catwalk). Er ist die
// teuerste Stufe: eine Palisade mit Laufgang dahinter, von dem aus die Wachen kaempfen.
const MAUER_KOSTEN = { mauer: { holz: 1 }, tor: { holz: 2, stein: 1 }, stein: { stein: 2 },
                       wehrgang: { holz: 4, stein: 1 } };
const MAUER_NAME = { mauer: "Palisade", tor: "Tor", stein: "Steinmauer", wehrgang: "Wehrgang" };
const MAUER_ARTEN = ["mauer", "tor", "stein", "wehrgang"];
const MAUER_WERT = { mauer: 1, tor: 0.5, stein: 2, wehrgang: 3 };   // so viel zaehlt ein Stueck fuer die Deckung
const mauerWert = art => (art === "tor" && dorf().toreZu ? 1.2 : MAUER_WERT[art] || 0);   // zu: fast wie Mauer
// Fallen: Gruben und Stolperseile vor der Mauer. Jede haelt in der Nacht einen Angreifer auf
// und ist danach ausgeloest. Sie stehen auf Grenzkacheln wie die Mauer.
const FALLE_KOSTEN = { holz: 2 };
const fallen = () => { const d = dorf(); return d ? (d.fallen = d.fallen || {}) : {}; };
const fallenBereit = () => Object.values(fallen()).filter(f => f).length;
function falleSetzen() {
  const d = dorf();
  // Wie der Mauerbau: drinnen gibt es keine Grenze. Vorher kam hier nur eine Fehlmeldung,
  // weil mauerPlatz() mit Zimmerkoordinaten rechnete.
  if (typeof innen !== "undefined" && innen) return meldung(t("Erst vor die Tür."), 2000);
  const k = mauerPlatz();
  if (!grenzKacheln().includes(k)) return meldung(t("Fallen kommen an die Grenze des Dorfes."), 2600);
  if (fallen()[k]) return meldung(t("Hier liegt schon eine Falle."), 2200);
  if (natuerlich(k)) return meldung(t("Hier hält Wasser oder Fels schon von allein."), 2600);
  const fehlt2 = Object.entries(FALLE_KOSTEN).filter(([v, n]) => d.vorrat[v] < n);
  if (fehlt2.length) return meldung(t("Dafür fehlt Holz ({0}).", FALLE_KOSTEN.holz), 2800);
  if (typeof mauerHandlung === "function" && !mauerHandlung()) return;
  for (const [v, n] of Object.entries(FALLE_KOSTEN)) d.vorrat[v] -= n;
  fallen()[k] = true;
  geraeusch("holz");
  const bereit = fallenBereit();
  meldung(bereit === 1 ? t("Falle gestellt – 1 hält in der Nacht.")
                       : t("Falle gestellt – {0} halten in der Nacht.", bereit), 3000);
  if (typeof bauAufbauen === "function") bauAufbauen();
  speichernBald();
}
// Nachts: jede bereite Falle haelt einen Angreifer auf und ist danach hin
function fallenAusloesen(wieviele) {
  const f = fallen();
  const bereit = Object.keys(f).filter(k => f[k]);
  const wirkt = Math.min(bereit.length, wieviele);
  bereit.slice(0, wirkt).forEach(k => { f[k] = false; });
  return wirkt;
}
function falleZeichnen(c, x, y, scharf) {
  const px = x * TK, py = y * TK;
  c.fillStyle = scharf ? "#2a1d13" : "#4a3a2a";
  c.fillRect(px + 2, py + 5, TK - 4, 8);
  if (!scharf) return;
  c.fillStyle = "#6a5a3a";                                 // Reisig darüber
  for (let i = 0; i < 4; i++) c.fillRect(px + 2 + i * 3, py + 6 + (i % 2), 2, 1);
}

function mauerStarten() {
  if (innen) return;
  if (typeof abstecken !== "undefined" && abstecken) abstecken = false;
  if (!grenzKacheln().length) return meldung(t("Dein Dorf hat noch keine Grenze."), 2500);
  mauerModus = { art: "mauer" };
  // Der Hinweis zählte die Arten einzeln auf und vergaß dabei den Wehrgang, seit es ihn gibt.
  // Jetzt kommt er aus MAUER_ARTEN - so kann er nicht wieder veralten.
  meldung(t("Mauer setzen: ← → wählt {0}, Enter setzt, Esc beendet.",
            MAUER_ARTEN.map(a => t(MAUER_NAME[a])).join(", ")), 4200);
}
// Die Kachel vor der Figur - über zelleVon(), nicht über eine eigene Rechnung. Vorher stand
// hier (spieler.y - 4) statt der Weltkonvention (y - 2): gemessen zeigte die Mauer dadurch in
// 8 von 64 Fußpositionen eine Kachel zu weit oben. Man stand richtig an der Grenze und bekam
// trotzdem "Die Mauer gehört an den Rand des Dorfes". Dasselbe galt für Fallen.
const mauerPlatz = () => {
  const [dx, dy] = RICHTUNG[spieler.blick];
  const [zx, zy] = zelleVon(spieler.x, spieler.y);
  return `${zx + dx},${zy + dy}`;
};
function mauerSetzen() {
  if (!mauerModus) return;
  const k = mauerPlatz(), teile = mauerteile(), d = dorf();
  // Ein Gerüst wieder abbauen: dasselbe Recht wie bei fertigen Stücken
  if (mauerRoh()[k]) {
    const roh = mauerRoh()[k];
    const zurueck2 = MAUER_KOSTEN[roh.art] || { holz: 1 };
    delete mauerRoh()[k];
    for (const [v, n] of Object.entries(zurueck2)) d.vorrat[v] += n;
    bauAufbauen();
    return meldung(t("Das Gerüst ist wieder weg – {0} zurück.",
      Object.entries(zurueck2).map(([v, n]) => `${n} ${VORRAT_NAME[v]}`).join(t(" und "))), 2400);
  }
  if (teile[k]) {                                          // Abreissen geht immer - auch mitten im Dorf
    // Zurueck kommt, was das Stueck gekostet hat. Vorher gab es immer genau 1 Holz - wer eine
    // Steinmauer oder einen Wehrgang abriss, verlor dabei Material.
    const zurueck = MAUER_KOSTEN[teile[k]] || { holz: 1 };
    const warTor = teile[k] === "tor";
    delete teile[k];
    for (const [v, n] of Object.entries(zurueck)) d.vorrat[v] += n;
    // War das das letzte Tor, kann man die Tore nicht mehr schalten - toreSchalten() kehrt
    // bei null Toren vorher um. Das Merkmal blieb dann fuer immer auf "verriegelt" stehen,
    // und der Nachtbericht meldete jede Nacht "Die Tore waren verriegelt".
    if (warTor && d.toreZu && !Object.values(teile).some(a => a === "tor")) d.toreZu = false;
    bauAufbauen();
    return meldung(t("Wieder abgerissen – {0} zurück.",
      Object.entries(zurueck).map(([v, n]) => `${n} ${VORRAT_NAME[v]}`).join(t(" und "))), 2400);
  }
  if (mauerRoh()[k]) return meldung(t("Hier wird schon gebaut – morgen früh steht es."), 2600);
  if (!grenzKacheln().includes(k)) return meldung(t("Die Mauer gehört an den Rand des Dorfes."), 2500);
  if (natuerlich(k)) return meldung(t("Hier hält Wasser oder Fels schon von allein."), 2600);
  const kosten = MAUER_KOSTEN[mauerModus.art];
  if (Object.entries(kosten).some(([v, n]) => d.vorrat[v] < n))
    return meldung(t("Dafür fehlt Material ({0}).",
      Object.entries(kosten).map(([v, n]) => `${n} ${VORRAT_NAME[v]}`).join(", ")), 3000);
  if (typeof mauerHandlung === "function" && !mauerHandlung()) return;   // fünf Stücke = eine Handlung
  for (const [v, n] of Object.entries(kosten)) d.vorrat[v] -= n;
  // Heute wird angefangen, morgen steht es. Bis dahin ein Lattengerüst - es hält nichts auf
  // und zählt nicht für die Deckung.
  mauerRoh()[k] = { art: mauerModus.art, tag: d.tag };
  geraeusch("haemmern");
  bauAufbauen();
  speichernBald();
  meldung(t("{0} angefangen – morgen früh steht es.", t(MAUER_NAME[mauerModus.art])), 2600);
}
// Ein Geruest: der Lattenzaun aus dem Zaunpaket (4 Spalten x 3 Zeilen a 16 px). Er lag seit
// Anfang ungenutzt im Paket und passt genau: leichte Latten, durch die man hindurchsieht -
// erkennbar etwas anderes als eine fertige Mauer.
const GERUEST_SPALTEN = [0, 1, 2, 3];
function geruestZeichnen(c, x, y) {
  const bild = welt && welt.zaun;
  if (!bild) {                                             // solange das Blatt laedt: zwei Latten
    c.fillStyle = "#8a6a4a";
    c.fillRect(x * TK + 3, y * TK + 4, 2, 11);
    c.fillRect(x * TK + 11, y * TK + 4, 2, 11);
    c.fillStyle = "#6b4424";
    c.fillRect(x * TK + 1, y * TK + 7, 14, 2);
    return;
  }
  const sp = GERUEST_SPALTEN[(typeof hash === "function" ? hash(`${x},${y}g`) : x + y) % GERUEST_SPALTEN.length];
  c.drawImage(bild, sp * 16, 16, 16, 16, x * TK, y * TK, 16, 16);
}
// Palisade und Tor werden gezeichnet, nicht aus Kacheln gebaut
// Die Palisade kommt aus dem Zaun-Paket: 19 Spalten à 16 Pixel breit und 64 hoch, unten buendig
// auf die Kachel gesetzt. Gerade Stuecke wechseln sich ab, damit die Mauer nicht gestempelt wirkt.
const PAL_GERADE = [0, 1, 5, 6];                           // gerade Wandstuecke (Vorderansicht)
const PAL_TOR = [14, 15];                                  // die beiden Torfluegel
function mauerZeichnen(c, x, y, art) {
  const bild = welt && welt.palisade;
  if (!bild) return mauerZeichnenAlt(c, x, y, art);        // solange das Blatt noch laedt
  if (art === "stein") return steinmauerZeichnen(c, x, y);
  if (art === "wehrgang") return wehrgangZeichnen(c, x, y);
  let spalte;
  if (art === "tor") {
    const links = mauerteile()[`${x - 1},${y}`] === "tor";
    spalte = PAL_TOR[links ? 1 : 0];                       // zwei nebeneinander ergeben ein ganzes Tor
  } else {
    spalte = PAL_GERADE[hash(`${x},${y}`) % PAL_GERADE.length];
  }
  c.drawImage(bild, spalte * 16, 0, 16, 64, x * TK, (y + 1) * TK - 64, 16, 64);
}
// Steinmauer (zweite Stufe) aus dem Zaunpaket. Gezeichnet wird Blattzeile 1 - der
// durchgehende Mauerkoerper. Vorher nahm der Ausschnitt die Zeilen 0 und 1 zusammen:
// Zeile 0 sind lose Steinbrocken, die dann ueber der Mauer schwebten. Spalte 5 ist ein
// Eckstueck und erschien mitten in der Reihe als dunkler Riss - beide sind jetzt draussen.
const STEIN_SPALTEN = [4, 6];
function steinmauerZeichnen(c, x, y) {
  const bild = welt && welt.steinmauer;
  if (!bild) { c.fillStyle = "#9a9a8a"; c.fillRect(x * TK, y * TK - 6, TK, 20); return; }
  const sp = STEIN_SPALTEN[hash(`${x},${y}s`) % STEIN_SPALTEN.length];
  c.drawImage(bild, sp * 16, 16, 16, 16, x * TK, (y + 1) * TK - 22, 16, 16);
}
// Wehrgang: Palisade mit Laufgang dahinter (Assets/15_zaeune_mauern/timber fort catwalk 16x16.png,
// 7 x 5 Kacheln). Die Wand steht in den unteren Zeilen, der Laufgang mit Gelaender oben.
// Gezeichnet wird ein zwei Kacheln hoher Ausschnitt, unten buendig wie die Palisade.
const WEHR_SPALTEN = [1, 2, 3, 4];                         // gerade Stuecke des Laufgangs
function wehrgangZeichnen(c, x, y) {
  const bild = welt && welt.wehrgang;
  if (!bild) return mauerZeichnen(c, x, y, "mauer");       // solange das Blatt laedt: Palisade
  const sp = WEHR_SPALTEN[hash(`${x},${y}w`) % WEHR_SPALTEN.length];
  // Wand (Blattzeilen 3-4) unten, darueber der Laufgang (Blattzeile 0)
  c.drawImage(bild, sp * 16, 3 * 16, 16, 32, x * TK, (y + 1) * TK - 32, 16, 32);
  c.drawImage(bild, sp * 16, 0, 16, 16, x * TK, (y + 1) * TK - 46, 16, 16);
}
// Rueckfall, falls das Bild fehlt: die alte gezeichnete Palisade
function mauerZeichnenAlt(c, x, y, art) {
  const px = x * TK, py = y * TK;
  if (art === "tor") {
    c.fillStyle = "#2a1a10"; c.fillRect(px, py + 4, TK, 10);
    c.fillStyle = "#9a6a3a"; c.fillRect(px + 1, py + 5, TK - 2, 8);
    c.fillStyle = "#6a4424"; c.fillRect(px + 1, py + 9, TK - 2, 2);
    c.fillStyle = "#3a2414"; c.fillRect(px + 7, py + 5, 2, 8);
    return;
  }
  c.fillStyle = "#2a1a10"; c.fillRect(px, py - 6, TK, 20);
  for (let i = 0; i < 4; i++) {
    const sx = px + i * 4;
    c.fillStyle = "#9a6a3a"; c.fillRect(sx + 1, py - 5, 3, 18);
    c.fillStyle = "#6a4424"; c.fillRect(sx + 3, py - 5, 1, 18);
    c.fillStyle = "#c08a50"; c.fillRect(sx + 1, py - 6, 2, 1);
  }
  c.fillStyle = "#6a4424"; c.fillRect(px, py + 2, TK, 2);
}

// ---------------------------------------------------------------------------
// Nachbardoerfer auf derselben Karte
// ---------------------------------------------------------------------------
function nachbardorfListe() {
  const d = dorf();
  if (!d) return [];                                         // waehrend des Weltwechsels
  if (!d.nachbarn) {
    const r = zufallsquelle(mische(stand.universum.saat, 606));
    d.nachbarn = [];
    const wieviele = 2;                                    // zwei Nachbardörfer reichen
    for (let i = 0; i < wieviele; i++) {
      for (let versuch = 0; versuch < 60; versuch++) {
        const p = `${Math.floor(r() * parzBreite())},${Math.floor(r() * parzHoehe())}`;
        const zuNah = [...felder(), ...d.nachbarn.flatMap(n => n.felder)]
          .some(q => { const [a, b] = parzTeile(q), [c2, e] = parzTeile(p); return Math.abs(a - c2) < 3 && Math.abs(b - e) < 3; });
        if (zuNah || besitzer(p)) continue;
        const anzahl = 2 + Math.floor(r() * 2);
        const dorfId = `n${i}`;
        // Auch die Nachbarn fangen als Dorf an, nicht als Hütte: 2x2 Parzellen
        const [nx, ny] = parzTeile(p);
        const block = [];
        for (let dy = 0; dy <= 1; dy++) for (let dx = 0; dx <= 1; dx++) {
          const q = `${Math.min(parzBreite() - 1, nx + dx)},${Math.min(parzHoehe() - 1, ny + dy)}`;
          if (!block.includes(q) && !besitzer(q)) block.push(q);
        }
        d.nachbarn.push({ id: dorfId, name: weltName(r), felder: block, gesinnung: 0, leute: anzahl,
                          handel: handelErzeugen(r),
                          bewohner: Array.from({ length: anzahl }, (_, j) => ({
                            id: `${dorfId}_${j}`, name: personName(r), figur: zufallsFigur(r), stimme: wuerfel(r, 1e9) })) });
        break;
      }
    }
  }
  for (const n of d.nachbarn) {                            // aeltere Laeufe nachziehen
    if (!n.handel || !n.bewohner) {
      const r2 = zufallsquelle(mische(stand.universum.saat, hash(n.id) * 17));
      n.handel = n.handel || handelErzeugen(r2);
      n.bewohner = n.bewohner || Array.from({ length: n.leute }, (_, j) => ({
        id: `${n.id}_${j}`, name: personName(r2), figur: zufallsFigur(r2), stimme: wuerfel(r2, 1e9) }));
    }
  }
  if (d.nachbarn.length) d.nachbarnGesehen = true;   // fuer die Siegpruefung: es gab welche
  return d.nachbarn;
}
// Leute der Nachbardoerfer - zu sehen sind sie erst, wenn dort das erste Haus steht
const nachbarLeute = () => nachbardorfListe().filter(n => (n.gebaut || 0) > 0)
  .flatMap(n => n.bewohner.slice(0, n.gebaut).map(b => ({ ...b, dorfId: n.id })));
const dorfVon = id => nachbardorfListe().find(n => n.bewohner.some(b => b.id === id)) || null;
// Alle paar Naechte nimmt sich ein Nachbardorf eine Parzelle dazu
function nachbarnWachsen() {
  const d = dorf();
  if (d.naechte % 3) return;
  const r = zufallsquelle(mische(stand.universum.saat, d.naechte * 13 + 5));
  for (const n of nachbardorfListe()) {
    if ((n.gebaut || 0) < n.leute) {                       // erst bauen sie ihre Häuser, dann breiten sie sich aus
      n.gebaut = (n.gebaut || 0) + 1;
      d.bericht.push(bz("In {0} steht ein neues Haus.", n.name));
      continue;
    }
    if (n.felder.length >= 5) continue;                    // sie wachsen, aber nicht ins Unendliche
    const frei = [...new Set(n.felder.flatMap(anliegend))].filter(p => !besitzer(p));
    if (!frei.length) continue;
    const neu = frei[Math.floor(r() * frei.length)];
    n.felder.push(neu);
    d.bericht.push(bz("{0} hat sich ausgebreitet.", n.name));
  }
}
// Es gibt kein Gewinnen mehr (Konzeptkonferenz K3). Der Satz des Spielers dazu war:
// "Es gibt nicht 'Ich habe gewonnen, weil ich X Nächte überlebt habe', sondern 'Ich habe
// verloren, weil alle draufgegangen sind'." Vorher verlangte der Sieg zwei Drittel des Tals
// und alle Nachbardoerfer - ein Ziel, das ueber zehn gemessene Laeufe nie erreicht wurde und
// zu keinem der beiden Konzepte gehoerte. Land nimmt man jetzt, weil man Platz braucht.
// Wenn das ganze Tal zusammensteht, ist das ein Moment, kein Abschluss: der Lauf geht weiter
// wie vorher, und er endet erst, wenn niemand mehr da ist.
// Ein angeschlossenes Dorf verschwindet aus der Liste (assimilieren in quests.js) - alle
// angeschlossen heisst also: die Liste ist leer, und es gab ueberhaupt einmal welche.
function nachbarnAngeschlossen() {
  const d = dorf();
  const liste = typeof nachbardorfListe === "function" ? nachbardorfListe() : [];
  if (liste.length) return false;                          // es stehen noch welche offen
  // Ein Stand, der vor dieser Regel entstand, hat kein "nachbarnGesehen" - waere der Sieg
  // dort an das Flag geknuepft, bliebe er fuer immer unmoeglich. Eine leere Liste bei
  // bestehendem Dorf heisst: es gab welche, und sie sind angeschlossen.
  // Nur fuer Altstaende, die das Merkmal noch nicht kannten - und nur, wenn es ueberhaupt
  // je Nachbarn gab. Ohne diese zweite Bedingung feierte ein Tal ohne einen einzigen
  // Nachbarn nach der ersten Nacht seinen Zusammenschluss.
  if (d.nachbarnGesehen === undefined) d.nachbarnGesehen = d.naechte > 0 && !!(d.nachbarn && d.nachbarn.length);
  return !!d.nachbarnGesehen;
}
// Wofuer das Land gut ist - fuer die Dorfkarte. Kein Ziel, sondern ein Stand.
function landStand() {
  const land = felder().length;
  const liste = typeof nachbardorfListe === "function" ? nachbardorfListe() : [];
  return { land, alle: parzAnzahl(), frei: erweiterbar().length, doerfer: liste.map(n => n.name),
           talZusammen: nachbarnAngeschlossen() };
}
// Steht das ganze Tal zusammen, ist das eine Nachricht im Dorfbuch - und sonst nichts.
// Weder endet der Lauf, noch aendert sich etwas an den Naechten.
function talZusammenPruefen() {
  const d = dorf();
  if (d.talGefeiert) return false;
  if (!nachbarnAngeschlossen()) return false;
  d.talGefeiert = true;
  // Eine Zeile im Dorfbuch, sonst nichts - so steht es in K3. Fanfare und Vollbildmeldung
  // waren die alte Siegesfeier in klein; das Tal zusammenzuführen ist kein Abschluss.
  d.bericht.push(bz("Das Tal steht zusammen – {0} und seine Nachbarn.", stand.dorfname || t("euer Dorf")));
  speichernBald();
  return true;
}

// ---------------------------------------------------------------------------
// Dorfkarte (E): Parzellen waehlen und einnehmen
// ---------------------------------------------------------------------------
let karteOffen = false, karteWahl = null;
function dorfkarteZeigen(an) {
  if (menueWechsel(an ? "dorfkarte" : null) === false) return;
  karteOffen = an;
  document.getElementById("dorfkarte").hidden = !an;
  if (!an) return;
  const frei = erweiterbar();
  if (!karteWahl || !frei.includes(karteWahl)) karteWahl = frei[0] || null;
  dorfkarteMalen();
  const reden = nachbarnAmRand().length ? ` · <kbd>T</kbd> ${t("mit {0} verhandeln", nachbarnAmRand()[0].name)}` : "";
  // Wofuer nimmt man Land? Fuer Bauplatz und Aecker - nicht fuer einen Sieg (K3).
  const so = landStand();
  const zielText = (so.frei ? t("{0} Felder liegen zum Einnehmen bereit", so.frei) : t("Hier ist kein Feld mehr frei"))
    + (so.doerfer.length ? ` · ${t("{0} anschließen", so.doerfer.join(t(" und ")))}` : "");
  const stand2 = t("{0} von {1} Feldern", so.land, so.alle);
  const sperre = typeof erweiternGrund === "function" ? erweiternGrund() : "";
  document.getElementById("kartefuss").innerHTML = sperre
    ? `${stand2} · ${sperre}${reden} · <kbd>E</kbd> ${t("schließen")}`
    : karteWahl
      ? `${stand2} · ${zielText} · <kbd>←</kbd><kbd>→</kbd> ${t("wählen")} · <kbd>Enter</kbd> ${t("einnehmen")}${reden} · <kbd>E</kbd> ${t("schließen")}`
      : `${stand2} · ${zielText}${reden} · <kbd>E</kbd> ${t("schließen")}`;
}
function dorfkarteMalen() {
  const c = document.getElementById("kartebild");
  const g = c.getContext("2d");
  const b = parzBreite(), h = parzHoehe(), z = Math.floor(Math.min(c.width / b, c.height / h));
  const ox = Math.floor((c.width - b * z) / 2), oy = Math.floor((c.height - h * z) / 2);
  g.imageSmoothingEnabled = false;
  g.fillStyle = "#23301e"; g.fillRect(0, 0, c.width, c.height);
  if (welt && welt.boden) g.drawImage(welt.boden, ox, oy, b * z, h * z);
  g.globalAlpha = 0.55;
  for (let y = 0; y < h; y++) for (let x = 0; x < b; x++) {
    const p = `${x},${y}`, wem = besitzer(p);
    g.fillStyle = wem === "dorf" ? "#e8b84a" : wem ? "#8a2a2a" : "rgba(20,16,12,.55)";
    g.fillRect(ox + x * z, oy + y * z, z - 1, z - 1);
  }
  g.globalAlpha = 1;
  for (const k of grenzKacheln()) {                        // Mauerstuecke als helle Punkte auf der Grenze
    const [x, y] = k.split(",").map(Number), art = mauerteile()[k];
    if (!art) continue;
    g.fillStyle = art === "tor" ? "#9a6a3a" : "#f6ead0";
    g.fillRect(ox + Math.floor(x / PARZ * z) + 1, oy + Math.floor(y / PARZ * z) + 1, 3, 3);
  }
  // Ein voller Punkt: hier steht ein Haus allein. Ein Ring: nur zusammen mit dem Nachbarfeld.
  const { allein: bauAllein, zuZweit: bauPaar } = bauFelderGeteilt();
  const bauBar = new Set([...bauAllein, ...bauPaar]);
  for (const p of bauAllein) {
    const [x, y] = parzTeile(p);
    g.fillStyle = "#6ad04a";
    g.fillRect(ox + x * z + Math.floor(z / 2) - 2, oy + y * z + Math.floor(z / 2) - 2, 4, 4);
  }
  for (const p of bauPaar) {
    const [x, y] = parzTeile(p);
    const mx = ox + x * z + Math.floor(z / 2) - 3, my = oy + y * z + Math.floor(z / 2) - 3;
    g.fillStyle = "#6ad04a";
    g.fillRect(mx, my, 6, 1); g.fillRect(mx, my + 5, 6, 1);
    g.fillRect(mx, my + 1, 1, 4); g.fillRect(mx + 5, my + 1, 1, 4);
  }
  if (karteWahl) {
    const [x, y] = parzTeile(karteWahl);
    g.strokeStyle = "#f6ead0"; g.lineWidth = 2;
    g.strokeRect(ox + x * z + 1, oy + y * z + 1, z - 3, z - 3);
  }
  const liste = document.getElementById("kartelegende");
  liste.innerHTML = [[t("Dein Dorf"), "#e8b84a", t("{0} Felder", felder().length)],
                     ...nachbardorfListe().map(n => [n.name, "#8a2a2a", t("{0} Felder", n.felder.length)]),
                     // Auf 100 % gedeckelt wie im Dorfbuch - Steinmauer zaehlt doppelt, Wehrgang dreifach,
                     // und eine gut gebaute Grenze stand hier sonst mit "160 %"
                     [t("Mauer"), "#f6ead0", t("deckt {0} % der Grenze", Math.round(Math.min(1, deckung()) * 100))],
                     // Wie viele warten ueberhaupt auf ein Grundstueck? Ohne diese Zahl las sich
                     // "3 Felder Bauland" wie "es ist genug da", obwohl fuenf Leute anstehen und
                     // bauAufbauen() die Plaetze der Reihe nach vergibt - wer wartet, bleibt liegen.
                     [t("Bauland"), "#6ad04a", bauBar.size
                       ? (() => {
                           const warten = Object.entries(stand.bau || {})
                             .filter(([id, b]) => id !== LAGER_ID && b && !b.platz && (b.fertig || b.material > 0)).length;
                           const grund = bauPaar.size
                             ? t("{0} tragen ein Haus allein, {1} nur zu zweit (Ring)", bauAllein.size, bauPaar.size)
                             : t("{0} freie Felder, auf denen ein Haus Platz hat", bauAllein.size);
                           return warten > bauAllein.size
                             ? grund + " – " + t("{0} warten auf ein Grundstück", warten)
                             : grund;
                         })()
                                                           : t("kein freies Feld taugt zum Bauen")]]
    .map(([n, f, was]) => `<div><i style="background:${f}"></i><b>${n}</b><small>${was}</small></div>`).join("");
}
// ---------------------------------------------------------------------------
// Konzept: Das Dorf wächst nicht auf Vorrat. Land nimmt man erst, wenn beides erschöpft ist -
// es hat niemand mehr ein Bett (keine Hauskapazität) UND es passt kein Haus mehr aufs eigene
// Land (keine Platzkapazität). Vorher konnte man sich von der ersten Minute an die halbe
// Karte nehmen; jede neue Parzelle verlängert aber die Grenze und verdünnt die Mauer.
// Rückgabe: "" = erlaubt, sonst der Grund.
// ---------------------------------------------------------------------------
// Passt ueberhaupt noch ein Krankenlager aufs eigene Land? Die Suche geht ueber die ganze
// Karte und der Kartenfuss fragt bei jedem Tastendruck - darum gemerkt, solange sich weder
// Tag noch Landbesitz geaendert haben.
let lagerPlatzMerk = { schluessel: "", passt: true };
function lagerPasstAufsLand() {
  if (typeof platzSuchen !== "function" || typeof LAGER_ID === "undefined" || !welt) return true;
  const d = dorf();
  const schluessel = `${d.tag}|${felder().length}|${d.krankenlager ? 1 : 0}`;
  if (lagerPlatzMerk.schluessel !== schluessel)
    lagerPlatzMerk = { schluessel, passt: !!platzSuchen(LAGER_ID) };
  return lagerPlatzMerk.passt;
}

function erweiternGrund() {
  if (typeof lebende !== "function" || typeof betten !== "function") return "";
  const d = dorf();
  // Ein Krankenlager ist sechs Kacheln breit, breiter als jedes Haus. Passt keins mehr aufs
  // eigene Land, schickt krankenlagerBauen() dich mit "nimm Land dazu (E)" zur Karte - und
  // dort stand bisher "noch hat jeder ein Bett". Das Spiel verwies auf eine Tür, die es
  // selbst zuhielt. Ein Lager gibt es nur einmal im Dorf, das ist keine Vorratshaltung.
  if (d && !d.krankenlager && !d.lagerRoh && !lagerPasstAufsLand()) return "";
  // Wer ein fertiges Haus hat, aber keinen Platz dafuer, ist ein Grund zu erweitern - auch
  // wenn rechnerisch noch Betten frei sind. betten() zaehlt je Haus 2 + Baumeisterstufe,
  // es "wohnen" also mehrere zusammen; bei sechs Haeusern und neun Leuten kommen so 18
  // Betten heraus und das Spiel meldete "Noch hat jeder ein Bett", waehrend daneben ein
  // bezahltes Haus auf ein Grundstueck wartete. Gemessen an einer echten Welt (Indie.ducx).
  const wartetAufGrund = Object.entries(stand.bau || {})
    .some(([id, b]) => id !== LAGER_ID && b && b.fertig && !b.platz);
  const ohneDach = lebende().length - betten();
  if (!wartetAufGrund && ohneDach <= 0)
    return t("Noch hat jeder ein Bett. Land nimmt man erst, wenn es eng wird.");
  if (typeof bauplatzIn === "function" && bauplatzIn(felder()))
    return t("Auf eurem Land ist noch Bauplatz – erst dort bauen, dann erweitern.");
  return "";
}
function feldNehmen() {
  if (!karteWahl) return;
  const grund = erweiternGrund();
  if (grund) return meldung(grund, 4000);
  if (typeof grosseHandlung === "function" && !grosseHandlung(t("Noch ein Feld"))) return;
  felder().push(karteWahl);
  innenMauerAbbauen();                                     // was jetzt mitten im Dorf steht, wandert ins Lager
  innenFallenRaeumen();                                    // und die Fallen gleich mit
  innenMauerRohRaeumen();                                  // auch angefangene Stücke
  geraeusch("fanfare");
  meldung(t("Feld eingenommen – {0} von {1}.", felder().length, parzAnzahl()), 3000);
  bauAufbauen();
  speichernBald();
  if (!talZusammenPruefen()) dorfkarteZeigen(true);
}
// Wo wuerde ein Haus hinpassen? Ein Grundstueck ist 4x6 Kacheln, eine Parzelle nur 4x4 -
// oft braucht es zwei uebereinander. Die Dorfkarte zeigt damit, welches Feld sich lohnt.
function bauplatzIn(felderliste) {
  if (typeof platzPruefen !== "function" || !welt) return false;
  const neu2 = felderliste.filter(p => !felder().includes(p));
  neu2.forEach(p => felder().push(p));
  let gut = false;
  for (const f of felderliste) {
    const [px, py] = parzTeile(f);
    for (let y = py * PARZ - HH + 1; y <= (py + 1) * PARZ && !gut; y++)
      for (let x = px * PARZ - HB + 1; x <= (px + 1) * PARZ && !gut; x++)
        if (x >= 1 && y >= 4 && !platzPruefen(x, y, null, true)) gut = true;
  }
  neu2.forEach(() => felder().pop());
  return gut;
}
// Freie Felder, auf denen wirklich gebaut werden kann - getrennt danach, ob eines reicht.
// Ein Haus ist 6 Kacheln hoch, eine Parzelle nur 4: oft braucht es zwei uebereinander.
// Vorher lagen beide Gruppen in einem Topf und sahen auf der Karte gleich aus. Wer ein
// Feld nahm, auf dem ein Haus erst ZU ZWEIT steht, sah danach: nichts passiert - und
// erfuhr nirgends, warum. (Wolfgangs Welt Indie.ducx: dorf_7 passte erst nach dem zweiten.)
function bauFelderGeteilt() {
  const frei = erweiterbar();
  const allein = new Set(), zuZweit = new Set();
  for (const p of frei) if (bauplatzIn([p])) allein.add(p);
  for (const p of frei) {
    if (allein.has(p) || zuZweit.has(p)) continue;
    for (const q of anliegend(p)) {
      if (allein.has(q) || !frei.includes(q)) continue;
      if (bauplatzIn([p, q])) { zuZweit.add(p); zuZweit.add(q); break; }
    }
  }
  return { allein, zuZweit };
}
// Alle bebaubaren zusammen - so, wie der Rest des Spiels sie bisher erwartet
function bauFelder() {
  const { allein, zuZweit } = bauFelderGeteilt();
  return new Set([...allein, ...zuZweit]);
}
// Palisaden, die durch das Wachstum im Inneren gelandet sind, kommen zurueck ins Lager
// Nimmt man Land hinter einer Grenze, ist die alte Grenzkachel keine Grenze mehr. Die Mauer
// wandert dann ins Lager - die Fallen lagen bisher weiter dort und loesten mitten im Dorf aus.
function innenMauerRohRaeumen() {
  const d = dorf();
  if (!d) return 0;
  const grenze = grenzKacheln(), roh = mauerRoh();
  let weg = 0;
  for (const [k, e] of Object.entries(roh)) {
    if (grenze.includes(k)) continue;
    for (const [v, n] of Object.entries(MAUER_KOSTEN[e.art] || { holz: 1 })) d.vorrat[v] += n;
    delete roh[k];
    weg++;
  }
  return weg;
}
function innenFallenRaeumen() {
  const d = dorf();
  if (!d || typeof fallen !== "function") return 0;
  const grenze = grenzKacheln();
  let zurueck = 0;
  for (const k of Object.keys(fallen())) {
    if (grenze.includes(k)) continue;
    delete fallen()[k];
    for (const [v, n] of Object.entries(FALLE_KOSTEN)) d.vorrat[v] += n;
    zurueck++;
  }
  if (zurueck) d.bericht.push(zurueck === 1
    ? bz("Eine Falle lag plötzlich mitten im Dorf – das Holz liegt wieder im Lager.")
    : bz("{0} Fallen lagen plötzlich mitten im Dorf – das Holz liegt wieder im Lager.", zurueck));
  return zurueck;
}
function innenMauerAbbauen() {
  const teile = mauerteile(), grenze = grenzKacheln(), d = dorf();
  let zurueck = 0;
  // Jedes Stueck bringt zurueck, was es gekostet hat - eine Steinmauer ist kein Holz wert
  for (const k of Object.keys(teile)) {
    if (grenze.includes(k)) continue;
    const kosten = MAUER_KOSTEN[teile[k]] || { holz: 1 };
    for (const [v, n] of Object.entries(kosten)) d.vorrat[v] += n;
    delete teile[k];
    zurueck++;
  }
  if (zurueck) {
    meldung(zurueck === 1 ? t("1 Mauerstück steht jetzt im Dorf – das Material ist zurück im Lager.")
                          : t("{0} Mauerstücke stehen jetzt im Dorf – das Material ist zurück im Lager.", zurueck), 4000);
  }
}

// Angrenzende Nachbardoerfer, mit denen man reden kann
const nachbarnAmRand = () => nachbardorfListe().filter(n =>
  n.felder.some(p => anliegend(p).some(q => felder().includes(q))));
function karteTaste(k) {
  const frei = erweiterbar();
  if (k === "e" || k === "Escape") { dorfkarteZeigen(false); return true; }
  if (k === "t") {                                           // verhandeln statt erobern
    const n = nachbarnAmRand()[0];
    if (!n) return meldung(t("Kein Nachbardorf grenzt an dein Land."), 2600), true;
    dorfkarteZeigen(false);
    verhandeln(n);
    return true;
  }
  if (!frei.length) return true;
  const i = Math.max(0, frei.indexOf(karteWahl));
  if (k === "ArrowRight" || k === "d" || k === "ArrowDown" || k === "s") karteWahl = frei[(i + 1) % frei.length];
  else if (k === "ArrowLeft" || k === "a" || k === "ArrowUp" || k === "w") karteWahl = frei[(i - 1 + frei.length) % frei.length];
  else if (k === "Enter" || k === " ") return feldNehmen() || true;
  dorfkarteZeigen(true);
  return true;
}
