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_haus_betreten() await test_hoersaal() await test_mission_annehmen() await test_reise_zur_erde() await test_tagesablauf() await test_wege_machbar() await test_geist() await test_technik_anwenden() 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() 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() ## 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.einsicht == 3, "Startkapital 3 Einsicht") p(Game.known.is_empty(), "Noch keine Technik gelernt") 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"] != "" if not vollstaendig: p(false, "Technik %s hat alle drei Hinweise" % t["id"]) p(true, "Alle %d Techniken tragen Was/Wann/Warum" % TechDB.LIST.size()) ui().journal_panel("techniken") p(ui().offen and ui()._kind == "journal", "Journal öffnet sich") p(_journal_text().contains("Hörsaal"), "Ohne gelernte Technik verweist das Journal auf den Hörsaal") p(not _knopf_da("Lernen") and not _knopf_da("Vertiefen"), "Das Journal lehrt und vertieft 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.learn(erster), "Der erste Weg ist gelernt: %s" % TechDB.by_id(erster)["name"]) p(Game.einsicht == 2, "Das kostet 1 von 3 Einsicht (%d)" % Game.einsicht) p(not Game.learn(str(TechDB.LIST[1]["id"])), "Der zweite Weg ist trotz Einsicht nirgends zu lernen") 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("Lernen"): 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.einsicht = 1 Game.known.clear() ui().lehre_panel() var vorher := Game.known.size() p(_druecke("Lernen"), "Ein Lernen-Knopf steht bereit") p(Game.known.size() == vorher + 1, "Im Hörsaal gelernt (%d -> %d)" % [vorher, Game.known.size()]) p(Game.einsicht == 0, "Lernen kostet 1 Einsicht") Game.einsicht = 1 ui().lehre_panel() var xp_summe := 0 for id in Game.known: xp_summe += int(Game.known[id]) p(_druecke("Vertiefen"), "Ein Vertiefen-Knopf steht bereit") var xp_neu := 0 for id in Game.known: xp_neu += int(Game.known[id]) p(xp_neu == xp_summe + 3, "Vertiefen bringt 3 EP (%d -> %d)" % [xp_summe, xp_neu]) p(Game.einsicht == 0, "Vertiefen kostet 1 Einsicht") ui().schliessen() # Ausgangslage für die folgenden Prüfungen wiederherstellen Game.known = {"pomodoro": 0, "zerlegen": 0, "umgebung": 0} Game.einsicht = 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") var alle_aus := true for h in HumanDB.LIST: if not _panel_text().contains(str(h["name"])): alle_aus = false p(alle_aus, "Alle %d Menschen hängen aus" % HumanDB.LIST.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) _wege_pruefen("Traumwelt", DreamDB.PLACES, DreamDB.LIST) func _wege_pruefen(welt_name: String, places: Dictionary, liste: Array) -> 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) / 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) -> float: var pts: Array = Orte.route(places, von, nach, Vector3.ZERO) 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") 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, "Misserfolg bringt keinen Fortschritt") p(Game.known["pomodoro"] > 0, "Jede Anwendung bringt Erfahrung") Game.progress["mensch_0"] = 0.0 Game.used.clear() Game.known["pomodoro"] = 28 var ertraege: Array = [] for i in range(6): 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(_panel_text().contains("Pomodoro-Technik"), "Gelernte Techniken stehen zur Wahl") p(_druecke("Diese Regel"), "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 hält nur einen Posten Game.regel_setzen("mensch_0", "arbeitet", "zerlegen") p(Game.regel_anzahl("mensch_0") == 3, "Dieselbe Technik anderswo einzusetzen fügt keine Regel hinzu (%d)" % Game.regel_anzahl("mensch_0")) p(Game.regel_fuer("mensch_0", "gruebelt") == "" and Game.regel_fuer("mensch_0", "arbeitet") == "zerlegen", "Sie ist umgezogen statt sich zu verdoppeln") Game.regel_setzen("mensch_0", "arbeitet", "") Game.regel_setzen("mensch_0", "gruebelt", "zerlegen") # 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 einsicht_vorher := Game.einsicht var runden := 0 while Game.mission_progress("mensch_0") < Game.MISSION_ZIEL and runden < 400: Game.apply_technique("pomodoro", "mensch_0", "zoegert", false) Game.apply_technique("zerlegen", "mensch_0", "gruebelt", false) 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(Game.einsicht == einsicht_vorher + 3, "Abschluss bringt 3 Einsicht") 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()) Game.einsicht = 3 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("Lernen"), "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.einsicht = 2 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) _wege_pruefen("Stadt mit %d Menschen" % HumanDB.LIST.size(), HumanDB.PLACES, HumanDB.LIST) Game.completed.assign(merker) Game.welt_erzeugen() func test_techniken_verbessern_sich() -> void: abschnitt("Techniken verbessern sich durch Anwendung") Game.einsicht = 2 p(Game.learn("zwei_minuten"), "Technik gelernt") p(Game.skill_of("zwei_minuten") == 1, "Start auf Stufe 1") var einsicht_vorher := Game.einsicht var anwendungen := 0 while Game.skill_of("zwei_minuten") < 3 and anwendungen < 60: 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")]) p(Game.einsicht == einsicht_vorher, "Das kostet keine Einsicht") var vor_xp: int = Game.known["zwei_minuten"] 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")) var xp_vor: int = Game.known["zwei_minuten"] p(Game.deepen("zwei_minuten"), "Vertiefen funktioniert") p(Game.known["zwei_minuten"] == xp_vor + 3, "Vertiefen bringt 3 EP") p(Game.einsicht == einsicht_vorher - 1, "Vertiefen kostet 1 Einsicht") 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"] 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 acht Steuerungsaktionen sind mit Tasten belegt") 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.einsicht = 3 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.einsicht >= 3, "Der erfüllte Traum bringt 3 Einsicht (%d)" % Game.einsicht) p(Game.offene_techniken() > 1, "Und öffnet neue Wege: %d entdeckt" % Game.offene_techniken())