Files
dreamer-game/tests/verify.gd
2026-09-05 11:07:43 +02:00

2250 lines
81 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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_verfolgung()
await test_gehen_bei_tempo()
await test_diagnose()
await test_notieren_im_spiel()
await test_tagesablauf_anzeige()
await test_ablauf_im_hud()
await test_probleme_und_momente()
await test_systeme()
await test_tempo_des_traums()
await test_cooldown()
await test_chance_verdeckt()
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 die Tätigkeiten benennen, gegen die die Technik
# hilft — sonst ist im Spiel nicht herauszulesen, wohin sie gehört.
var stamm := {
"putzflucht": "aufräumt", "recherche": "recherchiert", "liste": "später-liste",
"jasagen": "ja sagt", "scrollen": "scrollt", "serie": "serie",
"wach": "wach", "kuehlschrank": "kühlschrank", "spielen": "spielt",
"umschalten": "umschalt", "klagen": "klagt", "vergleichen": "vergleicht",
}
var luecken: Array = []
var zuviel: Array = []
for t in TechDB.LIST:
var text: String = str(t["woran"]).to_lower()
var eigene := Ursachen.alle_blocker(str(t["ursache"]))
var trifft := 0
for b in eigene:
if text.contains(str(stamm[b])):
trifft += 1
if trifft == 0:
luecken.append("%s nennt keinen eigenen Blocker" % t["id"])
for b in stamm:
if eigene.has(b):
continue
if text.contains(str(stamm[b])):
zuviel.append("%s nennt fremdes »%s«" % [t["id"], b])
p(luecken.is_empty(), "Jeder Woran-Hinweis nennt eine Tätigkeit seiner Ursache%s" %
("" if luecken.is_empty() else " — " + ", ".join(luecken)))
p(zuviel.is_empty(), "Und keine, gegen die die Technik gar nicht hilft%s" %
("" if zuviel.is_empty() else " — " + ", ".join(zuviel)))
ui().journal_panel("techniken")
p(ui().offen and ui()._kind == "journal", "Journal öffnet sich")
p(_knopf_texte().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("Schwarze"),
"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
var zeilen_buch := _knopf_texte()
for id in Game.known:
if not zeilen_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.kennt(str(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)
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 := _knopf_texte()
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(_panel_text().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")
ui().aushang_panel("mensch_0")
p(_panel_text().contains(str(HumanDB.by_id("mensch_0")["problem"])),
"Der Aushang zeigt aufgeschlagen, was im Weg steht")
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(_knopf_texte().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.kennt(str(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")
## Bei hohem Tempo wird der Schritt je Bild größer als die Ankunftsschwelle.
## Wer dann über den Wegpunkt hinausläuft, pendelt um ihn herum statt anzukommen.
func test_gehen_bei_tempo() -> void:
abschnitt("Gehen bleibt auch im Schnelllauf ruhig")
ui().schliessen()
var mensch: Bewohner = welt().humans[0]
var stufe_kehrt: Dictionary = {}
for stufe in [1, 3]:
Game.tempo_setzen(stufe)
Game.time_h = 12.9
for i in range(400):
schritte(1, 1.0 / 60.0)
if not mensch._path.is_empty():
break
var kehrt := 0
var bewegt := 0
var vorher: Vector3 = mensch.global_position
var richtung := Vector3.ZERO
for i in range(600):
schritte(1, 1.0 / 60.0)
if mensch._path.is_empty():
continue
var d: Vector3 = mensch.global_position - vorher
d.y = 0.0
if d.length() > 0.001:
bewegt += 1
if richtung != Vector3.ZERO and d.normalized().dot(richtung) < -0.5:
kehrt += 1
richtung = d.normalized()
vorher = mensch.global_position
stufe_kehrt[stufe] = kehrt
var weite := Bewohner.GEHTEMPO * (1.0 / 60.0) * Game.faktor_von(stufe)
p(kehrt <= 4, "Tempo %d (%d×, %.2f je Bild): %d Kehrtwenden in %d Bildern" %
[stufe, int(Game.faktor_von(stufe)), weite, kehrt, bewegt])
p(int(stufe_kehrt[3]) <= int(stufe_kehrt[1]) + 4,
"Schnelllauf zappelt nicht mehr als Normaltempo (%d gegen %d)" %
[stufe_kehrt[3], stufe_kehrt[1]])
Game.tempo_setzen(1)
## Sich an einen Menschen hängen: der Geist begleitet ihn durch seinen Tag,
## statt ihm hinterherzulaufen. Damit bleibt er die ganze Zeit beobachtbar.
func test_verfolgung() -> void:
abschnitt("Als Geist einen Menschen begleiten")
p(InputMap.has_action("folgen") and not InputMap.action_get_events("folgen").is_empty(),
"Eigene Taste fürs Folgen")
ui().schliessen()
main.player.set_mode(Player.Mode.GEIST)
main.player.look_enabled = true
main.player.yaw = 0.0
var mensch: Bewohner = welt().humans[0]
Game.active_mission = str(mensch.data["id"])
main.player.global_position = mensch.global_position + Vector3(0, 1.5, 2.0)
main.player.velocity = Vector3.ZERO
main._process(0.05)
p(main._fokus_mensch == mensch, "Der Mensch ist im Fokus")
main._verfolgen()
p(main.player.folgt == mensch, "F hängt den Geist an ihn")
# Er lebt seinen Tag weiter, der Geist bleibt dran
var weiteste := 0.0
var gelaufen := false
var im_fokus := 0
for i in range(900):
schritte(1)
await get_tree().physics_frame
if not mensch._path.is_empty():
gelaufen = true
if main._fokus_mensch == mensch:
im_fokus += 1
weiteste = maxf(weiteste,
main.player.global_position.distance_to(mensch.global_position))
p(gelaufen, "Er wechselt in dieser Zeit den Ort")
p(weiteste < 6.0, "Der Geist bleibt die ganze Zeit dicht dabei (höchstens %.1f)" % weiteste)
p(im_fokus > 850, "Und behält ihn im Fokus (%d von 900 Schritten)" % im_fokus)
p(main._fokus_mensch == mensch or main.player.folgt == mensch,
"Unterwegs bleibt er notierbar")
# Eigene Bewegung löst die Verfolgung
Input.action_press("vor")
for i in range(10):
await get_tree().physics_frame
Input.action_release("vor")
p(main.player.folgt == null, "Wer selbst losgeht, lässt ihn los")
# Die Taste löst ebenfalls
main.player.global_position = mensch.global_position + Vector3(0, 1.5, 2.0)
main.player.velocity = Vector3.ZERO
main._process(0.05)
main._verfolgen()
p(main.player.folgt == mensch, "Wieder angehängt")
main._verfolgen()
p(main.player.folgt == null, "F lässt wieder los")
# In der Traumwelt gibt es niemanden zu verfolgen
main._traumwelt()
await get_tree().process_frame
main._verfolgen()
p(main.player.folgt == null, "In der Traumwelt hängt sich der Läufer an niemanden")
main._erde()
await get_tree().process_frame
## Woran der Traum scheitert, steht nirgends. Man findet es, indem man
## dabeisteht -- awareness training, bevor irgendetwas ersetzt wird.
func test_diagnose() -> void:
abschnitt("Diagnose — die Ursache zeigt sich im Tag")
var id := "mensch_0"
Game.active_mission = id
Game.systeme.clear()
Game.erkannt_slots.clear()
Game.notiz_sperre = 0.0
var urs: Array = Game.ursachen_von(id)
p(urs.size() >= 2 and urs.size() <= 3,
"%s scheitert an %d Ursachen: %s" % [HumanDB.by_id(id)["name"], urs.size(), str(urs)])
var alle_bekannt := true
for u in urs:
if not Ursachen.LISTE.has(u):
alle_bekannt = false
p(alle_bekannt, "Alle davon stehen im Katalog")
# Zu jeder Ursache eine Tätigkeit im Tag
var luecken: Array = []
for u in urs:
var da := false
for nr in range(Game.slots(id).size()):
if Ursachen.ursache_von(Game.slot_grund(id, nr)) == str(u):
da = true
if not da:
luecken.append(str(u))
p(luecken.is_empty(), "Jede Ursache zeigt sich in einer Tätigkeit%s" %
("" if luecken.is_empty() else " — fehlt: " + ", ".join(luecken)))
# Ein Blocker-Slot ist zunächst verdeckt, ein neutraler nicht
var blocker_nr := -1
var neutral_nr := -1
for nr in range(Game.slots(id).size()):
var g := Game.slot_grund(id, nr)
if Ursachen.ist_blocker(g) and blocker_nr < 0:
blocker_nr = nr
elif g != "schlaeft" and not Ursachen.ist_blocker(g) and neutral_nr < 0:
neutral_nr = nr
p(blocker_nr >= 0 and neutral_nr >= 0, "Ihr Tag hat Blocker und freie Slots")
p(not Game.erkannt(id, blocker_nr), "Was im Blocker-Slot läuft, ist verdeckt")
p(Game.erkannt(id, neutral_nr), "Ein freier Slot ist offen sichtbar")
var ursache := Ursachen.ursache_von(Game.slot_grund(id, blocker_nr))
p(not Game.ursache_bekannt(id, ursache), "Und die Ursache ist noch unbekannt")
# Solange man nicht lange genug dabeisteht, passiert nichts
# Zusehen allein deckt nichts auf -- der Spieler muss den Moment benennen
for i in range(60):
schritte(1)
p(not Game.erkannt(id, blocker_nr), "Bloßes Dabeistehen deckt nichts auf")
p(Game.notieren(id, neutral_nr) == "daneben",
"Im freien Slot geht die Notiz daneben")
p(Game.notiz_sperre > 0.0, "Ein Fehlgriff sperrt kurz (%.0f s)" % Game.notiz_sperre)
p(Game.notieren(id, blocker_nr) == "gesperrt", "Solange nimmt er keine Notiz an")
Game.notiz_sperre = 0.0
p(Game.notieren(id, blocker_nr) == "treffer", "Im Blocker-Slot sitzt sie")
p(Game.erkannt(id, blocker_nr), "Und deckt auf, was dort läuft")
p(is_equal_approx(Game.notiz_sperre, 0.0), "Ein Treffer sperrt nicht")
p(Game.notieren(id, blocker_nr) == "bekannt", "Zweimal notieren bringt nichts Neues")
p(Game.ursache_bekannt(id, ursache),
"Und damit die Ursache: %s" % Ursachen.LISTE[ursache]["name"])
# Ohne Diagnose lässt sich nichts einrichten
Game.known = {"pomodoro": 0, "zerlegen": 0, "box_atmung": 0, "nein_sagen": 0,
"habit_stack": 0, "reframing": 0}
var zweiter := -1
for nr in range(Game.slots(id).size()):
if Ursachen.ist_blocker(Game.slot_grund(id, nr)) and nr != blocker_nr:
zweiter = nr
if zweiter >= 0:
var pr := Game.system_pruefen(id, zweiter, "pomodoro")
p(not pr["geht"] and str(pr["grund"]).contains("gesehen"),
"In einen unerkannten Slot lässt sich nichts bauen")
## Gegen eine Ursache baut man ein System. Passt die Belohnung, hält es besser.
## Die zweite Ebene: Probleme hängen nicht an Stunden, sondern an Momenten.
## Gewohnheiten laufen ohne dich weiter -- hier musst du dabei sein.
func test_probleme_und_momente() -> void:
abschnitt("Probleme zeigen sich in Momenten")
var id := "mensch_0"
Game.active_mission = id
Game.situation = {}
Game.situation_pause = 0.0
Game.probleme_bekannt.clear()
Game.problem_stand.clear()
Game.ueberwunden.clear()
Game.cooldown.clear()
ui().schliessen()
var prob: Array = Game.probleme_von(id)
p(prob.size() >= 1 and prob.size() <= 2,
"%s trägt %d Problem(e): %s" % [HumanDB.by_id(id)["name"], prob.size(), str(prob)])
var alle_bekannt := true
for x in prob:
if not Probleme.problem_da(str(x)):
alle_bekannt = false
p(alle_bekannt, "Alle stehen im Katalog")
var ohne_situation: Array = []
for x in Probleme.LISTE:
if Probleme.situationen_von(str(x)).is_empty():
ohne_situation.append(str(x))
p(ohne_situation.is_empty(), "Jedes Problem zeigt sich in Situationen%s" %
("" if ohne_situation.is_empty() else " — außer " + ", ".join(ohne_situation)))
var ohne_technik: Array = []
for x in Probleme.LISTE:
if Probleme.techniken_gegen(str(x)).is_empty():
ohne_technik.append(str(x))
p(ohne_technik.is_empty(), "Und gegen jedes gibt es Techniken%s" %
("" if ohne_technik.is_empty() else " — außer " + ", ".join(ohne_technik)))
# Sie kommt von allein, aber nicht sofort
p(not Game.situation_laeuft(), "Erst läuft kein Moment")
Game.situationen_ticken(1.0, false)
p(not Game.situation_laeuft(), "Bei einem Schlafenden kommt keiner")
Game.situationen_ticken(1.0, true)
p(Game.situation_laeuft(), "Bei einem wachen Menschen bricht einer auf")
var problem := Game.situation_problem()
p(prob.has(problem), "Und gehört zu einem seiner Probleme: %s" % problem)
# Solange sie läuft, steht der Traum still
Game.progress[id] = 10.0
Game.traum_voranbringen(6.0)
p(is_equal_approx(Game.mission_progress(id), 10.0),
"Im Moment kommt der Traum nicht voran (%.1f %%)" % Game.mission_progress(id))
# Dabeisein zeigt, was dahintersteckt
p(not Game.problem_bekannt(id, problem), "Was dahintersteckt, ist noch unbekannt")
Game.situation_sehen()
p(Game.problem_bekannt(id, problem), "Dabeisein zeigt es: %s" %
Probleme.name_von(problem))
# Eine unpassende Technik greift nicht
var passend := ""
var unpassend := ""
for tid in TechDB.ids():
if Probleme.hilft(tid, problem) and passend == "":
passend = tid
elif not Probleme.hilft(tid, problem) and unpassend == "":
unpassend = tid
Game.known[passend] = 0
Game.known[unpassend] = 0
var res_falsch := Game.situation_anwenden(unpassend)
p(res_falsch["ok"] and not res_falsch["treffer"],
"%s greift bei %s nicht" % [TechDB.by_id(unpassend)["name"],
Probleme.name_von(problem)])
p(Game.situation_laeuft(), "Der Moment läuft weiter")
# Die passende beendet ihn
var vorher: float = Game.mission_progress(id)
var res := Game.situation_anwenden(passend)
p(res["treffer"], "%s trägt hindurch" % TechDB.by_id(passend)["name"])
p(not Game.situation_laeuft(), "Der Moment ist vorbei")
p(Game.mission_progress(id) > vorher, "Und bringt den Traum voran (%.0f → %.0f %%)" %
[vorher, Game.mission_progress(id)])
p(Game.problem_fortschritt(id, problem) == 1, "Einmal geschafft")
# Oft genug, und das Problem kommt nicht wieder
for i in range(Game.PROBLEM_UEBERWINDEN):
Game.situation = {"mensch": id, "key": Probleme.situationen_von(problem)[0],
"problem": problem, "rest": Game.SITUATION_DAUER}
Game.cooldown.clear()
Game.situation_anwenden(passend)
p(Game.problem_ueberwunden(id, problem),
"Nach %d Malen ist %s überwunden" % [Game.PROBLEM_UEBERWINDEN,
Probleme.name_von(problem)])
Game.situation = {}
Game.situation_pause = 0.0
var kommt_wieder := false
for i in range(200):
Game.situationen_ticken(1.0, true)
if Game.situation_laeuft() and Game.situation_problem() == problem:
kommt_wieder = true
break
p(not kommt_wieder, "Dieser Moment kommt nicht wieder")
# Beide Ebenen stehen nebeneinander im HUD
Game.situation = {}
ui()._hud_aktualisieren()
p(ui()._ablauf.visible and ui()._probleme.visible,
"Tagesablauf und Momente stehen beide im HUD")
p(ui()._probleme.text.contains(Probleme.name_von(problem)),
"Die Momente nennen das Problem: %s" % ui()._probleme.text.replace("\n", " / "))
Game.situation = {"mensch": id, "key": Probleme.situationen_von(problem)[0],
"problem": problem, "rest": 9.0}
ui()._hud_aktualisieren()
p(ui()._probleme.text.contains("Jetzt:"), "Der laufende Moment steht oben")
# Und er kündigt sich am Menschen selbst an, nicht nur im HUD
var wer: Bewohner = null
for mm in welt().humans:
if mm.data["id"] == id:
wer = mm
p(wer.beschriftung().contains(str(Probleme.SITUATIONEN[Game.situation["key"]]["name"])),
"Am Menschen steht, was gerade aufbricht: %s" %
wer.beschriftung().replace("\n", " / "))
Game.situation = {}
p(not wer.beschriftung().contains("»"), "Ohne Moment steht dort nichts davon")
# E führt in den Moment, sonst in den Tagesablauf -- die zwei Ebenen
# teilen sich eine Taste, ohne sich in die Quere zu kommen
var mensch: Bewohner = null
for mm in welt().humans:
if mm.data["id"] == id:
mensch = mm
main.player.set_mode(Player.Mode.GEIST)
main.player.folgt = null
main.player.global_position = mensch.global_position + Vector3(0, 1.5, 1.5)
Game.situation = {}
main._process(0.05)
main._interagieren()
p(ui()._kind == "anwenden", "Ohne Moment führt E in den Tagesablauf (%s)" % ui()._kind)
ui().schliessen()
Game.situation = {"mensch": id, "key": Probleme.situationen_von(problem)[0],
"problem": problem, "rest": 12.0}
main._process(0.05)
main._interagieren()
p(ui()._kind == "situation", "Im Moment führt dieselbe Taste dorthin (%s)" % ui()._kind)
p(_panel_text().contains(str(Probleme.SITUATIONEN[Game.situation["key"]]["was"])),
"Das Panel zeigt, was gerade passiert")
ui().schliessen()
# Die Gewohnheitsebene läuft davon unberührt weiter
Game.situation = {}
Game.progress[id] = 0.0
Game.traum_voranbringen(24.0)
p(Game.mission_progress(id) > 0.0,
"Ohne Moment liefert der Tagesablauf wieder sein Tempo (%.0f %%)" %
Game.mission_progress(id))
Game.situation = {}
Game.ueberwunden.clear()
Game.probleme_bekannt.clear()
Game.problem_stand.clear()
## Der Ablauf steht auch im HUD, ohne dass man ein Panel öffnen muss.
func test_ablauf_im_hud() -> void:
abschnitt("Der Ablauf steht im HUD")
var id := "mensch_0"
Game.gesehen.clear()
Game.erkannt_slots.clear()
Game.systeme.clear()
Game.active_mission = ""
ui().schliessen()
ui()._hud_aktualisieren()
p(not ui()._ablauf.visible, "Ohne Mission zeigt das HUD keinen Ablauf")
Game.active_mission = id
Game.time_h = 9.5
ui()._hud_aktualisieren()
p(ui()._ablauf.visible, "Mit Mission schon")
p(ui()._zellen.size() == 24, "Ein Feld je Stunde (%d)" % ui()._zellen.size())
p(ui()._ablauf_titel.text.contains("0 – 23"), "Beschriftet von 0 bis 23 Uhr")
p(ui()._ablauf_titel.text.contains("24 Stunden unbekannt"),
"Am Anfang ist alles unbekannt: %s" % ui()._ablauf_titel.text)
var unbekannt: Color = ui()._ablauf_farbe(id, Game.slot_bei_stunde(id, 9))
var nr := Game.slot_bei_stunde(id, 9)
Game.sehen(id, nr)
ui()._hud_aktualisieren()
p(ui()._ablauf_farbe(id, nr) != unbekannt, "Eine gesehene Stunde färbt sich um")
p(not ui()._ablauf_titel.text.contains("24 Stunden"),
"Und der Zähler geht runter: %s" % ui()._ablauf_titel.text)
p(ui()._ablauf_jetzt.text.begins_with("09 Uhr"),
"Darunter steht die laufende Stunde: %s" % ui()._ablauf_jetzt.text)
# Die laufende Stunde ist hervorgehoben
var jetzt: ColorRect = ui()._zellen[9]
var andere: ColorRect = ui()._zellen[8]
p(jetzt.custom_minimum_size.y > andere.custom_minimum_size.y,
"Das Feld der laufenden Stunde steht heraus")
# Ein benannter Blocker sieht anders aus als eine harmlose Stunde
var blocker_nr := -1
var frei_nr := -1
for n in range(Game.slots(id).size()):
var g := Game.slot_grund(id, n)
if Ursachen.ist_blocker(g) and blocker_nr < 0:
blocker_nr = n
elif g != "schlaeft" and not Ursachen.ist_blocker(g) and frei_nr < 0:
frei_nr = n
Game.sehen(id, blocker_nr)
Game.sehen(id, frei_nr)
var vor_notiz: Color = ui()._ablauf_farbe(id, blocker_nr)
Game.notieren(id, blocker_nr)
p(ui()._ablauf_farbe(id, blocker_nr) != vor_notiz,
"Notiert wechselt der Blocker noch einmal die Farbe")
p(ui()._ablauf_farbe(id, blocker_nr) != ui()._ablauf_farbe(id, frei_nr),
"Und hebt sich von einer harmlosen Stunde ab")
Game.notiz_sperre = 0.0
## Der Ablauf 0-23 Uhr: am Anfang leer, füllt sich durchs Dabeisein.
func test_tagesablauf_anzeige() -> void:
abschnitt("Der Tag steht als Ablauf von 0 bis 23 Uhr")
var id := "mensch_0"
Game.active_mission = id
Game.gesehen.clear()
Game.erkannt_slots.clear()
Game.systeme.clear()
Game.notiz_sperre = 0.0
# Jede Stunde des Tages hat einen Slot, und jeder Tag ist derselbe
var luecken := 0
for stunde in range(24):
if Game.slot_bei_stunde(id, stunde) < 0:
luecken += 1
p(luecken == 0, "Alle 24 Stunden sind belegt")
Game.day = 1
var tag_eins: Array = []
for stunde in range(24):
tag_eins.append(Game.slot_bei_stunde(id, stunde))
Game.day = 9
var tag_neun: Array = []
for stunde in range(24):
tag_neun.append(Game.slot_bei_stunde(id, stunde))
p(tag_eins == tag_neun, "Tag 9 sieht aus wie Tag 1 — es gibt keine Wochentage")
Game.day = 1
ui().systeme_panel(id)
var zeilen := _knopf_texte()
p(zeilen.contains("00 – "), "Der Ablauf beginnt um 00 Uhr")
p(zeilen.contains(" – 24"), "Und reicht bis 24 Uhr")
var leer := 0
for stunde in range(24):
if not Game.gesehen_slot(id, Game.slot_bei_stunde(id, stunde)):
leer += 1
p(leer == 24, "Am Anfang ist jede der 24 Stunden unbekannt (%d)" % leer)
p(zeilen.contains("—") and not zeilen.contains("schläft"),
"Und im Ablauf steht nur ein Strich")
ui().schliessen()
# Dabeistehen füllt genau die Stunde, in der man steht
var mensch: Bewohner = null
for m in welt().humans:
if m.data["id"] == id:
mensch = m
var frei_nr := -1
for nr in range(Game.slots(id).size()):
var g := Game.slot_grund(id, nr)
if g != "schlaeft" and not Ursachen.ist_blocker(g):
frei_nr = nr
break
await _hin_zu(mensch, float(Game.slots(id)[frei_nr][0]) + 0.5)
main._process(0.05)
p(Game.gesehen_slot(id, frei_nr), "Wer dabeisteht, füllt die Stunde")
ui().systeme_panel(id)
p(_knopf_texte().contains(Orte.text(Game.slot_grund(id, frei_nr))),
"Sie steht jetzt im Ablauf: %s" % Orte.text(Game.slot_grund(id, frei_nr)))
ui().schliessen()
# Ein Blocker bleibt ein Fragezeichen, bis er benannt ist
var blocker_nr := -1
for nr in range(Game.slots(id).size()):
if Ursachen.ist_blocker(Game.slot_grund(id, nr)):
blocker_nr = nr
break
Game.sehen(id, blocker_nr)
ui().systeme_panel(id)
p(_knopf_texte().contains("?"), "Was schiefläuft, steht erst als Fragezeichen da")
ui().schliessen()
Game.notieren(id, blocker_nr)
ui().systeme_panel(id)
var b: Dictionary = Ursachen.BLOCKER[Game.slot_grund(id, blocker_nr)]
p(_knopf_texte().contains(str(b["name"])),
"Nach dem Notieren steht dort, was sie tut")
p(_knopf_texte().contains(str(Ursachen.LISTE[b["ursache"]]["name"])),
"Und warum: %s" % Ursachen.LISTE[b["ursache"]]["name"])
ui().schliessen()
## Der ganze Weg: dabeistehen, den Moment einschätzen, R drücken.
func test_notieren_im_spiel() -> void:
abschnitt("Notieren ist eine Entscheidung, kein Timer")
var id := "mensch_0"
Game.active_mission = id
Game.erkannt_slots.clear()
Game.notiz_sperre = 0.0
ui().schliessen()
main.player.set_mode(Player.Mode.GEIST)
main.player.look_enabled = true
var blocker_nr := -1
var frei_nr := -1
for nr in range(Game.slots(id).size()):
var g := Game.slot_grund(id, nr)
if Ursachen.ist_blocker(g) and blocker_nr < 0:
blocker_nr = nr
elif g != "schlaeft" and not Ursachen.ist_blocker(g) and frei_nr < 0:
frei_nr = nr
p(blocker_nr >= 0 and frei_nr >= 0, "Ihr Tag hat einen Blocker und einen freien Slot")
var mensch: Bewohner = null
for m in welt().humans:
if m.data["id"] == id:
mensch = m
# Im freien Slot danebengegriffen
await _hin_zu(mensch, float(Game.slots(id)[frei_nr][0]) + 0.5)
p(mensch.slot == frei_nr, "Sie steht im freien Slot (%d)" % mensch.slot)
main._notieren()
p(not Game.erkannt(id, blocker_nr), "Hier gibt es nichts zu notieren")
p(Game.notiz_sperre > 0.0, "Der Fehlgriff kostet eine Sperre")
# Sperre läuft in Echtzeit ab
for i in range(200):
main._process(0.05)
p(is_equal_approx(Game.notiz_sperre, 0.0), "Sie läuft von allein ab")
# Im Blocker-Slot getroffen
await _hin_zu(mensch, float(Game.slots(id)[blocker_nr][0]) + 0.5)
p(mensch.slot == blocker_nr, "Jetzt steht sie im Blocker-Slot (%d)" % mensch.slot)
p(main._fokus_mensch == mensch, "Und ist in Reichweite")
main._notieren()
p(Game.erkannt(id, blocker_nr), "R notiert, was hier schiefläuft: %s" %
Orte.text(Game.slot_grund(id, blocker_nr)))
# Ein fremder Mensch geht niemanden etwas an
Game.erkannt_slots.clear()
Game.notiz_sperre = 0.0
var fremd: Bewohner = null
for m in welt().humans:
if m.data["id"] != id:
fremd = m
main.player.folgt = null
main.player.global_position = fremd.global_position + Vector3(0, 1.5, 1.5)
main._process(0.05)
main._notieren()
p(Game.erkannt_slots.is_empty(),
"Ohne angenommene Mission notiert er bei %s nichts" % fremd.data["name"])
p(is_equal_approx(Game.notiz_sperre, 0.0), "Und das kostet auch keine Sperre")
# Aus der Verfolgung heraus geht es ebenfalls
Game.erkannt_slots.clear()
await _hin_zu(mensch, float(Game.slots(id)[blocker_nr][0]) + 0.5)
main._verfolgen()
p(main.player.folgt == mensch, "An sie gehängt")
main._notieren()
p(Game.erkannt(id, blocker_nr), "Auch im Vorbeischweben lässt sich notieren")
main.player.folgt = null
## Stellt den Geist zur genannten Stunde neben den Menschen.
func _hin_zu(mensch: Bewohner, stunde: float) -> void:
for i in range(600):
schritte(1, 0.05, stunde)
if mensch._path.is_empty():
break
main.player.folgt = null
main.player.global_position = mensch.global_position + Vector3(0, 1.5, 1.5)
main.player.velocity = Vector3.ZERO
await get_tree().process_frame
main._process(0.05)
func test_systeme() -> void:
abschnitt("Systeme — gegen die Ursache, nicht gegen die Tätigkeit")
var id := "mensch_0"
Game.active_mission = id
Game.systeme.clear()
Game.cooldown.clear()
Game.known.clear()
for t in TechDB.LIST:
Game.known[str(t["id"])] = 28
Game.erkannt_slots[id] = {}
for nr in range(Game.slots(id).size()):
Game.erkannt_slots[id][nr] = true
var blocker_nr := -1
for nr in range(Game.slots(id).size()):
if Ursachen.ist_blocker(Game.slot_grund(id, nr)):
blocker_nr = nr
break
var b: Dictionary = Ursachen.BLOCKER[Game.slot_grund(id, blocker_nr)]
p(blocker_nr >= 0, "Ein Blocker im Tag: %s" % b["name"])
# falsche Ursache geht nicht
var falsch := ""
for tid in TechDB.ids():
if str(TechDB.by_id(tid)["ursache"]) != str(b["ursache"]):
falsch = tid
break
var pr_falsch := Game.system_pruefen(id, blocker_nr, falsch)
p(not pr_falsch["geht"], "Eine Technik gegen eine andere Ursache passt nicht hier")
# richtige Ursache, gleiche Belohnung -> bessere Güte
var gleich := ""
var anders := ""
for tid in TechDB.fuer_ursache(str(b["ursache"])):
var t := TechDB.by_id(str(tid))
if str(t["belohnung"]) == str(b["belohnung"]) or TechDB.entfernt_ausloeser(str(tid)):
gleich = str(tid)
else:
anders = str(tid)
p(gleich != "", "Es gibt eine Technik mit derselben Belohnung: %s" %
TechDB.by_id(gleich)["name"])
var g1: float = float(Game.system_pruefen(id, blocker_nr, gleich)["guete"])
p(g1 > Game.GUETE_NEU, "Die hält besser (Güte %.2f)" % g1)
if anders != "":
var g2: float = float(Game.system_pruefen(id, blocker_nr, anders)["guete"])
p(g2 < g1, "Eine mit anderer Belohnung hält schlechter (%.2f)" % g2)
# einbauen
Game.cooldown.clear()
var vorher := Game.slot_aktivitaet(id, blocker_nr)
var versuche := 0
var res: Dictionary = {}
while versuche < 30:
Game.cooldown.clear()
res = Game.system_setzen(id, blocker_nr, gleich, false)
versuche += 1
if res.get("erfolg", false):
break
p(res.get("erfolg", false), "Nach höchstens %d Versuchen steht das System" % versuche)
p(Game.slot_aktivitaet(id, blocker_nr) != vorher,
"Der Slot zeigt jetzt die Ersatzhandlung: %s" % Game.slot_aktivitaet(id, blocker_nr))
p(Game.ursache_geloest(id, str(b["ursache"])) > 0.0,
"Und die Ursache gilt als gelöst")
Game.system_entfernen(id, blocker_nr)
p(Game.ursache_geloest(id, str(b["ursache"])) == 0.0,
"Herausnehmen macht es rückgängig")
p(Game.slot_aktivitaet(id, blocker_nr) == vorher, "Der Blocker ist wieder da")
# Schlaf bleibt tabu
var schlaf := -1
for nr in range(Game.slots(id).size()):
if Game.slot_grund(id, nr) == "schlaeft":
schlaf = nr
p(schlaf >= 0 and not Game.system_pruefen(id, schlaf, gleich)["geht"],
"Im Schlaf lässt sich nichts einrichten")
# In einen freien Slot passt jede Technik, aber nur mit einfacher Güte
var frei := -1
for nr in range(Game.slots(id).size()):
var g := Game.slot_grund(id, nr)
if g != "schlaeft" and not Ursachen.ist_blocker(g):
frei = nr
break
Game.cooldown.clear()
var pr_frei := Game.system_pruefen(id, frei, falsch)
p(pr_frei["geht"], "In einen freien Slot passt auch eine andere Technik")
p(is_equal_approx(float(pr_frei["guete"]), Game.GUETE_NEU),
"Aber nur mit einfacher Güte (%.2f)" % pr_frei["guete"])
## Je mehr Ursachen abgedeckt sind, desto schneller kommt der Traum voran.
func test_tempo_des_traums() -> void:
abschnitt("Tempo — je besser die Systeme, desto schneller")
var id := "mensch_0"
Game.active_mission = id
Game.systeme.clear()
Game.cooldown.clear()
Game.known.clear()
for t in TechDB.LIST:
Game.known[str(t["id"])] = 28
Game.erkannt_slots[id] = {}
for nr in range(Game.slots(id).size()):
Game.erkannt_slots[id][nr] = true
var urs: Array = Game.ursachen_von(id)
var leer := Game.traum_tempo(id)
p(leer < Game.BASIS_TEMPO * 0.25,
"Ohne Systeme kriecht der Traum: %.0f %% je Tag" % leer)
var gesetzt: Array = []
for u in urs:
for nr in range(Game.slots(id).size()):
if Ursachen.ursache_von(Game.slot_grund(id, nr)) != str(u):
continue
for tid in TechDB.fuer_ursache(str(u)):
var pr := Game.system_pruefen(id, nr, str(tid))
if pr["geht"]:
Game.systeme[id] = Game.systeme.get(id, {})
Game.systeme[id][nr] = {"tech": str(tid), "guete": pr["guete"]}
gesetzt.append(Game.traum_tempo(id))
break
break
p(gesetzt.size() == urs.size(), "Gegen jede Ursache steht ein System")
var steigt := true
var vor := leer
for w in gesetzt:
if float(w) <= vor:
steigt = false
vor = float(w)
p(steigt, "Jedes weitere System hebt das Tempo: %.0f -> %s" %
[leer, str(gesetzt.map(func(x): return "%.0f" % float(x)))])
p(Game.traum_tempo(id) > leer * 8.0,
"Alles gelöst ist mehr als achtmal so schnell (%.0f statt %.0f)" %
[Game.traum_tempo(id), leer])
# Die Bremsen wirken multiplikativ
Game.systeme.clear()
var keine := Game.traum_tempo(id)
var erwartet := Game.BASIS_TEMPO
for u in urs:
erwartet *= float(Ursachen.LISTE[u]["faktor"])
p(is_equal_approx(keine, erwartet),
"Zwei Bremsen bremsen doppelt: %.1f wie gerechnet" % keine)
# Fortschritt läuft über die Zeit, nicht über Einzeltreffer
Game.progress[id] = 0.0
Game.traum_voranbringen(24.0)
p(is_equal_approx(Game.mission_progress(id), keine),
"Ein ganzer Tag bringt genau ein Tagestempo (%.1f %%)" % Game.mission_progress(id))
func test_mission_abschliessen() -> void:
abschnitt("Mission abschließen")
var id := "mensch_0"
Game.active_mission = id
Game.progress[id] = 0.0
Game.completed.clear()
var tage := 0
while Game.mission_progress(id) < Game.MISSION_ZIEL and tage < 200:
Game.traum_voranbringen(24.0)
tage += 1
p(Game.completed.has(id), "Der Traum erfüllt sich nach %d Tagen" % tage)
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"])
Game.known = {str(_basiswege()[0]): 0}
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")
Game.known.clear()
Game.systeme.clear()
Game.cooldown.clear()
p(Game.learn("zwei_minuten"), "Technik gelernt")
p(Game.skill_of("zwei_minuten") == 1, "Frisch gelernt heißt Stufe 1")
var id := "mensch_0"
Game.active_mission = id
Game.erkannt_slots[id] = {}
for nr in range(Game.slots(id).size()):
Game.erkannt_slots[id][nr] = true
var frei := -1
for nr in range(Game.slots(id).size()):
var g := Game.slot_grund(id, nr)
if g != "schlaeft" and not Ursachen.ist_blocker(g):
frei = nr
break
var versuche := 0
while Game.skill_of("zwei_minuten") < 3 and versuche < 80:
Game.cooldown.clear()
Game.system_entfernen(id, frei)
Game.system_setzen(id, frei, "zwei_minuten", false)
versuche += 1
p(Game.skill_of("zwei_minuten") >= 3,
"Nach %d Einsätzen von allein auf Stufe %d" % [versuche, Game.skill_of("zwei_minuten")])
# 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.advance_time(Game.DAY_SECONDS)
p(Game.known["zwei_minuten"] == stand, "Ein ganzer Tag bringt keine Erfahrung")
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)
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)
## Nach jedem Einsatz braucht eine Technik Ruhe. Ein Weg allein deckt daher
## keinen ganzen Tag ab — das ist der Grund, mehrere zu lernen.
## Nach jedem Einsatz braucht eine Technik Ruhe. Ein Weg allein baut daher
## nicht den ganzen Tag um.
func test_cooldown() -> void:
abschnitt("Techniken haben einen Cooldown")
var id := "mensch_0"
Game.active_mission = id
Game.cooldown.clear()
Game.systeme.clear()
Game.known.clear()
for t in TechDB.LIST:
Game.known[str(t["id"])] = 28
Game.erkannt_slots[id] = {}
for nr in range(Game.slots(id).size()):
Game.erkannt_slots[id][nr] = true
var frei: Array = []
for nr in range(Game.slots(id).size()):
var g := Game.slot_grund(id, nr)
if g != "schlaeft" and not Ursachen.ist_blocker(g):
frei.append(nr)
p(Game.bereit("pomodoro"), "Frisch ist eine Technik einsatzbereit")
var erste := Game.system_setzen(id, int(frei[0]), "pomodoro", 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.system_setzen(id, int(frei[1]), "pomodoro", 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")
p(Game.system_setzen(id, int(frei[1]), "zerlegen", false)["ok"],
"Eine andere Technik greift weiter")
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")
## Wie gut etwas steht, sagt das Spiel nicht. Es zeigt Auslöser und Belohnung
## -- entscheiden muss der Spieler.
func test_chance_verdeckt() -> void:
abschnitt("Die Erfolgschance bleibt verdeckt")
var id := "mensch_0"
Game.active_mission = id
Game.cooldown.clear()
Game.systeme.clear()
Game.known.clear()
for t in TechDB.LIST:
Game.known[str(t["id"])] = 28
Game.erkannt_slots[id] = {}
for nr in range(Game.slots(id).size()):
Game.erkannt_slots[id][nr] = true
var blocker_nr := -1
for nr in range(Game.slots(id).size()):
if Ursachen.ist_blocker(Game.slot_grund(id, nr)):
blocker_nr = nr
break
ui().slot_panel(id, blocker_nr)
var wahl := _panel_text()
var knoepfe := _knopf_texte()
p(not knoepfe.contains("%"), "Kein Knopf nennt eine Prozentzahl")
p(not wahl.contains("Erfolgschance"), "Und das Wort Erfolgschance kommt nicht vor")
p(not wahl.contains("Wann:") and not wahl.contains("Zu sehen an:"),
"Die Auswahl zeigt nur Namen, keine Beschreibung")
p(wahl.contains("Auslöser") and wahl.contains("Belohnung"),
"Dafür stehen Auslöser und Belohnung des Blockers da")
ui().schliessen()
ui().systeme_panel(id)
p(not _knopf_texte().contains("%"), "Auch die Tageszeilen nennen keine Chance")
p(_panel_text().contains("je Tag"), "Das Panel nennt das Tempo des Traums")
ui().schliessen()
ui().journal_panel("techniken")
p(_knopf_texte().contains(str(TechDB.LIST[0]["name"])),
"Das Journal listet die Technik als eine Zeile")
ui().technik_panel(str(TechDB.LIST[0]["id"]))
var buch := _panel_text()
p(buch.contains("Wann:") and buch.contains("Zu sehen an:"),
"Aufgeschlagen stehen Was, Wann, Warum und Zu sehen an da")
ui().schliessen()
## 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()
## 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("Nacht"), "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)
## 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.
func _erster_traum_tage(saat: int) -> int:
seed(saat)
Game.cooldown.clear()
Game.known.clear()
Game.systeme.clear()
Game.erkannt_slots.clear()
Game.notiz_sperre = 0.0
Game.progress.clear()
Game.completed.clear()
Game.active_mission = ""
Game.welt_erzeugen()
for id in _basiswege():
Game.learn(str(id))
Game.accept_mission("mensch_0")
_alles_erkennen("mensch_0")
_bestes_system("mensch_0")
Game.progress["mensch_0"] = 0.0
Game.time_h = 7.0
Game.day = 1
for i in range(3000):
Game.advance_time(1.0)
Game.traum_voranbringen(24.0 / Game.DAY_SECONDS)
if Game.completed.has("mensch_0") or Game.day > 30:
break
return Game.day
## Deckt alle Slots eines Menschen auf -- für Messungen, nicht fürs Spiel.
func _alles_erkennen(id: String) -> void:
Game.erkannt_slots[id] = {}
for nr in range(Game.slots(id).size()):
Game.erkannt_slots[id][nr] = true
## Setzt gegen jede Ursache das beste verfügbare System.
func _bestes_system(id: String) -> int:
var n := 0
for u in Game.ursachen_von(id):
var bester := ""
var beste := 0.0
var beste_nr := -1
for nr in range(Game.slots(id).size()):
for tid in TechDB.fuer_ursache(str(u)):
if not Game.knows(str(tid)):
continue
Game.cooldown.clear()
var pr := Game.system_pruefen(id, nr, str(tid))
if pr["geht"] and float(pr["guete"]) > beste:
beste = float(pr["guete"])
bester = str(tid)
beste_nr = nr
if bester != "":
Game.systeme[id] = Game.systeme.get(id, {})
Game.systeme[id][beste_nr] = {"tech": bester, "guete": beste}
n += 1
return n
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.system_anzahl("mensch_0") == 1,
"Damit steht genau ein System (%d)" % Game.system_anzahl("mensch_0"))
var schlimmster: int = tage.max()
var bester: int = tage.min()
p(schlimmster <= 12, "Der Traum erfüllt sich in allen %d Läufen (%s)" %
[tage.size(), str(tage)])
p(bester >= 2, "Nie geschenkt — mindestens %d Tage" % bester)
p(schlimmster <= 8, "Nie zäh — höchstens %d Tage" % schlimmster)
p(Game.offene_techniken() > 1,
"Und öffnet neue Wege: %d entdeckt" % Game.offene_techniken())
## Die Probe auf den Kern: wer die Ursache trifft und die Belohnung hält,
## kommt schneller ans Ziel als wer irgendwo irgendwas einbaut.
func test_lesen_lohnt_sich() -> void:
abschnitt("Die Technik zu lesen zahlt sich aus")
var id := "mensch_0"
Game.known.clear()
for t in TechDB.LIST:
Game.known[str(t["id"])] = 0
Game.active_mission = id
_alles_erkennen(id)
Game.systeme.clear()
var n := _bestes_system(id)
var gezielt := Game.traum_tempo(id)
# blind: dieselbe Zahl Systeme, aber in freie Slots gesetzt
Game.systeme.clear()
var frei: Array = []
for nr in range(Game.slots(id).size()):
var g := Game.slot_grund(id, nr)
if g != "schlaeft" and not Ursachen.ist_blocker(g):
frei.append(nr)
var gesetzt := 0
for tid in TechDB.ids():
if gesetzt >= n or gesetzt >= frei.size():
break
Game.cooldown.clear()
var pr := Game.system_pruefen(id, int(frei[gesetzt]), str(tid))
if pr["geht"]:
Game.systeme[id] = Game.systeme.get(id, {})
Game.systeme[id][int(frei[gesetzt])] = {"tech": str(tid), "guete": pr["guete"]}
gesetzt += 1
var blind := Game.traum_tempo(id)
p(gezielt > blind, "Gezielt gesetzt läuft der Traum schneller: %.0f statt %.0f %% je Tag" %
[gezielt, blind])
# Der Grund: die Belohnung entscheidet über die Güte
Game.systeme.clear()
var blocker_nr := -1
for nr in range(Game.slots(id).size()):
if Ursachen.ist_blocker(Game.slot_grund(id, nr)):
blocker_nr = nr
break
var b: Dictionary = Ursachen.BLOCKER[Game.slot_grund(id, blocker_nr)]
var gleich := ""
var anders := ""
for tid in TechDB.fuer_ursache(str(b["ursache"])):
if str(TechDB.by_id(str(tid))["belohnung"]) == str(b["belohnung"]):
gleich = str(tid)
elif not TechDB.entfernt_ausloeser(str(tid)):
anders = str(tid)
if gleich != "" and anders != "":
var g1: float = float(Game.system_pruefen(id, blocker_nr, gleich)["guete"])
var g2: float = float(Game.system_pruefen(id, blocker_nr, anders)["guete"])
p(g1 > g2, "Gleiche Belohnung hält besser: Güte %.2f statt %.2f" % [g1, g2])
p(Game.system_pruefen(id, blocker_nr, "schlaffenster")["geht"]
== (str(TechDB.by_id("schlaffenster")["ursache"]) == str(b["ursache"])),
"Eine Technik gegen eine andere Ursache greift hier gar nicht")