extends Node ## Kampagne: eine Seekarte mit Kapitänen. Wen du schlägst, der verschwindet ## auf See und kommt nach zwei Partien stärker zurück - höhere Wertung, bessere ## Bots, mehr Mannschaft. Du hast eine Wertung (Elo) und daraus einen Rang. ## Der Stand liegt in user://kampagne.json. const DATEI := "user://kampagne.json" const K := 32.0 ## Elo-Schrittweite const AUF_SEE := 2 ## Partien, bis ein Geschlagener zurück ist const RAENGE := [ [0, "Schiffsjunge"], [950, "Matrose"], [1050, "Maat"], [1150, "Steuermann"], [1280, "Kapitän"], [1420, "Admiral"], ] ## Kapitäne mit Wertung, Bot-Stufe, Mannschaft (weitere Bots eine Stufe ## darunter) und Platz auf der Karte (0..1 in Breite und Höhe). ## `art` ist das Temperament: die vier Regler aus ai.gd, je Kopf anders. ## `wesen` steht auf der Karte. const START_KAPITAENE := [ {"name": "Roter Jack", "elo": 900, "stufe": 0, "mannschaft": 0, "pos": Vector2(0.14, 0.32), "wesen": "Bluffer", "art": {"bluff": 0.65, "genau": 0.40, "zweifel": 0.30, "sicher": 0.30}}, {"name": "Salz-Marie", "elo": 960, "stufe": 0, "mannschaft": 1, "pos": Vector2(0.33, 0.68), "wesen": "Zauderin", "art": {"bluff": 0.10, "genau": 0.45, "zweifel": 0.45, "sicher": 0.40}}, {"name": "Einauge Bill", "elo": 1030, "stufe": 1, "mannschaft": 1, "pos": Vector2(0.52, 0.24), "wesen": "Genau-Rufer", "art": {"bluff": 0.30, "genau": 0.15, "zweifel": 0.30, "sicher": 0.42}}, {"name": "Käpt'n Grau", "elo": 1110, "stufe": 1, "mannschaft": 2, "pos": Vector2(0.70, 0.62), "wesen": "Rechner", "art": {"bluff": 0.20, "genau": 0.26, "zweifel": 0.26, "sicher": 0.50}}, {"name": "Die Witwe", "elo": 1210, "stufe": 2, "mannschaft": 2, "pos": Vector2(0.87, 0.30), "wesen": "Eiskalt", "art": {"bluff": 0.45, "genau": 0.24, "zweifel": 0.22, "sicher": 0.50}}, {"name": "Admiral Krähe", "elo": 1360, "stufe": 2, "mannschaft": 3, "pos": Vector2(0.50, 0.80), "wesen": "Draufgänger", "art": {"bluff": 0.55, "genau": 0.20, "zweifel": 0.28, "sicher": 0.44}}, ] const ZIEL_RANG := "Admiral" ## wer das erreicht, hat die Kampagne gewonnen var datei := DATEI ## Tests schreiben woandershin var elo := 1000.0 var partien := 0 var siege := 0 var kapitaene: Array = [] ## {name, elo, stufe, mannschaft, pos, weg, geschlagen} var aktiv := "" ## Kapitän der laufenden Partie, "" = keine Kampagnenpartie var letztes := "" ## Text zum letzten Ergebnis, fürs Endfeld var zur_karte := false ## nach dem Ende zurück auf die Karte statt ins Hauptmenü var kampagnenpartie := false ## die laufende (oder gerade beendete) Partie gehört zur Kampagne var spielername := "Spieler" var meldungen: Array = [] ## die letzten Ereignisse für die Karte, neueste zuerst var beute := "" ## gewählte Münze für die nächste Partie var beute_offen := false ## nach einem Sieg darf eine Münze gewählt werden var gewonnen := false ## Zielrang erreicht func _ready() -> void: laden() static func rang(wert: float) -> String: var r := "" for stufe in RAENGE: if wert >= float(stufe[0]): r = String(stufe[1]) return r func mein_rang() -> String: return rang(elo) func zuruecksetzen() -> void: elo = 1000.0 partien = 0 siege = 0 aktiv = "" letztes = "" meldungen = [] beute = "" beute_offen = false gewonnen = false kapitaene = [] for k in START_KAPITAENE: var e: Dictionary = k.duplicate() e.weg = 0 e.geschlagen = 0 kapitaene.append(e) func kapitaen(name: String) -> Dictionary: for k in kapitaene: if k.name == name: return k return {} ## Partie gegen einen Kapitän: er selbst auf seiner Stufe, seine Mannschaft ## eine Stufe darunter. Der Spieler sitzt auf Platz 0. func starten(name: String) -> void: var k := kapitaen(name) if k.is_empty() or int(k.weg) > 0: return aktiv = name kampagnenpartie = true Net.player_name = spielername var bots: Array = [{"name": name, "stufe": int(k.stufe), "regler": k.art}] for i in int(k.mannschaft): bots.append({"name": "Maat %d" % (i + 1), "stufe": maxi(0, int(k.stufe) - 1)}) var mit := beute beute = "" beute_offen = false Net.start_singleplayer(spielername, bots.size(), int(k.stufe), 5, bots, mit) ## Erwartete Siegchance gegen einen Kapitän, nach Elo. func siegchance(k: Dictionary) -> float: return 1.0 / (1.0 + pow(10.0, (float(k.elo) - elo) / 400.0)) func _melden(text: String) -> void: meldungen.push_front(text) if meldungen.size() > 3: meldungen.pop_back() ## Wird vom Spiel am Ende gerufen. `ausgeschieden` ist die Reihenfolge der ## Plätze, die rausgeflogen sind. Geschlagen ist der Kapitän, wenn er vor dem ## Spieler ging - oder der Spieler gewonnen hat. func ergebnis(seats: Array, ausgeschieden: Array) -> void: if aktiv == "": return var k := kapitaen(aktiv) aktiv = "" if k.is_empty(): return var spieler := 0 var kap := -1 for i in seats.size(): if String(seats[i].name) == String(k.name): kap = i var sieg: bool if seats[spieler].alive: sieg = true elif kap >= 0 and ausgeschieden.has(kap) and ausgeschieden.has(spieler): sieg = ausgeschieden.find(kap) < ausgeschieden.find(spieler) else: sieg = false var erwartet := 1.0 / (1.0 + pow(10.0, (float(k.elo) - elo) / 400.0)) var punkte := 1.0 if sieg else 0.0 var vorher := elo var delta := K * (punkte - erwartet) elo = maxf(0.0, elo + delta) k.elo = maxf(0.0, float(k.elo) - delta) partien += 1 # Die anderen auf See rücken einen Schritt näher an die Rückkehr. for o in kapitaene: if o != k and int(o.weg) > 0: o.weg = int(o.weg) - 1 if int(o.weg) == 0: _zurueck(o) if sieg: siege += 1 k.weg = AUF_SEE k.geschlagen = int(k.geschlagen) + 1 beute_offen = true letztes = "Sieg gegen %s! %d → %d (%s)" % [k.name, int(round(vorher)), int(round(elo)), mein_rang()] _melden("%s geschlagen - auf See für %d Partien. Beute wartet." % [k.name, AUF_SEE]) else: letztes = "%s behält die Oberhand. %d → %d (%s)" % [k.name, int(round(vorher)), int(round(elo)), mein_rang()] _melden("%s behält die Oberhand." % k.name) if not gewonnen and mein_rang() == ZIEL_RANG: gewonnen = true _melden("Du bist %s. Die Kampagne ist gewonnen - die See bleibt offen." % ZIEL_RANG) speichern() ## Zurück von See: besser als vorher. Stufe bis 2, dann wächst die Mannschaft. func _zurueck(k: Dictionary) -> void: k.elo = float(k.elo) + 60.0 if int(k.stufe) < 2: k.stufe = int(k.stufe) + 1 else: k.mannschaft = mini(4, int(k.mannschaft) + 1) _melden("%s ist zurück - %s, Wertung %d, Mannschaft %d." % [ k.name, ["leicht", "normal", "schwer"][int(k.stufe)], int(round(float(k.elo))), int(k.mannschaft)]) func speichern() -> void: var daten := {"elo": elo, "partien": partien, "siege": siege, "kapitaene": [], "name": spielername, "meldungen": meldungen, "beute": beute, "beute_offen": beute_offen, "gewonnen": gewonnen} for k in kapitaene: daten.kapitaene.append({"name": k.name, "elo": k.elo, "stufe": k.stufe, "mannschaft": k.mannschaft, "weg": k.weg, "geschlagen": k.geschlagen}) var f := FileAccess.open(datei, FileAccess.WRITE) if f != null: f.store_string(JSON.stringify(daten)) f.close() func laden() -> void: zuruecksetzen() if not FileAccess.file_exists(datei): return var text := FileAccess.get_file_as_string(datei) var daten = JSON.parse_string(text) if not (daten is Dictionary): return elo = float(daten.get("elo", 1000.0)) partien = int(daten.get("partien", 0)) siege = int(daten.get("siege", 0)) spielername = String(daten.get("name", "Spieler")) meldungen = daten.get("meldungen", []) beute = String(daten.get("beute", "")) beute_offen = bool(daten.get("beute_offen", false)) gewonnen = bool(daten.get("gewonnen", false)) for d in daten.get("kapitaene", []): var k := kapitaen(String(d.get("name", ""))) if k.is_empty(): continue k.elo = float(d.get("elo", k.elo)) k.stufe = int(d.get("stufe", k.stufe)) k.mannschaft = int(d.get("mannschaft", k.mannschaft)) k.weg = int(d.get("weg", 0)) k.geschlagen = int(d.get("geschlagen", 0))