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