update
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# Tests
|
||||
|
||||
make test
|
||||
make test # alle Prüfungen, etwa 30 Sekunden
|
||||
make sim # Bot gegen Bot, je Stufe 20 Partien: Runden und Siegquoten
|
||||
|
||||
Läuft alles headless: Logik, Nochmal, Bot-Partie (Stufe schwer, 8×), und
|
||||
eine echte Mehrspieler-Partie mit Server, Host-Client und Gast-Client - der
|
||||
@@ -11,6 +12,10 @@ Fenster.
|
||||
|
||||
## Fallen
|
||||
|
||||
* **Kein `Engine.time_scale`.** Er staucht auch den Physikschritt; ab etwa 6×
|
||||
tunneln die Würfel durch den Filz und fallen ewig. Schnell wird es über
|
||||
`game.tempo = 0` - das streicht nur die Wartezeiten des Ablaufs.
|
||||
|
||||
* **Wurzelname.** Der Spielknoten muss auf Server und Client denselben Pfad
|
||||
haben (`/root/Main/Raeume/<CODE>/…`). Daran hängt jeder RPC. Die
|
||||
Mehrspieler-Tests laden deshalb die echte Hauptszene als aktuelle Szene und
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
extends Node
|
||||
## Gemeinsame Helfer für die Testszenen. Jede Testszene erbt hiervon.
|
||||
##
|
||||
## Kein Engine.time_scale: der staucht auch den Physikschritt. Ab etwa 6x
|
||||
## tunneln die Würfel durch den Filz und fallen ewig - die Partie hängt im
|
||||
## Würfeln. Schnell wird es über `game.tempo = 0`, das nur die Wartezeiten
|
||||
## des Ablaufs streicht.
|
||||
|
||||
var fehler := 0
|
||||
|
||||
|
||||
@@ -1,29 +1,32 @@
|
||||
extends "res://tests/basis.gd"
|
||||
## Alle Plätze sind Bots, achtfacher Zeitraffer. Läuft die Partie fehlerfrei
|
||||
## bis zum Ende? Stufe über "-- 0|1|2".
|
||||
## Alle Plätze sind Bots, ohne Wartezeiten, drei Plätze mit je drei Würfeln:
|
||||
## eine ganze Partie in Sekunden. Stufe über "-- 0|1|2".
|
||||
|
||||
var game: Node
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
Engine.time_scale = 12.0
|
||||
add_child(preload("res://scenes/main.tscn").instantiate())
|
||||
await get_tree().process_frame
|
||||
var args := OS.get_cmdline_user_args()
|
||||
Net.start_singleplayer("Tester", 3, int(args[0]) if args.size() > 0 else 1)
|
||||
Net.start_singleplayer("Tester", 2, int(args[0]) if args.size() > 0 else 1, 3)
|
||||
_lauf()
|
||||
|
||||
|
||||
func _lauf() -> void:
|
||||
await _warte(func(): game = _find(get_tree().root, "Game"); return game != null, 20.0)
|
||||
game.tempo = 0.0
|
||||
game.seats[game.local_seat].ai = true
|
||||
# Volle Vorräte: so wirft jeder Bot sicher, und der Topf kommt in Bewegung.
|
||||
for i in game.seats.size():
|
||||
game.hand[i] = ["peek", "gag", "count", "bid_up"]
|
||||
var t := 0.0
|
||||
var gesehen := {}
|
||||
var zaehler := {"Genau": 0, "Topf": 0, " wirft ": 0, "vom Tisch": 0}
|
||||
var runden := 0
|
||||
var letzte := ""
|
||||
var naechste_meldung := 500.0
|
||||
while game.phase != "over" and t < 6000.0:
|
||||
while game.phase != "over" and t < 600.0:
|
||||
await get_tree().create_timer(0.2).timeout
|
||||
t += 0.2
|
||||
if game.phase != letzte:
|
||||
@@ -40,6 +43,20 @@ func _lauf() -> void:
|
||||
for k in zaehler:
|
||||
if k in z:
|
||||
zaehler[k] += 1
|
||||
if game.phase != "over":
|
||||
# Wo hängt es? Alles ausgeben, was den Ablauf treibt.
|
||||
print(" STAND phase=%s turn=%d bid=%s must_bid=%s muenze_laeuft=%s answered=%s" % [
|
||||
game.phase, game.turn, game.bid, game.must_bid, game._muenze_laeuft, game._answered.keys()])
|
||||
for i in game.seats.size():
|
||||
print(" Platz %d %s count=%d alive=%s ai=%s hand=%s" % [i, game.seats[i].name,
|
||||
game.seats[i].count, game.seats[i].alive, game.seats[i].ai, game.hand.get(i, [])])
|
||||
for z in game.log_lines:
|
||||
print(" LOG " + z)
|
||||
print(" paused=%s stream=%s" % [get_tree().paused, game._stream])
|
||||
for i in game.seats.size():
|
||||
for d in game.table.dice_of(i):
|
||||
print(" Wuerfel Platz %d: pos=%s sleeping=%s freeze=%s settled=%s flat=%.2f" % [
|
||||
i, d.global_position, d.sleeping, d.freeze, d.is_settled(), d.flatness()])
|
||||
_pruef("Partie endet (%s, Stufe %d, %d Runden, %.0fs Spielzeit)" % [game.banner, DiceAI.stufe, runden, t], game.phase == "over")
|
||||
_pruef("Münzen wurden geworfen (%d)" % zaehler[" wirft "], zaehler[" wirft "] > 0)
|
||||
_pruef("Topf wurde vergeben (%d)" % zaehler["Topf"], zaehler["Topf"] > 0)
|
||||
|
||||
62
tests/genau.gd
Normal file
62
tests/genau.gd
Normal file
@@ -0,0 +1,62 @@
|
||||
extends "res://tests/basis.gd"
|
||||
## Genau und Aufdecken in Etappen. Eigene Szene, damit der Ablauf innerhalb
|
||||
## der 30 s Zugzeit bleibt - danach spielt der Host den Platz automatisch.
|
||||
|
||||
var game: Node
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
add_child(preload("res://scenes/main.tscn").instantiate())
|
||||
await get_tree().process_frame
|
||||
Net.start_singleplayer("Tester", 3, 1)
|
||||
_lauf()
|
||||
|
||||
|
||||
func _menschen() -> void:
|
||||
for i in [1, 2, 3]:
|
||||
game.seats[i].ai = false
|
||||
|
||||
|
||||
func _lauf() -> void:
|
||||
await _warte(func(): game = _find(get_tree().root, "Game"); return game != null, 20.0)
|
||||
game.tempo = 0.3
|
||||
await _warte(func(): return game.phase == "bid", 40.0)
|
||||
var me: int = game.local_seat
|
||||
_menschen()
|
||||
game.must_bid.clear()
|
||||
# --- Genau-Treffer: Ansager verliert, Rufer bekommt einen
|
||||
game.must_bid.clear()
|
||||
var n3: int = game._count_face(3)
|
||||
if n3 == 0:
|
||||
game._set_die(me, 0, 3)
|
||||
n3 = 1
|
||||
var vorher: Array = []
|
||||
for i in 4:
|
||||
vorher.append(int(game.seats[i].count))
|
||||
game._do_bid(me, n3, 3)
|
||||
await _warte(func(): return game.turn == 1, 5.0)
|
||||
game._do_exact(1)
|
||||
await _warte(func(): return game.phase == "reveal", 5.0)
|
||||
_pruef("Etappe 1: noch nicht gezählt", int(game.reveal_total) == -1)
|
||||
await _warte(func(): return int(game.reveal_total) >= 0, 3.0)
|
||||
_pruef("Etappe 2: gezählt", int(game.reveal_total) == n3)
|
||||
await _warte(func(): return game.banner.begins_with("Genau!"), 3.0)
|
||||
await _warte(func(): return game.phase != "reveal", 8.0)
|
||||
_pruef("Genau: Ansager -1, Rufer +1, Rest gleich",
|
||||
int(game.seats[me].count) == vorher[0] - 1 and int(game.seats[1].count) == vorher[1] + 1
|
||||
and int(game.seats[2].count) == vorher[2] and int(game.seats[3].count) == vorher[3])
|
||||
_pruef("Rufer beginnt", game.starter == 1)
|
||||
|
||||
# --- Genau daneben
|
||||
# Erst wenn die neue Ansagerunde wirklich läuft: Platz 1 ist dran und
|
||||
# noch unbeantwortet. Ein "turn == 1" allein gilt schon beim Würfeln.
|
||||
await _warte(func(): return game.phase == "bid" and game.turn == 1 and game._answered.is_empty(), 30.0)
|
||||
var n5: int = game._count_face(5)
|
||||
game._do_bid(1, mini(n5 + 2, game._total_dice()), 5)
|
||||
await _warte(func(): return game.turn == 2 and game._answered.is_empty(), 5.0)
|
||||
var c2: int = game.seats[2].count
|
||||
game._do_exact(2)
|
||||
await _warte(func(): return game.banner.begins_with("Daneben"), 8.0)
|
||||
await _warte(func(): return game.phase != "reveal", 8.0)
|
||||
_pruef("Genau daneben: nur der Rufer verliert", int(game.seats[2].count) == c2 - 1)
|
||||
_fertig()
|
||||
6
tests/genau.tscn
Normal file
6
tests/genau.tscn
Normal file
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tests/genau.gd" id="1"]
|
||||
|
||||
[node name="Test" type="Node"]
|
||||
script = ExtResource("1")
|
||||
@@ -1,7 +1,9 @@
|
||||
extends "res://tests/basis.gd"
|
||||
## Solopartie headless: Münzen, Genau, Topf, Zählen, Stufe, Tipp-Schranke.
|
||||
## Solopartie headless: Münzen, Topf, Zählen, Stufe, Tipp-Schranke.
|
||||
## Die Bots werden zu "Menschen" ohne Peer gemacht - dann wartet der Host
|
||||
## auf unsere Eingaben, und der Ablauf ist steuerbar.
|
||||
## auf unsere Eingaben, und der Ablauf ist steuerbar. Alles, was den eigenen
|
||||
## Zug braucht, kommt zuerst: nach 30 s Spielzeit spielt der Host den Platz
|
||||
## sonst automatisch. Genau und Aufdecken prüft tests/genau.gd.
|
||||
|
||||
var game: Node
|
||||
|
||||
@@ -20,6 +22,8 @@ func _menschen() -> void:
|
||||
|
||||
func _lauf() -> void:
|
||||
await _warte(func(): game = _find(get_tree().root, "Game"); return game != null, 20.0)
|
||||
# Nicht null: die Etappen beim Aufdecken sollen noch einzeln sichtbar sein.
|
||||
game.tempo = 0.3
|
||||
await _warte(func(): return game.phase == "bid", 40.0)
|
||||
var me: int = game.local_seat
|
||||
var hud: Node = game.hud
|
||||
@@ -35,6 +39,20 @@ func _lauf() -> void:
|
||||
await game._do_play(me, "count", 0)
|
||||
_pruef("Zählen ohne Ansage abgelehnt", game.hand[me].has("count"))
|
||||
|
||||
# --- Topf: Deckel und Übertrag
|
||||
game.topf.clear()
|
||||
game.hand[me] = ["gag", "gag", "gag", "gag", "gag", "gag", "gag"]
|
||||
for i in 7:
|
||||
await game._do_play(me, "gag", 0)
|
||||
_pruef("Topf höchstens %d" % BoosterDB.TOPF_MAX, game.topf.size() == BoosterDB.TOPF_MAX)
|
||||
_pruef("Rausfallen steht im Protokoll", game.log_lines.any(func(z): return "vom Tisch" in z))
|
||||
game.hand[2] = ["peek", "peek"]
|
||||
game._topf_an(2)
|
||||
_pruef("Gewinner nimmt bis zum Limit, Rest bleibt",
|
||||
game.hand[2].size() == BoosterDB.HANDLIMIT and game.topf.size() == BoosterDB.TOPF_MAX - 2)
|
||||
game.topf.clear()
|
||||
game.must_bid.clear()
|
||||
|
||||
# --- Wirkungen beider Seiten
|
||||
game.bid = {"seat": 1, "count": 1, "face": 4}
|
||||
var n4: int = game._count_face(4)
|
||||
@@ -57,19 +75,15 @@ func _lauf() -> void:
|
||||
game.bid = {}
|
||||
game.must_bid.clear()
|
||||
|
||||
# --- Topf: Deckel und Übertrag
|
||||
game.topf.clear()
|
||||
game.hand[me] = ["gag", "gag", "gag", "gag", "gag", "gag", "gag"]
|
||||
for i in 7:
|
||||
await game._do_play(me, "gag", 0)
|
||||
_pruef("Topf höchstens %d" % BoosterDB.TOPF_MAX, game.topf.size() == BoosterDB.TOPF_MAX)
|
||||
_pruef("Rausfallen steht im Protokoll", game.log_lines.any(func(z): return "vom Tisch" in z))
|
||||
game.hand[2] = ["peek", "peek"]
|
||||
game._topf_an(2)
|
||||
_pruef("Gewinner nimmt bis zum Limit, Rest bleibt",
|
||||
game.hand[2].size() == BoosterDB.HANDLIMIT and game.topf.size() == BoosterDB.TOPF_MAX - 2)
|
||||
game.topf.clear()
|
||||
game.must_bid.clear()
|
||||
# --- Fangnetz: ein Würfel unter dem Tisch kommt zurück und kommt zur Ruhe
|
||||
var d0 = game.table.dice_of(me)[0]
|
||||
d0.freeze = false
|
||||
d0.place(Transform3D(Basis(), Vector3(0, -50.0, 0)))
|
||||
_pruef("Fangnetz holt den Würfel zurück", game.table.fange_verirrte(me) and d0.global_position.y > 0.0)
|
||||
d0.place(Transform3D(Basis(), Vector3(0, -50.0, 0)))
|
||||
await game._neu_werfen(me, [0])
|
||||
_pruef("Würfeln endet trotz verirrtem Würfel, Wert gelesen",
|
||||
d0.global_position.y > 0.5 and game.host_values[me][0] >= 1 and game.host_values[me][0] <= 6)
|
||||
|
||||
# --- Protokoll und Tipp-Schranke im HUD
|
||||
game._push_state()
|
||||
@@ -88,39 +102,4 @@ func _lauf() -> void:
|
||||
hud._touch = false
|
||||
await get_tree().create_timer(2.5).timeout
|
||||
|
||||
# --- Genau-Treffer: Ansager verliert, Rufer bekommt einen
|
||||
game.must_bid.clear()
|
||||
var n3: int = game._count_face(3)
|
||||
if n3 == 0:
|
||||
game._set_die(me, 0, 3)
|
||||
n3 = 1
|
||||
var vorher: Array = []
|
||||
for i in 4:
|
||||
vorher.append(int(game.seats[i].count))
|
||||
game._do_bid(me, n3, 3)
|
||||
await _warte(func(): return game.turn == 1, 5.0)
|
||||
game._do_exact(1)
|
||||
await _warte(func(): return game.phase == "reveal", 5.0)
|
||||
_pruef("Etappe 1: noch nicht gezählt", int(game.reveal_total) == -1)
|
||||
await _warte(func(): return int(game.reveal_total) >= 0, 3.0)
|
||||
_pruef("Etappe 2: gezählt", int(game.reveal_total) == n3)
|
||||
await _warte(func(): return game.banner.begins_with("Genau!"), 3.0)
|
||||
await _warte(func(): return game.phase != "reveal", 8.0)
|
||||
_pruef("Genau: Ansager -1, Rufer +1, Rest gleich",
|
||||
int(game.seats[me].count) == vorher[0] - 1 and int(game.seats[1].count) == vorher[1] + 1
|
||||
and int(game.seats[2].count) == vorher[2] and int(game.seats[3].count) == vorher[3])
|
||||
_pruef("Rufer beginnt", game.starter == 1)
|
||||
|
||||
# --- Genau daneben
|
||||
await _warte(func(): return game.phase == "bid", 20.0)
|
||||
_menschen()
|
||||
await _warte(func(): return game.turn == 1, 5.0)
|
||||
var n5: int = game._count_face(5)
|
||||
game._do_bid(1, mini(n5 + 2, game._total_dice()), 5)
|
||||
await _warte(func(): return game.turn == 2, 5.0)
|
||||
var c2: int = game.seats[2].count
|
||||
game._do_exact(2)
|
||||
await _warte(func(): return game.banner.begins_with("Daneben"), 8.0)
|
||||
await _warte(func(): return game.phase != "reveal", 8.0)
|
||||
_pruef("Genau daneben: nur der Rufer verliert", int(game.seats[2].count) == c2 - 1)
|
||||
_fertig()
|
||||
|
||||
@@ -16,7 +16,6 @@ var _code_datei := ""
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
Engine.time_scale = 8.0
|
||||
_rng.randomize()
|
||||
var args := OS.get_cmdline_user_args()
|
||||
rolle = args[0]
|
||||
@@ -33,7 +32,7 @@ func _lauf() -> void:
|
||||
f.store_string(Net.room_code)
|
||||
f.close()
|
||||
await _warte(func(): return Net.lobby.size() >= 2, 80.0)
|
||||
Net.request_start(1, 1)
|
||||
Net.request_start(1, 1, 2)
|
||||
else:
|
||||
await _beitreten()
|
||||
await _warte(func(): game = _find(get_tree().root, "Game"); return game != null, 120.0)
|
||||
@@ -46,9 +45,9 @@ func _lauf() -> void:
|
||||
|
||||
var t := 0.0
|
||||
var gewartet := 0.0
|
||||
while t < 4400.0 and is_instance_valid(game) and game.phase != "over":
|
||||
await get_tree().create_timer(0.2).timeout
|
||||
t += 0.2
|
||||
while t < 1400.0 and is_instance_valid(game) and game.phase != "over":
|
||||
await get_tree().create_timer(0.1).timeout
|
||||
t += 0.1
|
||||
if not is_instance_valid(game):
|
||||
break
|
||||
if game.phase != _letzte_phase:
|
||||
@@ -63,14 +62,14 @@ func _lauf() -> void:
|
||||
if game.phase != "bid" or game.turn != me or not game.seats[me].alive:
|
||||
gewartet = 0.0
|
||||
continue
|
||||
gewartet += 0.2
|
||||
if gewartet < 0.6:
|
||||
gewartet += 0.1
|
||||
if gewartet < 0.2:
|
||||
continue
|
||||
var hand: Array = game.hand.get(me, [])
|
||||
if not hand.is_empty() and (not BoosterDB.needs_bid(hand[0]) or not game.bid.is_empty()) and _rng.randf() < 0.7:
|
||||
game._on_booster_chosen(hand[0], 0)
|
||||
wuerfe += 1
|
||||
await get_tree().create_timer(2.5).timeout
|
||||
await get_tree().create_timer(0.4).timeout
|
||||
if not is_instance_valid(game) or game.turn != me or game.phase != "bid":
|
||||
continue
|
||||
var total := 0
|
||||
@@ -88,11 +87,22 @@ func _lauf() -> void:
|
||||
game._on_exact_pressed()
|
||||
genau += 1
|
||||
zuege += 1
|
||||
gewartet = -2.0
|
||||
gewartet = -1.5
|
||||
_pruef("%s: Partie endet über RPC (Runden=%d Züge=%d Würfe=%d Genau=%d)" % [rolle, runden, zuege, wuerfe, genau],
|
||||
is_instance_valid(game) and game.phase == "over")
|
||||
if rolle == "join":
|
||||
_pruef("join: Wiedereinstieg hat geklappt", wieder_da)
|
||||
# Nochmal: der Besitzer bittet darum, beide bekommen eine frische Partie.
|
||||
var alt: Node = game
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
if Net.is_owner:
|
||||
Net.request_rematch()
|
||||
var neu := await _warte(func(): game = _find(get_tree().root, "Game"); return game != null and game != alt, 20.0)
|
||||
var laeuft := false
|
||||
if neu:
|
||||
laeuft = await _warte(func(): return game.phase == "bid" or game.phase == "roll", 20.0)
|
||||
_pruef("%s: Nochmal im Netz bringt eine neue Partie (Platz %d)" % [rolle, game.local_seat if neu else -1],
|
||||
neu and laeuft and game.local_seat >= 0)
|
||||
if is_instance_valid(game):
|
||||
print("%s: '%s'" % [rolle, game.banner])
|
||||
for i in game.seats.size():
|
||||
@@ -106,15 +116,19 @@ func _beitreten() -> void:
|
||||
Net.join_room(FileAccess.get_file_as_string(_code_datei).strip_edges(), "Gast")
|
||||
|
||||
|
||||
## Verbindung kappen, frisches Menü, mit demselben Namen wieder beitreten.
|
||||
## Verbindung reisst ab (kein bewusstes Verlassen), frisches Menü, dann der
|
||||
## Weg des Knopfs "Zurück in Raum XY".
|
||||
func _wiederkommen() -> void:
|
||||
var alter_platz: int = game.local_seat
|
||||
var alte_zuege := zuege
|
||||
Net.leave()
|
||||
get_tree().change_scene_to_file("res://scenes/main.tscn")
|
||||
await get_tree().create_timer(1.5).timeout
|
||||
var code := Net.room_code
|
||||
multiplayer.multiplayer_peer.close()
|
||||
if Net.in_game:
|
||||
Net._on_server_disconnected() # der Verlust-Handler, wie bei echtem Abbruch
|
||||
await get_tree().create_timer(1.0).timeout
|
||||
_pruef("join: Raum nach Abbruch gemerkt", Net.letzter_raum.get("code", "") == code)
|
||||
game = null
|
||||
await _beitreten()
|
||||
Net.wieder_rein()
|
||||
var ok := await _warte(func(): game = _find(get_tree().root, "Game"); return game != null, 60.0)
|
||||
if not ok:
|
||||
_pruef("join: nach Rückkehr kommt ein Spiel", false)
|
||||
|
||||
@@ -6,27 +6,34 @@ var game: Node
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
Engine.time_scale = 8.0
|
||||
var t := 0.0
|
||||
var runden := 0
|
||||
var letzte := ""
|
||||
while t < 4500.0:
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
t += 0.5
|
||||
var zuletzt := "" # letzter gesehener Stand - die Clients gehen sofort,
|
||||
# dann räumt der Server den Raum ab, bevor wir gucken
|
||||
while t < 1500.0:
|
||||
await get_tree().create_timer(0.2).timeout
|
||||
t += 0.2
|
||||
if game == null:
|
||||
game = _find(get_tree().root, "Game")
|
||||
if game != null:
|
||||
game.tempo = 0.0
|
||||
continue
|
||||
if not is_instance_valid(game) or game.phase == "over":
|
||||
if not is_instance_valid(game):
|
||||
break
|
||||
if game.phase != letzte:
|
||||
letzte = game.phase
|
||||
if letzte == "roll":
|
||||
runden += 1
|
||||
if is_instance_valid(game):
|
||||
print("SERVER: Ende %s '%s', Runden=%d" % [game.phase, game.banner, runden])
|
||||
for i in game.seats.size():
|
||||
print("SERVER: Platz %d %s count=%d alive=%s" % [i, game.seats[i].name, game.seats[i].count, game.seats[i].alive])
|
||||
var zurueck: bool = game.log_lines.any(func(z): return "ist zurück" in z)
|
||||
print("SERVER: Rückkehr im Protokoll=%s" % zurueck)
|
||||
await get_tree().create_timer(80.0).timeout
|
||||
var staende: Array = game.seats.map(func(x): return "%s=%d" % [x.name, x.count])
|
||||
zuletzt = "%s '%s' Runden=%d %s" % [game.phase, game.banner, runden, staende]
|
||||
if game.phase == "over":
|
||||
break
|
||||
print("SERVER: Ende %s" % zuletzt)
|
||||
var alt: Node = game
|
||||
var neu := await _warte(func(): game = _find(get_tree().root, "Game"); return game != null and game != alt, 15.0)
|
||||
if neu:
|
||||
game.tempo = 0.0
|
||||
print("SERVER: Nochmal-Partie aufgebaut=%s" % neu)
|
||||
await get_tree().create_timer(15.0).timeout
|
||||
get_tree().quit()
|
||||
|
||||
@@ -9,6 +9,7 @@ func _ready() -> void:
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
Net.start_singleplayer("Tester", 2, 0)
|
||||
await _warte(func(): game = _find(get_tree().root, "Game"); return game != null, 20.0)
|
||||
game.tempo = 0.0
|
||||
await _warte(func(): return game.phase == "bid", 40.0)
|
||||
_pruef("Erste Partie läuft, Stufe leicht", DiceAI.stufe == 0 and game.seats.size() == 3)
|
||||
game._on_rematch()
|
||||
|
||||
87
tests/run.sh
87
tests/run.sh
@@ -1,51 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
# Alle headless Tests. Aufruf: make test (oder: bash tests/run.sh)
|
||||
# Der Mehrspieler-Test braucht einen freien Port; 8000 ist auf Rechnern mit
|
||||
# laufendem Entwicklungsserver oft belegt, deshalb 8765.
|
||||
# Alle headless Tests, parallel. Aufruf: make test (oder: bash tests/run.sh)
|
||||
# Ein Test fällt sofort, wenn ein Skriptfehler im Log auftaucht - nicht erst
|
||||
# nach dem Timeout. Der Mehrspieler-Test braucht einen freien Port; 8000 ist
|
||||
# auf Rechnern mit laufendem Entwicklungsserver oft belegt, deshalb 8765.
|
||||
set -u
|
||||
GODOT="${GODOT:-$HOME/bin/godot}"
|
||||
PORT="${PORT:-8765}"
|
||||
# Ein Rest vom letzten Lauf oder ein fremder Dienst darf den Test nicht
|
||||
# still auf einen falschen Server schicken: freien Port suchen.
|
||||
while ss -ltn 2>/dev/null | grep -q ":$PORT "; do PORT=$((PORT + 1)); done
|
||||
TMP="$(mktemp -d)"
|
||||
cd "$(dirname "$0")/.."
|
||||
ergebnis=0
|
||||
start=$(date +%s)
|
||||
|
||||
lauf() { # name, timeout, argumente...
|
||||
declare -A PID
|
||||
starte() { # name, timeout, argumente... -> Hintergrund
|
||||
local name="$1"; local zeit="$2"; shift 2
|
||||
echo "=== $name"
|
||||
timeout "$zeit" "$GODOT" --headless --path . "res://tests/$name.tscn" -- "$@" > "$TMP/$name.log" 2>&1
|
||||
local rc=$?
|
||||
grep -E "^(OK |FEHL| |TEST:)" "$TMP/$name.log"
|
||||
local fehler; fehler=$(grep -c "SCRIPT ERROR" "$TMP/$name.log")
|
||||
[ "$fehler" -gt 0 ] && { echo " $fehler Skriptfehler im Log: $TMP/$name.log"; rc=1; }
|
||||
[ $rc -ne 0 ] && ergebnis=1
|
||||
timeout "$zeit" "$GODOT" --headless --path . "res://tests/${name%%:*}.tscn" -- "$@" > "$TMP/$name.log" 2>&1 &
|
||||
PID[$name]=$!
|
||||
}
|
||||
|
||||
lauf logik 200
|
||||
lauf nochmal 120
|
||||
lauf bots 900 2
|
||||
|
||||
echo "=== mehrspieler"
|
||||
starte logik 90
|
||||
starte genau 90
|
||||
starte nochmal 60
|
||||
starte bots 90 2
|
||||
rm -f "$TMP/code.txt"
|
||||
timeout 700 "$GODOT" --headless --path . res://tests/mp_start.tscn -- server "$PORT" > "$TMP/srv.log" 2>&1 &
|
||||
SRV=$!
|
||||
sleep 3
|
||||
PIRATE_DICE_SERVER="ws://127.0.0.1:$PORT" timeout 680 "$GODOT" --headless --path . res://tests/mp_start.tscn -- host "$TMP/code.txt" > "$TMP/host.log" 2>&1 &
|
||||
H=$!
|
||||
starte mp_start:srv 200 server "$PORT"
|
||||
sleep 2
|
||||
PIRATE_DICE_SERVER="ws://127.0.0.1:$PORT" timeout 680 "$GODOT" --headless --path . res://tests/mp_start.tscn -- join "$TMP/code.txt" > "$TMP/join.log" 2>&1 &
|
||||
J=$!
|
||||
wait $H; rc_h=$?
|
||||
wait $J; rc_j=$?
|
||||
# Der Server beendet sich nach dem Ende selbst. Ihn abzuschiessen würde
|
||||
# seine letzten Zeilen verlieren - Ausgabe in eine Datei ist gepuffert.
|
||||
for _ in $(seq 1 40); do kill -0 $SRV 2>/dev/null || break; sleep 1; done
|
||||
kill $SRV 2>/dev/null; wait $SRV 2>/dev/null
|
||||
grep -E "^(OK |FEHL|SERVER:|TEST:)" "$TMP/host.log" "$TMP/join.log" "$TMP/srv.log" | sed 's|^.*/||'
|
||||
pfad=$(cat "$TMP/host.log" "$TMP/join.log" "$TMP/srv.log" | grep -c "Failed to get path")
|
||||
fehler=$(cat "$TMP/host.log" "$TMP/join.log" "$TMP/srv.log" | grep -c "SCRIPT ERROR")
|
||||
echo " RPC-Pfadfehler=$pfad Skriptfehler=$fehler (Logs: $TMP)"
|
||||
[ $rc_h -ne 0 ] || [ $rc_j -ne 0 ] || [ "$pfad" -gt 0 ] || [ "$fehler" -gt 0 ] && ergebnis=1
|
||||
PIRATE_DICE_SERVER="ws://127.0.0.1:$PORT" starte mp_start:host 190 host "$TMP/code.txt"
|
||||
sleep 1
|
||||
PIRATE_DICE_SERVER="ws://127.0.0.1:$PORT" starte mp_start:join 190 join "$TMP/code.txt"
|
||||
|
||||
# Warten, aber bei Skriptfehlern sofort abbrechen.
|
||||
while :; do
|
||||
offen=0
|
||||
for name in "${!PID[@]}"; do
|
||||
pid=${PID[$name]}
|
||||
kill -0 "$pid" 2>/dev/null || continue
|
||||
if grep -q "SCRIPT ERROR" "$TMP/$name.log" 2>/dev/null; then
|
||||
kill "$pid" 2>/dev/null
|
||||
continue
|
||||
fi
|
||||
offen=$((offen + 1))
|
||||
done
|
||||
[ "$offen" -eq 0 ] && break
|
||||
sleep 1
|
||||
done
|
||||
wait 2>/dev/null
|
||||
|
||||
ergebnis=0
|
||||
for name in logik genau nochmal bots mp_start:host mp_start:join mp_start:srv; do
|
||||
log="$TMP/$name.log"
|
||||
echo "=== $name"
|
||||
grep -E "^(OK |FEHL|SERVER:| |TEST:)" "$log"
|
||||
fehler=$(grep -c "SCRIPT ERROR" "$log")
|
||||
[ "$fehler" -gt 0 ] && { echo " $fehler Skriptfehler:"; grep -A2 "SCRIPT ERROR" "$log" | head -6; ergebnis=1; }
|
||||
if [ "$name" != "mp_start:srv" ] && ! grep -q "TEST: fertig, Fehler = 0" "$log"; then ergebnis=1; fi
|
||||
done
|
||||
pfad=$(cat "$TMP"/mp_start:*.log | grep -c "Failed to get path")
|
||||
[ "$pfad" -gt 0 ] && { echo " RPC-Pfadfehler: $pfad"; ergebnis=1; }
|
||||
|
||||
echo
|
||||
[ $ergebnis -eq 0 ] && echo "ALLE TESTS GRÜN" || echo "TESTS ROT (Logs: $TMP)"
|
||||
echo "Dauer: $(( $(date +%s) - start ))s Logs: $TMP"
|
||||
[ $ergebnis -eq 0 ] && echo "ALLE TESTS GRÜN" || echo "TESTS ROT"
|
||||
exit $ergebnis
|
||||
|
||||
71
tests/sim.gd
Normal file
71
tests/sim.gd
Normal file
@@ -0,0 +1,71 @@
|
||||
extends "res://tests/basis.gd"
|
||||
## Bot gegen Bot, viele Partien hintereinander: Rundenzahl und Siegquoten je
|
||||
## Platz. Kein Test, ein Messgerät für die Regler in ai.gd.
|
||||
## Aufruf: make sim (oder einzeln: -- <stufe> <partien>)
|
||||
|
||||
var game: Node
|
||||
var stufe := 1
|
||||
var partien := 20
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
if args.size() > 0:
|
||||
stufe = int(args[0])
|
||||
if args.size() > 1:
|
||||
partien = int(args[1])
|
||||
add_child(preload("res://scenes/main.tscn").instantiate())
|
||||
await get_tree().process_frame
|
||||
_lauf()
|
||||
|
||||
|
||||
func _lauf() -> void:
|
||||
var runden_summe := 0
|
||||
var siege := {}
|
||||
var genau := 0
|
||||
var wuerfe := 0
|
||||
var laengste := 0
|
||||
for n in partien:
|
||||
Net.start_singleplayer("Tester", 2, stufe, 3)
|
||||
await _warte(func(): game = _find(get_tree().root, "Game"); return game != null and game.phase != "lobby", 20.0)
|
||||
game.tempo = 0.0
|
||||
game.seats[game.local_seat].ai = true
|
||||
var runden := 0
|
||||
var letzte := ""
|
||||
var gesehen := {}
|
||||
var t := 0.0
|
||||
while game.phase != "over" and t < 300.0:
|
||||
await get_tree().create_timer(0.1).timeout
|
||||
t += 0.1
|
||||
if game.phase != letzte:
|
||||
letzte = game.phase
|
||||
if letzte == "roll":
|
||||
runden += 1
|
||||
for z in game.log_lines:
|
||||
if gesehen.has(z):
|
||||
continue
|
||||
gesehen[z] = true
|
||||
if "Genau" in z:
|
||||
genau += 1
|
||||
if " wirft " in z:
|
||||
wuerfe += 1
|
||||
runden_summe += runden
|
||||
laengste = maxi(laengste, runden)
|
||||
var sieger := ""
|
||||
for s in game.seats:
|
||||
if s.alive:
|
||||
sieger = String(s.name)
|
||||
siege[sieger] = int(siege.get(sieger, 0)) + 1
|
||||
# Aufräumen für die nächste Partie: wie "Nochmal", nur ohne Menü.
|
||||
game._quitting = true
|
||||
Net.leave()
|
||||
var raum: Node = game.get_parent().get_parent().get_parent()
|
||||
raum.get_parent().remove_child(raum)
|
||||
raum.queue_free()
|
||||
game = null
|
||||
await get_tree().process_frame
|
||||
print("SIM Stufe %d: %d Partien, Ø %.1f Runden (längste %d), Ø %.1f Würfe, Ø %.1f Genau-Rufe" % [
|
||||
stufe, partien, float(runden_summe) / partien, laengste, float(wuerfe) / partien, float(genau) / partien])
|
||||
for k in siege:
|
||||
print("SIM Stufe %d: %s gewinnt %d×" % [stufe, k, siege[k]])
|
||||
get_tree().quit()
|
||||
6
tests/sim.tscn
Normal file
6
tests/sim.tscn
Normal file
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tests/sim.gd" id="1"]
|
||||
|
||||
[node name="Test" type="Node"]
|
||||
script = ExtResource("1")
|
||||
Reference in New Issue
Block a user