extends Node ## Fährt das echte Spiel headless und prüft die Implementierungen. var fehler := 0 var main: Node3D func p(ok: bool, text: String) -> void: print((" OK " if ok else " FEHLT ") + text) if not ok: fehler += 1 func abschnitt(t: String) -> void: print("\n== " + t) func _ready() -> void: seed(20240) main = load("res://scenes/main.tscn").instantiate() add_child(main) await get_tree().process_frame await get_tree().process_frame await test_start() await test_journal() await test_traumdorf() await test_dorf_organisch() await test_haus_betreten() await test_hoersaal() await test_mission_annehmen() await test_reise_zur_erde() await test_bezirk() await test_tagesablauf() await test_wege_machbar() await test_geist() await test_technik_anwenden() await test_cooldown() await test_chance_verdeckt() await test_pipeline() await test_mission_abschliessen() await test_welt_waechst() await test_techniken_verbessern_sich() await test_journal_ueberall() await test_steuerung() await test_erster_traum_schaffbar() await test_brett_nie_leer() await test_lesen_lohnt_sich() await test_tempo() print("\n=========================================") print("Fehlende Prüfungen: %d" % fehler) print("=========================================") get_tree().quit(1 if fehler > 0 else 0) func ui() -> UI: return main.ui func welt() -> Node3D: return main.welt func schritte(n: int, dt: float = 0.05, zeit: float = -1.0) -> void: for i in range(n): if zeit >= 0.0: Game.time_h = zeit main._process(dt) if welt().has_method("_process"): welt()._process(dt) for kind in welt().get_children(): if kind is Bewohner: kind._process(dt) func _panel_text() -> String: var out: Array = [] for c in ui()._content.get_children(): if c is RichTextLabel: out.append((c as RichTextLabel).get_parsed_text()) return "\n".join(out) func _journal_text() -> String: return _panel_text() func _lehre_text() -> String: return _panel_text() ## Hebt alle Technik-Sperren auf. Nur für Messschleifen, die viele Anwendungen ## in Folge brauchen — der Cooldown selbst wird eigens geprüft. func _frei() -> void: Game.cooldown.clear() ## Welche Gebäude stehen wirklich in der Traumwelt? Erkennbar an ihren Schildern. ## Was der Hörsaal von Anfang an lehrt. func _basiswege() -> Array: return DreamDB.techniken_von("hoersaal") func _gebaute_orte() -> Array: var namen: Array = [] for key in DreamDB.PLACES: if float(DreamDB.PLACES[key]["size"][0]) <= 0.0: continue namen.append(str(DreamDB.PLACES[key]["name"])) var da: Array = [] for n in welt().get_children(): if n is Label3D and namen.has((n as Label3D).text): da.append((n as Label3D).text) return da func _marke(kind: String) -> Node3D: for n in get_tree().get_nodes_in_group("interactable"): if n.get_meta("kind") == kind: return n return null ## Stellt den Spieler vor eine Marke und lässt den Fokus neu suchen. func _hingehen(ziel: Node3D) -> void: main.player.set_mode(Player.Mode.TRAUM) main.player.global_position = ziel.global_position + Vector3(0, 0, 1.2) main.player.velocity = Vector3.ZERO ui().schliessen() main._process(0.05) ## Drückt den ersten aktiven Knopf, dessen Beschriftung so beginnt. func _knopf_texte() -> String: var out: Array = [] for c in ui()._content.get_children(): if c is Button: out.append((c as Button).text) return "\n".join(out) func _knopf_da(anfang: String) -> bool: for c in ui()._content.get_children(): if c is Button and (c as Button).text.begins_with(anfang): return true return false func _druecke(anfang: String) -> bool: for c in ui()._content.get_children(): if c is Button and not (c as Button).disabled and (c as Button).text.begins_with(anfang): (c as Button).pressed.emit() return true return false func laufe(von: Vector3, yaw: float, frames: int) -> Vector3: main.player.set_mode(Player.Mode.TRAUM) main.player.look_enabled = true main.player.global_position = von main.player.velocity = Vector3.ZERO main.player.yaw = yaw Input.action_press("vor") for i in range(frames): await get_tree().physics_frame Input.action_release("vor") return main.player.global_position # -------------------------------------------------------------------- func test_start() -> void: abschnitt("Start") p(not ui().offen, "Kein Startbildschirm — das Spiel läuft sofort") p(welt() is DreamWorld, "Startwelt ist die Traumwelt") p(main.player != null and main.player.mode == Player.Mode.TRAUM, "Spieler läuft (kein Geist)") var arten: Dictionary = {} for n in get_tree().get_nodes_in_group("interactable"): arten[n.get_meta("kind")] = true p(arten.has("erde") and arten.has("lehre") and arten.has("brett"), "Traumwelt bietet Tor, Hörsaal und Schwarzes Brett (%s)" % ", ".join(arten.keys())) p(Game.known.size() == 1, "Ein Weg ist von Anfang an gelernt (%d)" % Game.known.size()) p(Game.knows(str(_basiswege()[0])), "Und zwar der des Hörsaals: %s" % TechDB.by_id(str(_basiswege()[0]))["name"]) p(Game.skill_of(str(_basiswege()[0])) == 1, "Auf Stufe 1, wie frisch gelernt") p(not Game.offene_missionen().is_empty(), "Damit hängt vom ersten Moment an eine Mission aus (%d)" % Game.offene_missionen().size()) p((welt() as DreamWorld).bewohner.is_empty(), "Das Traumdorf steht am Anfang leer (%d Wesen)" % (welt() as DreamWorld).bewohner.size()) var lebt := false for n in welt().get_children(): if n is Bewohner: lebt = true p(not lebt, "Auch kein Wesen im Szenenbaum") var gebaut := _gebaute_orte() p(gebaut.size() == 1 and gebaut.has("Hörsaal der Wege"), "Nur der Hörsaal steht als Gebäude (%s)" % ", ".join(gebaut)) func test_journal() -> void: abschnitt("Journal") for t in TechDB.LIST: var vollstaendig: bool = t["was"] != "" and t["wann"] != "" and t["warum"] != "" \ and t["woran"] != "" if not vollstaendig: p(false, "Technik %s hat alle vier Hinweise" % t["id"]) p(true, "Alle %d Techniken tragen Was/Wann/Warum/Woran" % TechDB.LIST.size()) # Der Woran-Hinweis muss jeden Moment der Technik wirklich benennen — # sonst ist die Technik im Spiel nicht lesbar. var stamm := { "schlaeft": "schläf", "morgenroutine": "morgenroutine", "arbeitet": "arbeit", "pause": "pause", "essen": "essen", "gruebelt": "grübel", "ruht": "ausruh", "angespannt": "angespannt", "abendroutine": "abendroutine", "aufraeumen": "aufräum", "wartet": "wartet", "muede": "müde", "unterwegs": "unterwegs", "zoegert": "zöger", } var luecken: Array = [] for t in TechDB.LIST: var text: String = str(t["woran"]).to_lower() for m in t["momente"]: if not stamm.has(m): luecken.append("%s: %s unbekannt" % [t["id"], m]) elif not text.contains(str(stamm[m])): luecken.append("%s nennt »%s« nicht" % [t["id"], m]) p(luecken.is_empty(), "Jeder Woran-Hinweis benennt alle Momente seiner Technik%s" % ("" if luecken.is_empty() else " — " + ", ".join(luecken))) # Aber er verrät nicht mehr als die Momente var zuviel: Array = [] for t in TechDB.LIST: var text: String = str(t["woran"]).to_lower() for m in stamm: if (t["momente"] as Array).has(m): continue if text.contains(str(stamm[m])): zuviel.append("%s nennt fremdes »%s«" % [t["id"], m]) p(zuviel.is_empty(), "Und keinen Moment, der nicht dazugehört%s" % ("" if zuviel.is_empty() else " — " + ", ".join(zuviel))) ui().journal_panel("techniken") p(ui().offen and ui()._kind == "journal", "Journal öffnet sich") p(_journal_text().contains(str(TechDB.by_id(str(_basiswege()[0]))["name"])), "Das Journal führt den mitgebrachten Weg") p(not _knopf_da("Diesen Weg"), "Das Journal lehrt nicht selbst") ui().journal_panel("missionen") p(_journal_text().contains("Schwarzen Brett"), "Ohne Mission verweist das Journal auf das Schwarze Brett") p(not _knopf_da("Mission annehmen"), "Das Journal vergibt keine Missionen") ui().schliessen() var erster := str(_basiswege()[0]) p(Game.offene_techniken() == 1, "Am Anfang ist genau ein Weg entdeckt (%d)" % Game.offene_techniken()) p(Game.knows(erster), "Er ist schon gelernt: %s" % TechDB.by_id(erster)["name"]) p(not Game.learn(erster), "Zweimal lernen geht nicht") p(not Game.learn(str(TechDB.LIST[1]["id"])), "Der zweite Weg ist nirgends zu lernen — er ist noch nicht entdeckt") p(Game.skill_of(erster) == 1, "Frisch gelernte Technik ist Stufe 1") Game.add_xp(erster, 4) p(Game.skill_of(erster) == 2, "4 EP heben auf Stufe 2") Game.add_xp(erster, 24) p(Game.skill_of(erster) == 5, "28 EP erreichen Stufe 5") Game.known[erster] = 0 ui().journal_panel("techniken") var buch := _journal_text() var alle_gelernten := true for id in Game.known: if not buch.contains(str(TechDB.by_id(id)["name"])): alle_gelernten = false p(alle_gelernten, "Das Journal führt alle %d gelernten Techniken" % Game.known.size()) var fremde := 0 for t in TechDB.LIST: if not Game.knows(t["id"]) and buch.contains(str(t["name"])): fremde += 1 p(fremde == 0, "Ungelernte Techniken stehen nicht im Journal (%d)" % fremde) ui().schliessen() func test_traumdorf() -> void: abschnitt("Traumwesen ziehen ein und leben ihren Tag") p(welt().bewohner.is_empty(), "Vorher wohnt niemand hier") for i in range(4): Game.completed.append("probe_%d" % i) Game.welt_erzeugen() main._traumwelt() await get_tree().process_frame if welt().bewohner.size() != i + 1: p(false, "Nach %d Träumen wohnen %d Wesen hier" % [i + 1, welt().bewohner.size()]) p(welt().bewohner.size() == 4, "Je erfülltem Traum zieht eines ein, hier sind es vier (%d)" % welt().bewohner.size()) var wesen: Array = welt().bewohner var haeuser := 0 for key in DreamDB.PLACES: if key.begins_with("haus_"): haeuser += 1 p(haeuser == 4, "Vier Häuser sind erzeugt (%d)" % haeuser) p(float(DreamDB.PLACES["hoersaal"]["size"][0]) > 0.0, "Dazu steht der Hörsaal") var eigene: Dictionary = {} for w in wesen: var haus: String = str(w.data["haus"]) p(DreamDB.PLACES.has(haus), "%s hat ein eigenes Haus (%s)" % [w.data["name"], haus]) eigene[haus] = true var kommt_vor := false for e in w.data["routine"]: if e[1] == haus: kommt_vor = true p(kommt_vor, "Die Routine von %s führt ins eigene Haus" % w.data["name"]) p(eigene.size() == 4, "Jedes Wesen hat ein anderes Haus") var luecken := 0 for w in wesen: for h in range(24): var r: Array = w._routine_at(float(h) + 0.5) if not DreamDB.PLACES.has(r[0]) or not Orte.ACTIVITY_TEXT.has(r[1]): luecken += 1 p(luecken == 0, "Alle vier haben rund um die Uhr Ort und Tätigkeit") # Ein voller Tag am Stück: niemand bleibt hängen Game.time_h = 0.0 Game.day = 1 var besucht: Dictionary = {} for w in wesen: besucht[w.data["id"]] = {} for i in range(9000): schritte(1) for w in wesen: if w._path.is_empty(): besucht[w.data["id"]][w.place] = true if Game.time_h >= 23.5: break var alle_ok := true for w in wesen: var soll: Dictionary = {} for e in w.data["routine"]: soll[e[1]] = true var ist: int = besucht[w.data["id"]].size() if ist < soll.size(): alle_ok = false p(false, "%s erreichte nur %d von %d Stationen" % [w.data["name"], ist, soll.size()]) p(alle_ok, "Über einen ganzen Tag erreicht jedes Wesen jede Station seiner Routine") var zuhause := 0 schritte(20, 0.05, 1.0) for w in wesen: var r: Array = w._routine_at(1.0) if w.place == r[0]: zuhause += 1 p(zuhause == 4, "Um 01:00 sind alle vier an ihrem Nachtplatz (%d)" % zuhause) # Ausgangslage wiederherstellen: das Dorf ist wieder leer Game.completed.clear() Game.welt_erzeugen() main._traumwelt() await get_tree().process_frame func test_haus_betreten() -> void: abschnitt("Orte betreten") p(not Game.ort_offen(DreamDB.haus_key(0)), "Ohne Bewohner steht kein Haus") Game.completed.append("probe_haus") Game.welt_erzeugen() main._traumwelt() await get_tree().process_frame var haus_name := str(DreamDB.PLACES[DreamDB.haus_key(0)]["name"]) p(_gebaute_orte().has(haus_name), "Mit dem ersten Bewohner steht sein Haus: %s" % haus_name) var haus := Orte.center(DreamDB.PLACES, DreamDB.haus_key(0)) var tuer := Orte.door(DreamDB.PLACES, DreamDB.haus_key(0)) var hinein: Vector3 = (haus - tuer).normalized() var yaw_hinein := atan2(-hinein.x, -hinein.z) var ziel: Vector3 = await laufe(tuer - hinein * 1.5 + Vector3(0, 1.0, 0), yaw_hinein, 80) var drin: bool = absf(ziel.x - haus.x) < 6.0 and absf(ziel.z - haus.z) < 5.0 p(drin, "Durch die Tür läuft man ins Haus (Ziel %.1f / %.1f, Haus %.1f / %.1f)" % [ziel.x, ziel.z, haus.x, haus.z]) # Die Wand hält: von der türabgewandten Seite kommt niemand hinein var rueck := haus + hinein * 11.0 + Vector3(0, 1.0, 0) var gestoppt: Vector3 = await laufe(rueck, atan2(hinein.x, hinein.z), 80) var drin2: bool = absf(gestoppt.x - haus.x) < 6.0 and absf(gestoppt.z - haus.z) < 5.0 p(not drin2, "Die Rückwand hält den Spieler auf (%.1f / %.1f)" % [gestoppt.x, gestoppt.z]) main.player.set_mode(Player.Mode.TRAUM) main.player.global_position = (welt() as DreamWorld).spawn Game.completed.clear() Game.welt_erzeugen() main._traumwelt() await get_tree().process_frame func test_hoersaal() -> void: abschnitt("Techniken werden im Hörsaal gelehrt") var pult := _marke("lehre") p(pult != null, "Der Hörsaal hat ein Lehrpult") var raum := Orte.center(DreamDB.PLACES, "hoersaal") p(absf(pult.global_position.x - raum.x) < 8.0 and absf(pult.global_position.z - raum.z) < 6.0, "Das Pult steht im Hörsaal (%.1f / %.1f)" % [pult.global_position.x, pult.global_position.z]) var im_saal: Vector3 = await laufe(Vector3(0, 1.0, 24.0), PI, 150) p(im_saal.z > 33.0, "Vom Eingang führt ein freier Mittelgang zum Pult (z = %.1f, Pult bei %.1f)" % [im_saal.z, raum.z + 3.4]) p(absf(im_saal.x) < 2.0, "Der Gang hält den Spieler in der Mitte (x = %.1f)" % im_saal.x) _hingehen(pult) p(main._fokus == pult, "Das Pult wird als Fokus erkannt") main._interagieren() p(ui().offen and ui()._kind == "lehre", "Der Hörsaal öffnet sich") var seite := _panel_text() var basis_da := true for id in _basiswege(): if not seite.contains(str(TechDB.by_id(id)["name"])): basis_da = false p(basis_da, "Die %d Basiswege werden gelehrt" % 1) var lernbar := 0 for c in ui()._content.get_children(): if c is Button and (c as Button).text.begins_with("Diesen Weg"): lernbar += 1 p(lernbar <= 1, "Und höchstens einer davon ist zu lernen (%d)" % lernbar) p(seite.contains(str(TechDB.LIST[1]["name"])), "Der Hörsaal nennt den nächsten noch unentdeckten Weg") p(not Game.learn("box_atmung"), "Eine unentdeckte Technik lässt sich nicht lernen") Game.known.clear() ui().lehre_panel() var vorher := Game.known.size() p(_druecke("Diesen Weg"), "Ein Lern-Knopf steht bereit") p(Game.known.size() == vorher + 1, "Im Hörsaal gelernt (%d -> %d)" % [vorher, Game.known.size()]) # Gelernt heißt fertig: der Lehrort bietet danach nichts mehr an ui().lehre_panel() var xp_summe := 0 for id in Game.known: xp_summe += int(Game.known[id]) p(not _knopf_da("Vertiefen") and not _knopf_da("Üben") and not _knopf_da("Trainieren"), "Kein Knopf trainiert eine gelernte Technik") p(not _druecke("Diesen Weg"), "Auch der Lern-Knopf ist verschwunden") var xp_neu := 0 for id in Game.known: xp_neu += int(Game.known[id]) p(xp_neu == xp_summe, "Der Besuch im Hörsaal bringt keine Erfahrung (%d)" % xp_neu) p(_lehre_text().contains("anwendest"), "Stattdessen verweist der Hörsaal aufs Anwenden") ui().schliessen() # Ausgangslage für die folgenden Prüfungen wiederherstellen Game.known = {"pomodoro": 0, "zerlegen": 0, "umgebung": 0} func test_mission_annehmen() -> void: abschnitt("Mission am Schwarzen Brett annehmen") var brett := _marke("brett") p(brett != null, "Das Schwarze Brett steht in der Traumwelt") var spawn: Vector3 = (welt() as DreamWorld).spawn p(absf(brett.global_position.x) > 2.5, "Es steht neben der Achse Start–Tor, versperrt sie also nicht (x = %.1f)" % brett.global_position.x) p(brett.global_position.distance_to(spawn) < 14.0, "Vom Start aus in Sichtweite (%.1f)" % brett.global_position.distance_to(spawn)) var hin: Vector3 = await laufe(Vector3(brett.global_position.x, 1.0, spawn.z), 0.0, 150) p(hin.z < 14.0, "Man läuft vom Start ungehindert bis davor (z = %.1f)" % hin.z) main._process(0.05) p(main._fokus == brett, "Davor steht man in Reichweite") ui().schliessen() main._interagieren() p(ui().offen and ui()._kind == "brett", "Das Brett öffnet sich") # Nur wer mit dem Gelernten zu schaffen ist, hängt aus. var alle_wege: Dictionary = Game.known.duplicate() Game.known = {str(_basiswege()[0]): 0} ui().brett_panel() var aushang := _panel_text() var gezeigt := 0 var fehlend := 0 for h in HumanDB.LIST: var id: String = h["id"] if aushang.contains(str(h["name"])): gezeigt += 1 if not Game.machbar(id): p(false, "%s hängt aus, obwohl kein Weg greift" % h["name"]) elif Game.machbar(id): p(false, "%s wäre lösbar, hängt aber nicht aus" % h["name"]) else: fehlend += 1 p(gezeigt > 0, "Mit einem Weg hängt mindestens ein Aushang (%d von %d)" % [gezeigt, HumanDB.LIST.size()]) p(fehlend > 0, "Und mindestens einer bleibt verdeckt (%d)" % fehlend) p(aushang.contains("verdeckt"), "Das Brett sagt, dass etwas verdeckt bleibt") p(Game.offene_missionen().size() == gezeigt, "Brett und offene_missionen() sind sich einig (%d)" % gezeigt) # Ohne jede Technik bleibt das Brett leer, mit Hinweis auf den Hörsaal Game.known.clear() ui().brett_panel() p(Game.offene_missionen().is_empty(), "Ohne Technik ist nichts zu holen") p(_panel_text().contains("Hörsaal"), "Und das Brett schickt in den Hörsaal") Game.known = alle_wege ui().brett_panel() p(Game.offene_missionen().size() >= gezeigt, "Mehr Wege heißt mehr Aushänge (%d)" % Game.offene_missionen().size()) p(Game.active_mission == "", "Vorher keine Mission") var gedrueckt := _druecke("Mission annehmen") p(gedrueckt, "Ein Aushang lässt sich annehmen") p(Game.active_mission == "mensch_0", "Mission Lena ist aktiv (%s)" % Game.active_mission) p(Game.angenommen("mensch_0"), "Lena steht als angenommen im Buch") ui().schliessen() ui().journal_panel("missionen") var buch := _panel_text() p(buch.contains(str(HumanDB.by_id("mensch_0")["name"])), "Das Journal führt die angenommene Mission") p(not buch.contains(str(HumanDB.by_id("mensch_1")["name"])), "Nicht angenommene Menschen fehlen im Journal") p(not _knopf_da("Mission annehmen"), "Im Journal lässt sich keine Mission annehmen") ui().schliessen() func test_reise_zur_erde() -> void: abschnitt("Reise zur Erde") var tor: Node3D = null for n in get_tree().get_nodes_in_group("interactable"): if n.get_meta("kind") == "erde": tor = n p(tor != null, "Tor zur Erde existiert") # Zu Fuß hin: die Torplattform darf keine unüberwindliche Stufe sein ui().schliessen() main.player.set_mode(Player.Mode.TRAUM) main.player.look_enabled = true main.player.global_position = Vector3(0, 1.0, -20.0) main.player.velocity = Vector3.ZERO main.player.yaw = 0.0 Input.action_press("vor") var erreicht := false var weit: float = main.player.global_position.z for i in range(200): await get_tree().physics_frame main._process(0.02) weit = main.player.global_position.z if main._fokus == tor: erreicht = true break Input.action_release("vor") p(erreicht, "Zu Fuß kommt man ans Tor — keine Stufe im Weg (z = %.1f, Tor bei %.1f)" % [weit, tor.global_position.z]) Game.teleport_lock = 1.0 main.player.global_position = tor.global_position + Vector3(0, 0, 1.5) main._process(0.05) p(main._fokus == tor, "Tor wird als Fokus erkannt") main._interagieren() await get_tree().process_frame p(main.auf_erde and welt() is EarthWorld, "Spieler ist auf der Erde") p(welt().humans.size() == HumanDB.LIST.size() and welt().humans.size() >= 3, "%d Menschen leben in der Stadt" % welt().humans.size()) func test_tagesablauf() -> void: abschnitt("Tagesablauf der Menschen") var mensch: Bewohner = null for m in welt().humans: if m.data["id"] == "mensch_0": mensch = m p(mensch != null, "Der erste Mensch lebt auf der Erde: %s" % mensch.data["name"]) schritte(30, 0.05, 2.0) p(mensch.activity == "schlaeft", "Um 02:00 schläft er (%s)" % mensch.activity) p(mensch.global_position.distance_to( Orte.center(HumanDB.PLACES, str(mensch.data["heim"]))) < 5.0, "Und zwar zu Hause") # Jede Station des eigenen Tagesablaufs wird pünktlich erreicht var verfehlt: Array = [] for e in mensch.data["routine"]: var stunde: float = float(e[0]) + 0.5 var da := false for i in range(1200): schritte(1, 0.05, stunde) if mensch.place == e[1] and mensch._path.is_empty(): da = true break if not da or mensch.activity != e[2]: verfehlt.append("%s um %d Uhr" % [e[1], int(e[0])]) p(verfehlt.is_empty(), "Alle %d Stationen seines Tages werden erreicht (%s)" % [(mensch.data["routine"] as Array).size(), "lückenlos" if verfehlt.is_empty() else ", ".join(verfehlt)]) var luecken := 0 for m in welt().humans: for h in range(24): var r: Array = m._routine_at(float(h) + 0.5) if not HumanDB.PLACES.has(r[0]) or not Orte.ACTIVITY_TEXT.has(r[1]): luecken += 1 p(luecken == 0, "Alle %d Menschen haben rund um die Uhr Ort und Tätigkeit" % welt().humans.size()) Game.time_h = 0.0 Game.day = 1 var besucht: Dictionary = {} for m in welt().humans: besucht[m.data["id"]] = {} for i in range(9000): schritte(1) for m in welt().humans: if m._path.is_empty(): besucht[m.data["id"]][m.place] = true if Game.time_h >= 23.5: break var alle_ok := true for m in welt().humans: var soll: Dictionary = {} for e in m.data["routine"]: soll[e[1]] = true if besucht[m.data["id"]].size() < soll.size(): alle_ok = false p(false, "%s erreichte nur %d von %d Stationen" % [m.data["name"], besucht[m.data["id"]].size(), soll.size()]) p(alle_ok, "Über einen ganzen Tag erreicht jeder Mensch jede Station seiner Routine") func test_wege_machbar() -> void: abschnitt("Wege passen in die Zeitfenster") _wege_pruefen("Erde", HumanDB.PLACES, HumanDB.LIST, HumanDB.NETZ, HumanDB.ANKER) _wege_pruefen("Traumwelt", DreamDB.PLACES, DreamDB.LIST, DreamDB.NETZ, DreamDB.ANKER) func _wege_pruefen(welt_name: String, places: Dictionary, liste: Array, netz: Netz = null, anker: Dictionary = {}) -> void: var sek_pro_stunde := Game.DAY_SECONDS / 24.0 var alles_ok := true var engste := 999.0 var engste_text := "" for m in liste: var r: Array = m["routine"] for i in range(r.size()): var von: String = String(r[i - 1][1]) if i > 0 else String(r[r.size() - 1][1]) var nach: String = String(r[i][1]) if von == nach: continue var dauer := _weg_laenge(places, von, nach, netz, anker) / Bewohner.GEHTEMPO var slot_h: float = (float(r[i + 1][0]) - float(r[i][0])) if i + 1 < r.size() \ else (24.0 - float(r[i][0])) var fenster := slot_h * sek_pro_stunde if dauer >= fenster: alles_ok = false p(false, "%s: %s -> %s braucht %.0f s, Fenster nur %.0f s" % [m["name"], von, nach, dauer, fenster]) elif fenster - dauer < engste: engste = fenster - dauer engste_text = ("%s: %s -> %s (%.0f s Weg, %.0f s Fenster)" % [m["name"], von, nach, dauer, fenster]) p(alles_ok, "%s — jeder Weg passt, engster Fall: %s" % [welt_name, engste_text]) func _weg_laenge(places: Dictionary, von: String, nach: String, netz: Netz = null, anker: Dictionary = {}) -> float: var pts: Array = Orte.route(places, von, nach, Vector3.ZERO, netz, anker) var l := 0.0 var pos := Orte.center(places, von) for pt in pts: l += pos.distance_to(pt) pos = pt return l func test_geist() -> void: abschnitt("Geist-Bewegung") p(main.player.mode == Player.Mode.GEIST, "Spieler ist auf der Erde ein Geist") p(main.player.collision_layer == 0 and main.player.collision_mask == 0, "Der Geist kollidiert mit nichts — er schwebt durch Wände") var vor: Vector3 = main.player.global_position main.player.global_position = Vector3(0, 8.0, 0) main.player.velocity = Vector3(6, 0, 0) for i in range(30): await get_tree().physics_frame p(main.player.global_position.x > 0.5, "Der Geist bewegt sich frei durch den Raum") p(absf(main.player.global_position.y - 8.0) < 0.5, "Ohne Schwerkraft fällt er nicht") var lena: Bewohner = welt().humans[0] var davor: Vector3 = lena.global_position main.player.global_position = lena.global_position + Vector3(0, 1, 0.5) schritte(10) p(lena.global_position.distance_to(davor) < 2.0, "Menschen nehmen den Geist nicht wahr") func test_technik_anwenden() -> void: abschnitt("Techniken anwenden — Erfolgschance") var lena: Bewohner = null for m in welt().humans: if m.data["id"] == "mensch_0": lena = m # Die Stunde suchen, in der dieser Mensch zögert — sie steht in seiner Routine var zoegert_um := -1.0 for e in lena.data["routine"]: if e[2] == "zoegert": zoegert_um = float(e[0]) + 0.5 for i in range(1200): schritte(1, 0.05, zoegert_um) if lena.activity == "zoegert" and lena._path.is_empty(): break p(lena.activity == "zoegert", "%s zögert um %.0f Uhr (%s)" % [lena.data["name"], zoegert_um, lena.activity]) main.player.set_mode(Player.Mode.GEIST) main.player.global_position = lena.global_position + Vector3(0, 1.0, 1.5) main._process(0.05) p(main._fokus_mensch == lena, "Der Geist erfasst Lena in Reichweite") main._interagieren() p(ui().offen and ui()._kind == "anwenden", "Anwenden-Panel öffnet sich") ui().schliessen() Game.known["pomodoro"] = 0 var s1: float = Game.evaluate("pomodoro", "mensch_0", "zoegert", false)["chance"] Game.known["pomodoro"] = 28 var s5: float = Game.evaluate("pomodoro", "mensch_0", "zoegert", false)["chance"] p(s5 > s1, "Höherer Skill = höhere Erfolgschance (%.0f%% -> %.0f%%)" % [s1 * 100, s5 * 100]) Game.known["pomodoro"] = 0 var passend: Dictionary = Game.evaluate("pomodoro", "mensch_0", "zoegert", false) var unpassend: Dictionary = Game.evaluate("pomodoro", "mensch_0", "essen", false) p(passend["moment"] and not unpassend["moment"], "Moment-Erkennung: zögert ja, isst nein") p(passend["chance"] > unpassend["chance"], "Passender Moment erhöht die Chance") p(passend["tag"], "Pomodoro trifft Lenas Thema Aufschieben") var falsches_thema: Dictionary = Game.evaluate("pomodoro", "mensch_2", "zoegert", false) p(not falsches_thema["tag"], "Bei Mira trifft Pomodoro das Thema nicht") p(passend["chance"] > falsches_thema["chance"], "Passendes Thema erhöht die Chance") p(Game.evaluate("pomodoro", "mensch_0", "schlaeft", false)["blocked"], "Schlafende Menschen sind nicht erreichbar") p(Game.evaluate("pomodoro", "mensch_0", "zoegert", true)["chance"] < passend["chance"], "Wer abblockt, ist schwerer zu erreichen") p(Game.evaluate("nein_sagen", "mensch_0", "zoegert", false)["blocked"], "Ungelernte Techniken lassen sich nicht anwenden") _frei() Game.progress["mensch_0"] = 50.0 var vorher_p: float = Game.mission_progress("mensch_0") var res := Game.apply_technique("pomodoro", "mensch_0", "zoegert", false) p(res["ok"], "Anwendung wird ausgeführt") if res["success"]: p(Game.mission_progress("mensch_0") > vorher_p, "Erfolg bringt Missionsfortschritt") else: p(Game.mission_progress("mensch_0") < vorher_p, "Fehlversuch kostet Fortschritt (%.0f -> %.0f)" % [vorher_p, Game.mission_progress("mensch_0")]) p(Game.known["pomodoro"] > 0, "Jede Anwendung bringt Erfahrung") _frei() Game.progress["mensch_0"] = 0.0 Game.used.clear() Game.known["pomodoro"] = 28 var ertraege: Array = [] for i in range(6): _frei() var r := Game.apply_technique("pomodoro", "mensch_0", "zoegert", false) if r["success"]: ertraege.append(r["gain"]) p(ertraege.size() >= 2 and ertraege[ertraege.size() - 1] < ertraege[0], "Dieselbe Technik immer wieder wirkt schwächer") func test_pipeline() -> void: abschnitt("Regelwerk — Missionsfortschritt automatisieren") Game.active_mission = "mensch_0" Game.progress["mensch_0"] = 0.0 Game.completed.clear() Game.used.clear() Game.pipeline.clear() Game.known = {"pomodoro": 28, "zerlegen": 28, "umgebung": 28} var taten := HumanDB.aktivitaeten("mensch_0") p(taten.size() >= 5, "Lenas Tag hat %d verschiedene Tätigkeiten" % taten.size()) p(taten.has("zoegert") and taten.has("schlaeft"), "Darunter zögert und schläft: %s" % ", ".join(taten)) # Regelwerk über das Journal aufbauen ui().journal_panel("missionen") p(_knopf_da("Regelwerk"), "Die angenommene Mission trägt ein Regelwerk") p(_druecke("Regelwerk"), "Das Regelwerk öffnet sich") p(ui()._kind == "pipeline", "Panel »pipeline« ist offen") var zeilen := _knopf_texte() var fehlt: Array = [] for a in taten: if not zeilen.contains(Orte.text(a)): fehlt.append(a) p(fehlt.is_empty(), "Jede der %d Tätigkeiten hat eine Zeile (fehlt: %s)" % [taten.size(), ", ".join(fehlt)]) p(zeilen.contains("schläft → unerreichbar"), "Schlaf ist als unerreichbar ausgewiesen, statt eine Regel anzubieten") var schlaf_aktiv := false for c in ui()._content.get_children(): if c is Button and (c as Button).text.begins_with("schläft") and not (c as Button).disabled: schlaf_aktiv = true p(not schlaf_aktiv, "Und lässt sich gar nicht erst anklicken") ui().regel_panel("mensch_0", "zoegert") p(ui()._kind == "regel", "Die Technikwahl für »zögert« öffnet sich") p(_knopf_texte().contains("Pomodoro-Technik"), "Gelernte Techniken stehen zur Wahl") p(_druecke("Pomodoro-Technik"), "Eine Technik lässt sich zuordnen") p(Game.regel_fuer("mensch_0", "zoegert") != "", "Regel steht: zögert -> %s" % Game.regel_fuer("mensch_0", "zoegert")) p(ui()._kind == "pipeline", "Danach ist wieder das Regelwerk offen") ui().schliessen() Game.regel_setzen("mensch_0", "gruebelt", "zerlegen") Game.regel_setzen("mensch_0", "aufraeumen", "umgebung") p(Game.regel_anzahl("mensch_0") == 3, "Drei Regeln im Werk (%d)" % Game.regel_anzahl("mensch_0")) # Eine Technik darf an mehreren Tätigkeiten hängen Game.regel_setzen("mensch_0", "arbeitet", "zerlegen") p(Game.regel_anzahl("mensch_0") == 4, "Dieselbe Technik anderswo einzusetzen kommt hinzu (%d)" % Game.regel_anzahl("mensch_0")) p(Game.regel_fuer("mensch_0", "gruebelt") == "zerlegen" and Game.regel_fuer("mensch_0", "arbeitet") == "zerlegen", "Sie hängt jetzt an beiden Tätigkeiten") p(Game.regel_momente("mensch_0", "zerlegen").size() == 2, "Und das Spiel weiß von beiden Posten (%d)" % Game.regel_momente("mensch_0", "zerlegen").size()) Game.regel_setzen("mensch_0", "arbeitet", "") p(Game.regel_momente("mensch_0", "zerlegen").size() == 1, "Einzeln löschen lässt den anderen Posten stehen") # Ohne Zutun einen Tag laufen lassen var lena: Bewohner = null for m in welt().humans: if m.data["id"] == "mensch_0": lena = m p(lena != null, "Lena lebt in der Stadt") var vorher: float = Game.mission_progress("mensch_0") var vor_xp: int = int(Game.known["pomodoro"]) + int(Game.known["zerlegen"]) Game.time_h = 0.0 main.player.global_position = Vector3(0, 30.0, 60.0) # weit weg, kein Zutun for i in range(9000): schritte(1) if Game.time_h >= 23.5: break var nachher: float = Game.mission_progress("mensch_0") p(nachher > vorher, "Der Traum wächst von allein: %d %% -> %d %%" % [int(vorher), int(nachher)]) p(int(Game.known["pomodoro"]) + int(Game.known["zerlegen"]) > vor_xp, "Die Regeln sammeln nebenbei Erfahrung") # Ohne Regeln passiert nichts Game.pipeline.clear() Game.progress["mensch_0"] = 0.0 var still: float = Game.mission_progress("mensch_0") Game.time_h = 0.0 for i in range(9000): schritte(1) if Game.time_h >= 23.5: break p(is_equal_approx(Game.mission_progress("mensch_0"), still), "Ohne Regeln bewegt sich nichts (%d %%)" % int(Game.mission_progress("mensch_0"))) # Regeln greifen nur für die aktive Mission Game.regel_setzen("mensch_1", "zoegert", "zerlegen") Game.progress["mensch_1"] = 0.0 Game.time_h = 0.0 for i in range(9000): schritte(1) if Game.time_h >= 23.5: break p(is_equal_approx(Game.mission_progress("mensch_1"), 0.0), "Regeln fremder Missionen bleiben still (%d %%)" % int(Game.mission_progress("mensch_1"))) # Ausgangslage für die folgenden Prüfungen Game.pipeline.clear() Game.progress["mensch_0"] = 0.0 Game.used.clear() Game.known = {"pomodoro": 0, "zerlegen": 0, "umgebung": 0} func test_mission_abschliessen() -> void: abschnitt("Mission abschließen") Game.progress["mensch_0"] = 0.0 Game.used.clear() var runden := 0 while Game.mission_progress("mensch_0") < Game.MISSION_ZIEL and runden < 400: _frei() Game.apply_technique("pomodoro", "mensch_0", "zoegert", false) _frei() Game.apply_technique("zerlegen", "mensch_0", "gruebelt", false) _frei() Game.apply_technique("umgebung", "mensch_0", "aufraeumen", false) runden += 1 p(Game.mission_progress("mensch_0") >= Game.MISSION_ZIEL, "Lenas Traum erreicht 100 %% (nach %d Runden)" % runden) p(Game.completed.has("mensch_0"), "Mission gilt als abgeschlossen") p(Game.active_mission == "", "Aktive Mission wurde freigegeben") p(DreamDB.LIST.size() == Game.completed.size(), "Der Abschluss lässt das Dorf wachsen (%d Wesen)" % DreamDB.LIST.size()) schritte(3) var lena: Bewohner = welt().humans[0] p(lena.beschriftung().contains("erfüllt"), "Lena zeigt sichtbar den erfüllten Traum") func test_welt_waechst() -> void: abschnitt("Erfüllte Träume lassen das Dorf wachsen") var zugeordnet: Dictionary = {} var doppelt: Array = [] for i in range(TechDB.LIST.size()): var ort := DreamDB.ort_von_technik(str(TechDB.LIST[i]["id"])) if zugeordnet.has(ort): doppelt.append(ort) zugeordnet[ort] = TechDB.LIST[i]["id"] p(zugeordnet.size() == TechDB.LIST.size() and doppelt.is_empty(), "Jede der %d Techniken hat einen eigenen Lehrort (%d Orte, %d doppelt)" % [TechDB.LIST.size(), zugeordnet.size(), doppelt.size()]) p(DreamDB.ort_von_technik(str(TechDB.LIST[0]["id"])) == "hoersaal", "Den ersten Weg lehrt der Hörsaal") p(DreamDB.ort_von_technik(str(TechDB.LIST[1]["id"])) == DreamDB.haus_key(0), "Den zweiten das erste Haus") p(Game.completed.size() == 1, "Ein Traum ist erfüllt (%d)" % Game.completed.size()) main._traumwelt() await get_tree().process_frame var lehrorte: Dictionary = {} for n in get_tree().get_nodes_in_group("interactable"): if n.get_meta("kind") == "lehre": lehrorte[n.get_meta("arg")] = n p(lehrorte.has("hoersaal") and lehrorte.has(DreamDB.haus_key(0)), "Hörsaal und das erste Haus lehren (%s)" % ", ".join(lehrorte.keys())) p(not lehrorte.has(DreamDB.haus_key(1)), "Ein zweites Haus gibt es noch nicht") p(Game.insel_radius() >= Game.INSEL_GRUND, "Die Insel trägt das Dorf (%.0f)" % Game.insel_radius()) var neu_id := str(TechDB.LIST[1]["id"]) ui().lehre_panel(DreamDB.haus_key(0)) p(ui()._kind == "lehre", "Das neue Haus lehrt") p(_panel_text().contains(str(TechDB.by_id(neu_id)["name"])), "Und zwar den Weg, den sein Bewohner mitbrachte") p(_druecke("Diesen Weg"), "Er lässt sich lernen") p(Game.knows(neu_id), "Gelernt: %s" % TechDB.by_id(neu_id)["name"]) ui().schliessen() p(not Game.technik_offen(str(TechDB.LIST[2]["id"])), "%s bleibt unentdeckt" % TechDB.by_id(str(TechDB.LIST[2]["id"]))["name"]) p(Game.offene_techniken() == 2, "Entdeckt sind zwei Wege (%d)" % Game.offene_techniken()) await _dorf_platz_pruefen() # Ausgangslage für die folgenden Prüfungen Game.known.erase(neu_id) Game.welt_erzeugen() main._erde() await get_tree().process_frame ## Das Dorf wird erzeugt — also muss über viele Ausbaustufen gelten: ## nichts überlappt, nichts ragt über den Rand, jeder Weg passt ins Zeitfenster. func _dorf_platz_pruefen() -> void: var merker: Array = Game.completed.duplicate() var stufen := 24 var engster := 999.0 var engster_text := "" var ueberlappung := "" var rand_fehler := "" for stufe in range(stufen + 1): Game.completed.clear() for i in range(stufe): Game.completed.append("stufe_%d" % i) Game.welt_erzeugen() var radius := Game.insel_radius() var keys: Array = [] for key in DreamDB.PLACES: if float(DreamDB.PLACES[key]["size"][0]) > 0.0: keys.append(key) for a in range(keys.size()): var ka: String = keys[a] var ca := Orte.center(DreamDB.PLACES, ka) var sa: Array = DreamDB.PLACES[ka]["size"] var ecke: float = Vector3(absf(ca.x) + float(sa[0]) * 0.5, 0, absf(ca.z) + float(sa[1]) * 0.5).length() if radius - ecke < engster: engster = radius - ecke engster_text = ("%s bei %d Häusern (%.1f von %.0f)" % [DreamDB.PLACES[ka]["name"], stufe, ecke, radius]) if ecke >= radius and rand_fehler == "": rand_fehler = "%s ragt bei %d Häusern über den Rand (%.1f >= %.0f)" % \ [DreamDB.PLACES[ka]["name"], stufe, ecke, radius] for b in range(a + 1, keys.size()): var kb: String = keys[b] var cb := Orte.center(DreamDB.PLACES, kb) var sb: Array = DreamDB.PLACES[kb]["size"] var dx: float = absf(ca.x - cb.x) - (float(sa[0]) + float(sb[0])) * 0.5 var dz: float = absf(ca.z - cb.z) - (float(sa[1]) + float(sb[1])) * 0.5 if dx < 2.0 and dz < 2.0 and ueberlappung == "": ueberlappung = "%s und %s stehen ineinander (%.1f / %.1f)" % \ [DreamDB.PLACES[ka]["name"], DreamDB.PLACES[kb]["name"], dx, dz] p(ueberlappung == "", "Über %d Ausbaustufen berührt sich kein Gebäude (%s)" % [stufen, "keins" if ueberlappung == "" else ueberlappung]) p(rand_fehler == "" and engster >= 4.0, "Und keins ragt über den Rand — enger Fall: %s%s" % [engster_text, "" if rand_fehler == "" else " — " + rand_fehler]) Game.completed.clear() for i in range(stufen): Game.completed.append("stufe_%d" % i) Game.welt_erzeugen() _wege_pruefen("Traumdorf mit %d Häusern" % stufen, DreamDB.PLACES, DreamDB.LIST, DreamDB.NETZ, DreamDB.ANKER) _wege_pruefen("Stadt mit %d Menschen" % HumanDB.LIST.size(), HumanDB.PLACES, HumanDB.LIST, HumanDB.NETZ, HumanDB.ANKER) Game.completed.assign(merker) Game.welt_erzeugen() func test_techniken_verbessern_sich() -> void: abschnitt("Techniken verbessern sich nur durch Anwendung") p(Game.learn("zwei_minuten"), "Technik gelernt") p(Game.skill_of("zwei_minuten") == 1, "Frisch gelernt heißt Stufe 1") var anwendungen := 0 while Game.skill_of("zwei_minuten") < 3 and anwendungen < 60: _frei() Game.apply_technique("zwei_minuten", "mensch_2", "gruebelt", false) anwendungen += 1 p(Game.skill_of("zwei_minuten") >= 3, "Nach %d Anwendungen von allein auf Stufe %d" % [anwendungen, Game.skill_of("zwei_minuten")]) var vor_xp: int = Game.known["zwei_minuten"] _frei() var res := Game.apply_technique("zwei_minuten", "mensch_2", "essen", false) p(Game.known["zwei_minuten"] > vor_xp, "Auch ein %s bringt Erfahrung" % ("Erfolg" if res["success"] else "Fehlversuch")) # Es gibt keinen zweiten Weg nach oben p(not Game.has_method("deepen"), "Kein gezieltes Vertiefen mehr im Spiel") p(not ("einsicht" in Game), "Und keine Währung, die man dafür ausgeben könnte") var stand: int = Game.known["zwei_minuten"] Game.learn("zwei_minuten") ui().lehre_panel(DreamDB.ort_von_technik("zwei_minuten")) var geknoepft := _druecke("Diesen Weg") or _druecke("Vertiefen") or _druecke("Üben") ui().schliessen() p(not geknoepft and Game.known["zwei_minuten"] == stand, "Ein zweiter Besuch am Lehrort ändert nichts (%d EP)" % stand) # Zeitablauf allein bringt auch nichts var vorher: int = Game.known["zwei_minuten"] Game.advance_time(Game.DAY_SECONDS) p(Game.known["zwei_minuten"] == vorher, "Und auch ein ganzer Tag bringt keine Erfahrung") func test_journal_ueberall() -> void: abschnitt("Journal ist immer dabei") p(InputMap.has_action("journal"), "Eigene Aktion für das Journal") var tasten: Array = [] for ev in InputMap.action_get_events("journal"): tasten.append(ev.physical_keycode) p(tasten.has(KEY_J) and tasten.has(KEY_TAB), "Auf J und Tab gelegt") p(main.auf_erde, "Test läuft auf der Erde") ui().journal_panel() p(ui().offen, "Journal öffnet auf der Erde") ui().schliessen() main._traumwelt() await get_tree().process_frame ui().journal_panel() p(ui().offen, "Journal öffnet auch in der Traumwelt") ui().schliessen() # Traumwesen sind keine Missionsziele var wesen: Bewohner = welt().bewohner[0] main.player.set_mode(Player.Mode.TRAUM) main.player.global_position = wesen.global_position + Vector3(0, 1.0, 1.2) main._process(0.05) p(main._fokus_mensch == null, "Traumwesen lassen sich nicht mit Techniken bearbeiten") p(not wesen.mit_mission, "Sie tragen keine Missionsmarkierung") p(wesen.beschriftung().contains(str(wesen.data["rolle"])), "Ihre Beschriftung nennt Name und Rolle: %s" % wesen.beschriftung().replace("\n", " / ")) func test_steuerung() -> void: abschnitt("Steuerung") var aktionen := ["vor", "zurueck", "links", "rechts", "steigen", "sinken", "interagieren", "journal", "tempo_1", "tempo_2", "tempo_3"] var alle := true for a in aktionen: if not InputMap.has_action(a) or InputMap.action_get_events(a).is_empty(): alle = false p(false, "Aktion '%s' ist belegt" % a) p(alle, "Alle %d Steuerungsaktionen sind mit Tasten belegt" % aktionen.size()) ui().schliessen() main.player.look_enabled = true main.player.set_mode(Player.Mode.GEIST) main.player.global_position = Vector3(0, 8.0, 0) main.player.velocity = Vector3.ZERO main.player.yaw = 0.0 var start: Vector3 = main.player.global_position Input.action_press("vor") for i in range(40): await get_tree().physics_frame Input.action_release("vor") p(main.player.global_position.z < start.z - 1.0, "W schiebt den Spieler nach vorn (dz = %.1f)" % (main.player.global_position.z - start.z)) main.player.global_position = Vector3(0, 8.0, 0) main.player.velocity = Vector3.ZERO Input.action_press("rechts") for i in range(40): await get_tree().physics_frame Input.action_release("rechts") p(main.player.global_position.x > 1.0, "D schiebt den Spieler nach rechts (x = %.1f)" % main.player.global_position.x) main.player.global_position = Vector3(0, 8.0, 0) main.player.velocity = Vector3.ZERO Input.action_press("steigen") for i in range(40): await get_tree().physics_frame Input.action_release("steigen") p(main.player.global_position.y > 9.0, "Leertaste lässt den Geist steigen (y = %.1f)" % main.player.global_position.y) main.player.velocity = Vector3.ZERO var hoch: float = main.player.global_position.y Input.action_press("sinken") for i in range(40): await get_tree().physics_frame Input.action_release("sinken") p(main.player.global_position.y < hoch - 1.0, "Umschalt lässt den Geist sinken (y = %.1f)" % main.player.global_position.y) ## Die Probe auf Loop 2: ohne einen erfüllten Traum gibt es keine neuen Wege. ## Also muss der erste Traum mit dem Startkapital allein zu schaffen sein — ## und zwar zuverlässig, nicht nur bei gutem Würfelglück. func _erster_traum_tage(saat: int) -> int: seed(saat) Game.cooldown.clear() Game.known.clear() Game.used.clear() Game.pipeline.clear() Game.progress.clear() Game.completed.clear() Game.active_mission = "" Game.welt_erzeugen() for id in _basiswege(): Game.learn(id) Game.accept_mission("mensch_0") # So ordnet ein Spieler zu: jede Technik an den Moment, wo sie am besten wirkt. for id in Game.known: var bester := "" var beste_chance := 0.0 for activity in HumanDB.aktivitaeten("mensch_0"): if not Game.erreichbar(activity) or Game.regel_fuer("mensch_0", activity) != "": continue var ev := Game.evaluate(id, "mensch_0", activity, false) if float(ev["chance"]) > beste_chance: beste_chance = float(ev["chance"]) bester = activity if bester != "": Game.regel_setzen("mensch_0", bester, id) main._erde() await get_tree().process_frame main.player.global_position = Vector3(0, 30.0, 60.0) # der Spieler tut nichts Game.time_h = 7.0 Game.day = 1 for i in range(140000): schritte(1) if Game.completed.has("mensch_0") or Game.day > 14: break return Game.day func test_erster_traum_schaffbar() -> void: abschnitt("Der erste Traum ist mit dem Startkapital zu schaffen") Game.completed.clear() Game.welt_erzeugen() p(Game.offene_techniken() == 1, "Am Anfang ist genau ein Weg entdeckt (%d)" % Game.offene_techniken()) var tage: Array = [] for saat in [1, 7, 555, 20240, 90210]: tage.append(await _erster_traum_tage(saat)) p(Game.known.size() == 1, "Man startet mit einem Weg (%d)" % Game.known.size()) p(Game.regel_anzahl("mensch_0") == 1, "Der trägt genau eine Regel (%d)" % Game.regel_anzahl("mensch_0")) var schlimmster: int = tage.max() var bester: int = tage.min() p(schlimmster <= 14, "Der Traum erfüllt sich in allen %d Läufen ohne Zutun (%s)" % [tage.size(), str(tage)]) p(bester >= 2, "Nie geschenkt — mindestens %d Tage" % bester) p(schlimmster <= 8, "Nie zäh — höchstens %d Tage (von Hand geht es schneller)" % schlimmster) p(Game.offene_techniken() > 1, "Und öffnet neue Wege: %d entdeckt" % Game.offene_techniken()) ## Nach jedem Einsatz braucht eine Technik Ruhe. Ein Weg allein deckt daher ## keinen ganzen Tag ab — das ist der Grund, mehrere zu lernen. func test_cooldown() -> void: abschnitt("Techniken haben einen Cooldown") _frei() Game.known = {"pomodoro": 28, "zerlegen": 28} Game.active_mission = "mensch_0" Game.progress["mensch_0"] = 50.0 p(Game.bereit("pomodoro"), "Frisch ist eine Technik einsatzbereit") var erste := Game.apply_technique("pomodoro", "mensch_0", "zoegert", false) p(erste["ok"], "Der erste Einsatz geht durch") p(not Game.bereit("pomodoro"), "Danach ist sie gesperrt") p(is_equal_approx(Game.cooldown_rest("pomodoro"), Game.TECHNIK_COOLDOWN), "Und zwar %.0f Sekunden lang" % Game.TECHNIK_COOLDOWN) var zweite := Game.apply_technique("pomodoro", "mensch_0", "zoegert", false) p(not zweite["ok"], "Ein zweiter Einsatz prallt an der Sperre ab") p(str(zweite["grund"]).contains("einsatzbereit"), "Mit klarer Begründung") p(Game.bereit("zerlegen"), "Die Sperre gilt nur für diese eine Technik") var andere := Game.apply_technique("zerlegen", "mensch_0", "gruebelt", false) p(andere["ok"], "Eine andere Technik greift weiter") # Eine Regel bleibt still, solange die Technik gesperrt ist Game.pipeline.clear() Game.regel_setzen("mensch_0", "zoegert", "pomodoro") var still := Game.regel_ausloesen("mensch_0", "zoegert", false) p(still.is_empty(), "Auch eine Regel läuft nicht gegen die Sperre an") Game.advance_time(Game.TECHNIK_COOLDOWN * 0.5) p(not Game.bereit("pomodoro"), "Nach der halben Zeit noch immer gesperrt") Game.advance_time(Game.TECHNIK_COOLDOWN * 0.6) p(Game.bereit("pomodoro"), "Nach Ablauf wieder frei") p(Game.cooldown_rest("pomodoro") == 0.0, "Und der Zähler steht auf null") var wieder := Game.regel_ausloesen("mensch_0", "zoegert", false) p(not wieder.is_empty(), "Jetzt greift die Regel wieder") # Im Anwenden-Panel ist die Sperre sichtbar und der Knopf tot var lena: Bewohner = null for m in welt().humans: if m.data["id"] == "mensch_0": lena = m ui().anwenden_panel(lena) var gesperrt_sichtbar := _knopf_texte().contains("gesperrt") var knopf_tot := true for c in ui()._content.get_children(): if c is Button and (c as Button).text.contains("gesperrt") \ and not (c as Button).disabled: knopf_tot = false p(gesperrt_sichtbar, "Der Anwenden-Knopf weist die Sperre aus") p(knopf_tot, "Und lässt sich nicht drücken") ui().schliessen() Game.pipeline.clear() _frei() ## Wie gut eine Technik im Moment steht, sagt das Spiel nicht. Es nennt nur, ## was zum Thema und was zum Moment passt — entscheiden muss der Spieler. func test_chance_verdeckt() -> void: abschnitt("Die Erfolgschance bleibt verdeckt") _frei() Game.known = {"pomodoro": 28, "zerlegen": 28} Game.active_mission = "mensch_0" Game.pipeline.clear() Game.regel_setzen("mensch_0", "zoegert", "pomodoro") ui().regel_panel("mensch_0", "zoegert") var knoepfe := _knopf_texte() p(not knoepfe.contains("%"), "Kein Knopf der Technikwahl nennt eine Prozentzahl") p(not _panel_text().contains("Erfolgschance"), "Und das Wort Erfolgschance kommt nicht vor") var wahl := _panel_text() p(not wahl.contains("passt zum Thema") and not wahl.contains("passender Moment") and not wahl.contains("weder Thema"), "Auch kein Hinweis, ob Thema oder Moment passen") p(not wahl.contains("Wann:") and not wahl.contains("Zu sehen an:"), "Die Auswahl zeigt nur die Namen, keine Beschreibung") p(_knopf_texte().contains("Pomodoro-Technik · Stufe"), "Jede Wahl steht als Name mit Stufe da") ui().schliessen() ui().pipeline_panel("mensch_0") var zeilen := _knopf_texte() p(not zeilen.contains("%"), "Auch die Regelzeilen nennen keine Chance") p(zeilen.contains("Pomodoro-Technik"), "Sie nennen nur die eingehängte Technik") ui().schliessen() var lena: Bewohner = null for m in welt().humans: if m.data["id"] == "mensch_0": lena = m ui().anwenden_panel(lena) var anwenden := _panel_text() p(not _knopf_texte().contains("%"), "Und der Anwenden-Knopf nennt keine Chance") p(_knopf_texte().contains("Pomodoro-Technik · Stufe"), "Er trägt nur Name und Stufe") p(not anwenden.contains("passt zum Thema") and not anwenden.contains("passender Moment"), "Auch beim Anwenden steht nicht da, ob es passt") p(not anwenden.contains("Hilft bei") and not anwenden.contains("Momente:"), "Und keine Liste der passenden Themen oder Momente") p(not anwenden.contains("Wann:") and not anwenden.contains("Zu sehen an:"), "Auch dort nur Namen — die Beschreibung steht im Journal") p(anwenden.contains("Journal"), "Und das Panel sagt, wo sie steht") ui().schliessen() ui().journal_panel("techniken") var buch := _panel_text() p(not buch.contains("Hilft bei") and not buch.contains("Momente:"), "Auch das Journal verrät die Passung nicht") p(buch.contains("Wann:") and buch.contains("Zu sehen an:"), "Das Journal dagegen führt Was, Wann, Warum und Zu sehen an") ui().schliessen() ui().brett_panel() p(not _panel_text().contains("Themen:"), "Und das Brett listet die Themen des Menschen nicht auf") ui().schliessen() Game.pipeline.clear() ## Wenn nur lösbare Missionen aushängen, darf das Brett nie ganz leer werden — ## sonst hinge Loop 2 fest: ohne Traum kein neuer Weg, ohne Weg kein Aushang. func test_brett_nie_leer() -> void: abschnitt("Das Brett läuft nie leer") var merk_c: Array = Game.completed.duplicate() var merk_k: Dictionary = Game.known.duplicate() var leer_bei := -1 var kleinste := 99 var stufen := 14 for stufe in range(stufen): # Ein Spieler, der alles lernt, was das Dorf hergibt for t in TechDB.LIST: if Game.technik_offen(str(t["id"])) and not Game.knows(str(t["id"])): Game.known[str(t["id"])] = 0 var offen: Array = Game.offene_missionen() kleinste = mini(kleinste, offen.size()) if offen.is_empty(): leer_bei = stufe break Game.completed.append(str(offen[0])) Game.welt_erzeugen() p(leer_bei < 0, "Über %d Ausbaustufen hängt immer etwas aus%s" % [stufen, "" if leer_bei < 0 else " — leer bei Stufe %d" % leer_bei]) p(kleinste >= 1, "Im knappsten Fall bleibt %d Aushang übrig" % kleinste) # Und der jeweils neueste Mensch ist mit dem neuesten Weg zu schaffen Game.completed.clear() Game.known.clear() Game.welt_erzeugen() var passt := 0 for i in range(TechDB.LIST.size()): Game.completed.clear() for k in range(i): Game.completed.append("stufe_%d" % k) Game.welt_erzeugen() Game.known = {str(TechDB.LIST[i]["id"]): 0} if Game.machbar("mensch_%d" % i): passt += 1 p(passt == TechDB.LIST.size(), "Zu jedem der %d Wege gibt es den passenden Menschen (%d)" % [TechDB.LIST.size(), passt]) Game.completed.assign(merk_c) Game.known = merk_k Game.welt_erzeugen() ## Die Probe auf den Kern: wer die Technik liest und sie nur dort einhängt, wo ## ihr "Wann" trifft, kommt schneller ans Ziel als wer sie überall hinhängt. ## Ohne diesen Abstand wäre das Lesen umsonst. func _traum_tage(saat: int, alles: bool) -> int: seed(saat) Game.cooldown.clear() Game.known.clear() Game.used.clear() Game.pipeline.clear() Game.progress.clear() Game.completed.clear() Game.active_mission = "" Game.welt_erzeugen() var weg := str(_basiswege()[0]) Game.learn(weg) Game.accept_mission("mensch_0") var t := TechDB.by_id(weg) for a in HumanDB.aktivitaeten("mensch_0"): if not Game.erreichbar(a): continue if alles or (t["momente"] as Array).has(a): Game.regel_setzen("mensch_0", a, weg) main._erde() await get_tree().process_frame main.player.global_position = Vector3(0, 30.0, 60.0) Game.time_h = 7.0 Game.day = 1 for i in range(200000): schritte(1) if Game.completed.has("mensch_0") or Game.day > 14: break return Game.day if Game.completed.has("mensch_0") else 99 func test_lesen_lohnt_sich() -> void: abschnitt("Die Technik zu lesen zahlt sich aus") var gezielt: Array = [] var blind: Array = [] for saat in [7, 555, 90210]: gezielt.append(await _traum_tage(saat, false)) blind.append(await _traum_tage(saat, true)) var g := 0.0 var b := 0.0 for i in range(gezielt.size()): g += float(gezielt[i]) b += float(blind[i]) g /= float(gezielt.size()) b /= float(blind.size()) p(b >= g * 1.25, "Gezielt gesetzt geht es deutlich schneller: %.1f statt %.1f Tage (gezielt %s, blind %s)" % [g, b, str(gezielt), str(blind)]) var schlechter := 0 for i in range(gezielt.size()): if int(blind[i]) < int(gezielt[i]): schlechter += 1 p(schlechter == 0, "Und blind gesetzt ist in keinem der %d Läufe besser (%d)" % [gezielt.size(), schlechter]) # Der Grund: ein falscher Moment ist unterm Strich ein Verlustgeschäft Game.known = {"pomodoro": 0} var gut := Game.evaluate("pomodoro", "mensch_0", "zoegert", false) var falsch := Game.evaluate("pomodoro", "mensch_0", "gruebelt", false) p(gut["moment"] and not falsch["moment"], "»zögert« ist Pomodoros Moment, »grübelt« nicht") var ertrag_gut := (8.0 + 2.0) + 5.0 + 5.0 var ertrag_falsch := (8.0 + 2.0) + 5.0 var ev_gut: float = float(gut["chance"]) * ertrag_gut \ - (1.0 - float(gut["chance"])) * Game.FEHLVERSUCH_VERLUST var ev_falsch: float = float(falsch["chance"]) * ertrag_falsch \ - (1.0 - float(falsch["chance"])) * Game.FEHLVERSUCH_VERLUST p(ev_gut > 0.0, "Im richtigen Moment trägt sie (%.1f je Einsatz)" % ev_gut) p(ev_falsch < 0.0, "Im falschen kostet sie (%.1f je Einsatz)" % ev_falsch) ## Warten soll nicht wehtun: 1, 2, 3 stellen das Tempo. Über der Nacht des ## Menschen springt es von allein auf drei — dort ist ohnehin nichts zu holen. func test_tempo() -> void: abschnitt("Tempo lässt sich stellen") Game.completed.clear() Game.known = {"pomodoro": 0} Game.welt_erzeugen() Game.active_mission = "" Game.tempo = 1 p(is_equal_approx(Game.zeit_faktor(), 1.0), "Ohne Zutun läuft die Welt einfach") # Wie in Die Sims: die Stufe ist nicht der Faktor var erwartet := {1: 1.0, 2: 3.0, 3: 7.0} for stufe in [2, 3, 1]: Game.tempo_setzen(stufe) p(Game.tempo == stufe and is_equal_approx(Game.zeit_faktor(), float(erwartet[stufe])), "Stufe %d läuft %.0f-fach" % [stufe, Game.zeit_faktor()]) Game.tempo_setzen(3) p(not is_equal_approx(Game.zeit_faktor(), 3.0), "Stufe 3 heißt gerade nicht dreifach (%.0f×)" % Game.zeit_faktor()) Game.tempo_setzen(1) Game.tempo_setzen(9) p(Game.tempo == 3, "Höher als drei geht nicht (%d)" % Game.tempo) Game.tempo_setzen(0) p(Game.tempo == 1, "Und tiefer als eins auch nicht (%d)" % Game.tempo) # Über der Nacht des Menschen springt es von allein Game.accept_mission("mensch_0") var wach := -1.0 var nacht := -1.0 for stunde in range(24): Game.time_h = float(stunde) + 0.5 if Game.schlaeft_ziel(): nacht = Game.zeit_faktor() else: wach = Game.zeit_faktor() p(nacht > 0.0 and wach > 0.0, "Lenas Tag hat wache und schlafende Stunden") p(is_equal_approx(nacht, Game.faktor_von(3)), "Im Schlaf läuft die Welt auf der höchsten Stufe (%.0f×)" % nacht) p(is_equal_approx(wach, 1.0), "Wach wieder auf der gewählten Stufe (%.0f)" % wach) Game.tempo_setzen(1) Game.time_h = 2.0 p(Game.schlaeft_ziel(), "Um 02:00 schläft sie") p(is_equal_approx(Game.zeit_faktor(), Game.faktor_von(3)), "Die eigene Wahl Stufe 1 gilt dann nicht — die Nacht springt") # Ohne Mission gibt es nichts zu überspringen Game.active_mission = "" p(not Game.schlaeft_ziel(), "Ohne Mission kein Nachtsprung") p(is_equal_approx(Game.zeit_faktor(), 1.0), "Dann zählt nur die Wahl") # Der Faktor beschleunigt die Uhr wirklich Game.tempo_setzen(3) Game.time_h = 8.0 Game.day = 1 var vorher := Game.time_h Game.advance_time(1.0 * Game.zeit_faktor()) var schnell := Game.time_h - vorher Game.tempo_setzen(1) Game.time_h = 8.0 vorher = Game.time_h Game.advance_time(1.0 * Game.zeit_faktor()) var langsam := Game.time_h - vorher p(is_equal_approx(schnell, langsam * Game.faktor_von(3)), "Auf Stufe 3 vergeht %.0f-mal so viel Zeit je Sekunde (%.4f statt %.4f h)" % [Game.faktor_von(3), schnell, langsam]) # Das HUD sagt, woran man ist Game.tempo_setzen(2) ui().journal_panel() ui().schliessen() ui()._hud_aktualisieren() p(ui()._hud.text.contains("Tempo 2 (3×)"), "Das HUD nennt Stufe und Faktor getrennt") Game.accept_mission("mensch_0") Game.time_h = 2.0 ui()._hud_aktualisieren() p(ui()._hud.text.contains("Nachtsprung"), "Und weist den Nachtsprung aus") Game.tempo_setzen(1) Game.active_mission = "" ## Das Dorf wird nicht gesetzt, sondern wächst. Also muss über viele ## Ausbaustufen gelten: nichts überlappt, kein Weg führt durch ein Haus, alles ## hängt zusammen, und was einmal steht, bleibt stehen. func test_dorf_organisch() -> void: abschnitt("Das Dorf wächst organisch") var merker: Array = Game.completed.duplicate() var stufen := 24 var ueberlappung := "" var wegtreffer := "" var lose := "" var ohne_anker := "" var unerreichbar := "" var sektoren_12 := 0 var dichte := "" for stufe in range(stufen + 1): Game.completed.clear() for i in range(stufe): Game.completed.append("stufe_%d" % i) Game.welt_erzeugen() var netz: Netz = DreamDB.NETZ var keys: Array = [] for key in DreamDB.PLACES: if float(DreamDB.PLACES[key]["size"][0]) > 0.0: keys.append(key) # nichts steht ineinander for a in range(keys.size()): for b in range(a + 1, keys.size()): var ca := Orte.center(DreamDB.PLACES, keys[a]) var cb := Orte.center(DreamDB.PLACES, keys[b]) var sa: Array = DreamDB.PLACES[keys[a]]["size"] var sb: Array = DreamDB.PLACES[keys[b]]["size"] if absf(ca.x - cb.x) < (float(sa[0]) + float(sb[0])) * 0.5 \ and absf(ca.z - cb.z) < (float(sa[1]) + float(sb[1])) * 0.5 \ and ueberlappung == "": ueberlappung = ("%s und %s bei %d Häusern" % [keys[a], keys[b], stufe]) # kein Weg führt durch ein Gebäude for k in netz.kanten: for key in keys: var s2: Array = DreamDB.PLACES[key]["size"] if Netz.trifft_rechteck(netz.knoten[k[0]], netz.knoten[k[1]], Orte.center(DreamDB.PLACES, key), Vector2(float(s2[0]), float(s2[1])), 0.0) and wegtreffer == "": wegtreffer = "%s bei %d Häusern" % [key, stufe] if not netz.zusammenhaengend() and lose == "": lose = "bei %d Häusern" % stufe # jedes Haus hängt am Netz und ist vom Dorfplatz aus zu erreichen for i in range(stufe): var key3 := DreamDB.haus_key(i) if not DreamDB.ANKER.has(key3): if ohne_anker == "": ohne_anker = "%s bei %d Häusern" % [key3, stufe] continue if netz.weglaenge(int(DreamDB.ANKER["lichtung"]), int(DreamDB.ANKER[key3])) == INF \ and unerreichbar == "": unerreichbar = "%s bei %d Häusern" % [key3, stufe] if stufe == 12: var sekt: Dictionary = {} for i in range(stufe): var c := Orte.center(DreamDB.PLACES, DreamDB.haus_key(i)) sekt[int(floor((atan2(c.z, c.x) + PI) / (TAU / 8.0))) % 8] = true sektoren_12 = sekt.size() if stufe == stufens_probe(): var flaeche := PI * pow(Game.insel_radius(), 2.0) if flaeche / float(stufe) > 2200.0: dichte = "%.0f m² je Haus" % (flaeche / float(stufe)) p(ueberlappung == "", "Über %d Ausbaustufen steht kein Gebäude im anderen%s" % [stufen, "" if ueberlappung == "" else " — " + ueberlappung]) p(wegtreffer == "", "Und kein Weg führt durch ein Gebäude%s" % ["" if wegtreffer == "" else " — " + wegtreffer]) p(lose == "", "Das Wegenetz hängt immer zusammen%s" % ["" if lose == "" else " — " + lose]) p(ohne_anker == "", "Jedes Haus hängt am Netz%s" % ["" if ohne_anker == "" else " — " + ohne_anker]) p(unerreichbar == "", "Und ist vom Dorfplatz aus erreichbar%s" % ["" if unerreichbar == "" else " — " + unerreichbar]) p(sektoren_12 >= 6, "Bei 12 Häusern ist das Dorf rundum gewachsen (%d von 8 Richtungen)" % sektoren_12) p(dichte == "", "Die Insel bleibt dicht besiedelt%s" % ["" if dichte == "" else " — " + dichte]) # Verwinkelt heißt: Gabeln und Schleifen, nicht Speichen zur Mitte Game.completed.clear() for i in range(18): Game.completed.append("stufe_%d" % i) Game.welt_erzeugen() var netz2: Netz = DreamDB.NETZ var gabeln := 0 for i in range(netz2.knoten.size()): if netz2.nachbarn(i).size() >= 3: gabeln += 1 var schleifen: int = netz2.kanten.size() - netz2.knoten.size() + 1 p(gabeln >= 6, "Bei 18 Häusern gabelt sich das Netz %d-mal" % gabeln) p(schleifen >= 2, "Und trägt %d Schleifen statt lauter Sackgassen" % schleifen) # Was einmal steht, bleibt stehen var vorher: Array = [] for i in range(18): vorher.append(Orte.center(DreamDB.PLACES, DreamDB.haus_key(i))) Game.completed.append("stufe_18") Game.welt_erzeugen() var verrutscht := 0 for i in range(18): if vorher[i].distance_to(Orte.center(DreamDB.PLACES, DreamDB.haus_key(i))) > 0.01: verrutscht += 1 p(verrutscht == 0, "Das 19. Haus verschiebt keines der 18 alten (%d)" % verrutscht) Game.completed.assign(merker) Game.welt_erzeugen() func stufens_probe() -> int: return 24 ## Die Erde ist ein erzeugter Bezirk. Also muss gelten: Parzellen liegen ## nebeneinander statt ineinander, jede hat Straßenanschluss, das Netz hängt ## zusammen -- und anders als im Dorf wächst hier nichts. func test_bezirk() -> void: abschnitt("Die Erde ist ein Bezirk") var netz: Netz = HumanDB.NETZ p(HumanDB.PARZELLEN.size() >= 60, "Der Bezirk ist in %d Parzellen geteilt" % HumanDB.PARZELLEN.size()) var ueber := 0 for i in range(HumanDB.PARZELLEN.size()): for j in range(i + 1, HumanDB.PARZELLEN.size()): var a: Dictionary = HumanDB.PARZELLEN[i] var b: Dictionary = HumanDB.PARZELLEN[j] if absf(a["pos"].x - b["pos"].x) < (a["size"].x + b["size"].x) * 0.5 - 0.05 \ and absf(a["pos"].z - b["pos"].z) < (a["size"].y + b["size"].y) * 0.5 - 0.05: ueber += 1 p(ueber == 0, "Keine Parzelle steht in einer anderen (%d)" % ueber) var ohne := 0 var weiteste := 0.0 for pz in HumanDB.PARZELLEN: var d := INF for st in HumanDB.STRASSEN: var f := Geometry3D.get_closest_point_to_segment(pz["pos"], st["a"], st["b"]) d = minf(d, f.distance_to(pz["pos"]) - maxf(pz["size"].x, pz["size"].y) * 0.5 - float(st["breite"]) * 0.5) weiteste = maxf(weiteste, d) if d > 2.0: ohne += 1 p(ohne == 0, "Jede Parzelle liegt an einer Straße (weiteste Lücke %.1f m)" % weiteste) p(netz.zusammenhaengend(), "Das Straßennetz hängt zusammen (%d Knoten, %d Kanten)" % [netz.knoten.size(), netz.kanten.size()]) var unerreichbar := 0 var keys: Array = HumanDB.PLACES.keys() for i in range(keys.size()): for j in range(i + 1, keys.size()): if netz.weglaenge(int(HumanDB.ANKER[keys[i]]), int(HumanDB.ANKER[keys[j]])) == INF: unerreichbar += 1 p(unerreichbar == 0, "Jeder Ort ist von jedem anderen aus zu erreichen (%d Lücken)" % unerreichbar) # Arterien im Raster, roji schmal und als Sackgasse ins Blockinnere var arterien := 0 var gassen := 0 for st in HumanDB.STRASSEN: if float(st["breite"]) >= HumanDB.ARTERIE: arterien += 1 else: gassen += 1 p(arterien == HumanDB.ACHSEN.size() * 2, "%d Arterien spannen das Raster auf" % arterien) p(gassen >= 4, "Und %d roji führen ins Blockinnere" % gassen) p(HumanDB.ROJI < HumanDB.ARTERIE * 0.6, "Eine roji ist mit %.1f m deutlich schmaler als eine Arterie (%.1f m)" % [HumanDB.ROJI, HumanDB.ARTERIE]) var sackgassen := 0 for st in HumanDB.STRASSEN: if float(st["breite"]) < HumanDB.ARTERIE: var ende: Vector3 = st["b"] if netz.nachbarn(netz.fuege_knoten(ende)).size() <= 1: sackgassen += 1 p(sackgassen >= gassen / 2, "Die meisten roji enden blind (%d von %d)" % [sackgassen, gassen]) # Alle Menschen sind da, und sie werden nicht mehr var vorher := HumanDB.LIST.size() p(vorher == HumanDB.EINWOHNER, "Alle %d Menschen sind von Anfang an da" % vorher) var merker: Array = Game.completed.duplicate() for i in range(6): Game.completed.append("stufe_%d" % i) Game.welt_erzeugen() p(HumanDB.LIST.size() == vorher, "Sechs erfüllte Träume ändern daran nichts (%d)" % HumanDB.LIST.size()) Game.completed.assign(merker) Game.welt_erzeugen() # Jeder wohnt in einer eigenen Wohnung var heime: Dictionary = {} for m in HumanDB.LIST: heime[str(m["heim"])] = true p(heime.size() == HumanDB.LIST.size(), "Jeder Mensch hat seine eigene Wohnung (%d)" % heime.size()) # Der mittlere Block bleibt frei var im_park := 0 var halb: float = (float(HumanDB.ACHSEN[2]) - float(HumanDB.ACHSEN[1])) * 0.5 \ - HumanDB.ARTERIE for pz in HumanDB.PARZELLEN: var q: Vector3 = pz["pos"] if absf(q.x) < halb and absf(q.z) < halb: im_park += 1 p(im_park == 0, "Im mittleren Block steht kein Haus — er ist der Park (%d)" % im_park)