Zum Inhalt springen

Beispiele

Jedes Skript hier wird entweder mit der App ausgeliefert oder ist aus Teilen gebaut, die mitgeliefert werden. Kopier eines, füg es in das darüber genannte Feld ein und pass es an.

Diese kommen in das Feld Wert eines Inhalts.

{{ (secondsLeftInLevel * 1000).toCompactTimeDurationString }}

secondsLeftInLevel sind Sekunden, und die Zeitspannen-Formatierer lesen Millisekunden, deshalb wird zuerst mit 1000 multipliziert. toCompactTimeDurationString ergibt 20:00, toTimeDurationString würde 0:20:00 ergeben.

Level {{ blindLevelNumber }} von {{ blindLevelCount }}

Mit der Bedingung isBlindsLevel, damit die Zeile während einer Pause verschwindet, statt auf dem gerade beendeten Level einzufrieren.

{{ (currentLevel.smallBlind).toCompactDecimalString + " / " + (currentLevel.bigBlind).toCompactDecimalString }}

toCompactDecimalString macht auf Englisch aus 1500 ein 1.5K, was verhindert, dass große Stapel die Zelle sprengen.

{{ (averageChipsPerPlayer / activeBigBlind).round }} BBs

Gib dem Ganzen die Bedingung activeBigBlind > 0, damit nicht durch null geteilt wird, bevor die Blinds anfangen.

endet gegen {{ (now() + estimatedSecondsUntilEnd * 1000).round.toHMTimeString }}

now() sind Millisekunden, deshalb wird die Schätzung passend umgerechnet und dann als Uhrzeit formatiert. Gib ihr die Bedingung isTimerRunning, denn bei pausierter Uhr ist die Schätzung sinnlos.

{{ chipLeaders.take(3).map(func(p) { p.shortName + " — " + p.currentChips.round.toCompactDecimalString }).join("\n") }}

chipLeaders sind die aktiven Spieler mit gemeldetem Chipstand, meiste Chips zuerst, take(3) liefert also die Top drei; map macht aus jedem eine Zeile „Name — Stapel“ und join stapelt sie untereinander. Bedingung: chipLeaders.isNotEmpty.

{{ cards = highHand.pokerHandToCards; highHandPlayer.shortName + " — " + cards.pokerCardsToHandName + " " + cards.pokerCardsToText }}

Zwei Anweisungen in einer Zeile, getrennt durch ;, weil eine Vorlage in ein Klammernpaar passen muss. Die erste entschlüsselt die Hand in Karten, die zweite baut die Zeile. Bedingung: hasHighHand.

Der Trick bei einer Rangliste ist where zum Filtern, sort mit negiertem Schlüssel zum Ordnen, take zum Abschneiden und join("\n"), um die Ergebnisse zu einer Spalte zu stapeln.

{{
eliminators = players.where(func(p) { p.totalEliminated > 0 }).sort(func(p) { -p.totalEliminated })
eliminators.take(5).map(func(p) { p.shortName }).join("\n")
+ if (eliminators.count > 5) { "\n…" } else { "" }
}}

Das if am Ende hängt drei Punkte an, wenn mehr Spieler da sind als hineinpassen, und hängt nichts an, wenn nicht, denn if liefert so oder so einen Wert.

Die Zahlenspalte daneben ist dasselbe Skript mit zwei Änderungen: p.totalEliminated statt p.shortName, und ein blankes "\n" statt "\n…":

{{
eliminators = players.where(func(p) { p.totalEliminated > 0 }).sort(func(p) { -p.totalEliminated })
eliminators.take(5).map(func(p) { p.totalEliminated }).join("\n")
+ if (eliminators.count > 5) { "\n" } else { "" }
}}

Nur die Namensspalte bekommt die drei Punkte, die Zahlenspalte hängt bloß die Leerzeile an. So bleiben beide Spalten gleich hoch und ihre Zeilen fluchten.

Das eingebaute Widget Punkte, in zwei Spalten. playersEliminated ist bereits nach Platz sortiert, und der Sieger ist der eine aktive Spieler mit einer Platzierung, wird also vorangestellt, sobald das Turnier vorbei ist: take einer bedingten Anzahl fügt ihn dann hinzu und jetzt noch nicht.

{{
scored = playersActive.take(if (isPostgame) { 1 } else { 0 }) + playersEliminated
scored.take(10).map(func(p) { p.shortName }).join("\n")
+ if (scored.count > 10) { "\n…" } else { "" }
}}

Die Punktespalte daneben tauscht p.shortName gegen p.points und die Auslassungspunkte gegen ein schlichtes "\n":

{{
scored = playersActive.take(if (isPostgame) { 1 } else { 0 }) + playersEliminated
scored.take(10).map(func(p) { p.points }).join("\n")
+ if (scored.count > 10) { "\n" } else { "" }
}}

p.points führt deine eigene Punkteformel aus, hier steht also keine Wertung, die du mit ihr abgleichen müsstest.

{{
winners = players.where(func(p) { p.bountyChipWinnings > 0 }).sort(func(p) { -p.bountyChipWinnings })
winners.take(10).map(func(p) { p.shortName }).join("\n")
+ if (winners.count > 10) { "\n…" } else { "" }
}}

Die Gewinnspalte dazu funktioniert genauso: p.bountyChipWinnings.toCurrencyString für den Betrag, und "\n" ohne die drei Punkte.

Die einfachste Fassung der Auszahlungsliste sind drei Inhalte nebeneinander. Die Plätze:

{{ i = 0; payouts.take(payoutsToShow).map(func(e) { i = i + 1; i.withOrdinalSuffix }).join("\n") }}

withOrdinalSuffix ist fest englisch und ergibt 1st, 2nd, 3rd. Für deutsche Platzierungen nimmst du stattdessen:

{{ i = 0; payouts.take(payoutsToShow).map(func(e) { i = i + 1; i.toString + "." }).join("\n") }}

Die Beträge:

{{ payouts.take(payoutsToShow).map(func(e) { e.toCurrencyString }).join("\n") }}

Und sobald Spieler fertig sind, ihre Namen:

{{ playersWithPayouts.map(func(p) { p.shortName }).join("\n") }}

Das eingebaute Widget Auszahlungen benutzt längere Fassungen dieser drei, die die noch offenen Plätze mit den fertigen Spielern zusammenführen und die jüngsten Knockouts unter einer Leerzeile anhängen. Die lohnen einen Blick, sobald du dich sicher fühlst, aber diese drei sind das Muster.

Bedingungen kommen in das Feld Bedingung eines Inhalts oder an eine Zeile oder Spalte in Layout & Inhalt.

totalCollected > 0
currentLevel.bigBlind > 0 || currentLevel.ante > 0
levelIndex + 1 < levels.length
useBountyChips && players.where(func(p) { p.bountyChipWinnings > 0 }).count > 0

Das sind die echten Bedingungen der eingebauten Widgets Auszahlungen, Blinds und Antes, Nächstes Level und Pause sowie Kopfgelder.

Die Bedingung einer Warnung wird etwa einmal pro Sekunde geprüft, und die Warnung löst jedes Mal aus, wenn sie wahr ist. becameTrue(...), changed(...) und was(...) sind das, was aus „zehn Minuten lang wahr“ ein „in genau diesem Moment wahr“ macht.

becameTrue(currentPlayerCount == 3)
becameTrue(currentLevel.bigBlind >= 100)
becameTrue(secondsLeftInLevel <= 60) && currentLevel.isBlindLevel

Das ist die eingebaute Warnung nahe dem Ende des Levels. Ohne das becameTrue würde sie sechzigmal auslösen.

currentPlayerCount < was(currentPlayerCount)

was(x) gibt dir den Wert selbst von vor einer Sekunde, für Vergleiche, die die beiden anderen nicht direkt ausdrücken — hier, dass die Anzahl gesunken und nicht bloß anders ist.

(changed(levelIndex) || becameTrue(isInGame)) && currentLevel.isBlindLevel && currentLevel.bigBlind > 0

Das ist die eingebaute Warnung Blinds geändert. Die erste Hälfte ist der Levelwechsel, die zweite der Beginn des Spiels — auch das ist eine Änderung der Blinds, nämlich von nichts auf die Startblinds. Diese Hälfte ist es, die die Blinds in einer Struktur ohne Vorlauflevel ansagt: Dort startet der Timer auf dem ersten Blind-Level und wechselt somit nie darauf.

Mit diesem TTS-Text:

Der Small Blind beträgt {{ currentLevel.smallBlind }}, und der Big Blind beträgt {{ currentLevel.bigBlind }}
if log.entries.isEmpty { false } else {
e = log.entries.last
e.type == "knockout" && e.status == "enabled" && changed(log.entries.last.dt)
}

Die isEmpty-Absicherung kommt zuerst, weil .last auf einem leeren Protokoll fehlschlagen würde. changed(...) auf dem dt des Eintrags ist das, was ein erneutes Auslösen beim selben Knockout verhindert, und es steht am Ende, damit die Typprüfung zuerst läuft. Beachte, dass dort ausgeschrieben log.entries.last.dt steht statt e: Diese Funktionen lesen Turnierwerte erneut, und e hast du selbst zugewiesen.

Der zugehörige TTS-Text nennt alle Beteiligten:

Es ist ein Knockout! {{ e = log.entries.last; e.players.map(func(p) { p.toPlayer().name }).join(", ", ", and ") }}, ausgeknockt von {{ e.byPlayers.map(func(p) { p.toPlayer().name }).join(", ", ", and ") }}!

join(", ", ", and ") gibt dem letzten Namen ein eigenes Trennzeichen, sodass drei Namen als „Alice, Bob, and Carol“ gelesen werden statt als „Alice, Bob, Carol“.

Diese kommen in AuszahlungenSkript. Das Ergebnis muss eine Liste von Beträgen sein; ihre Länge bestimmt, wie viele Plätze bezahlt werden.

places = 1 + totalBuyIns / 5
defaultPayoutPercentages(places).map(func(p) { p * netPrize })

Ein bezahlter Platz je fünf Buy-ins, aufgeteilt nach den Prozentsätzen der App.

if totalBuyIns < 10 { [100, 60, 40] }
else if totalBuyIns < 17 { [150, 90, 60] }
else { [180, 110, 70, 40] }

Feste Beträge statt Prozentsätze, wobei ab siebzehn Buy-ins ein vierter Platz dazukommt.

[0.55 * (netPrize - 50), 0.45 * (netPrize - 50), 50]

Platz drei bekommt immer 50, die oberen beiden teilen den Rest 55/45.

table = [
[90, 30],
[110, 40],
[130, 50],
[150, 60],
[160, 60, 20],
[170, 70, 30]
]
n = totalBuyIns + totalRebuys
table[min(max(n, 4), 9) - 4]

Eine Zeile pro Teilnehmerzahl, beginnend bei vier. Die letzte Zeile begrenzt die Anzahl auf den Bereich, den die Tabelle abdeckt, und greift die passende Zeile heraus. Zeilen dürfen unterschiedlich lang sein, die Zahl der bezahlten Plätze ändert sich also im Verlauf der Tabelle. Erweitere sie Zeile für Zeile.

Diese kommen in StatistikenSpielerpunktformel und werden einmal pro Spieler ausgewertet, wobei playerId auf diesen Spieler zeigt.

Die eingebaute Formel, mit zwei Alternativen als Kommentar:

player = playerId.toPlayer()
p = player.place
e = player.totalEliminated
c = totalPlayerCount
t = totalCollected
points = 0
// Linear
// points = c - p + 1
// Square root
// points = sqrt(c + 1) / p * 10
// Logarithmic
points = ln(c + 1) / p * 10
points.round

Tausch, welche Zeile auskommentiert ist, um die Kurve zu ändern. Linear gibt einen Punkt für jeden Spieler, den du überdauert hast; logarithmisch, die Voreinstellung, belohnt die vordersten Plätze deutlich stärker.

Um Knockouts einzurechnen, addierst du sie dazu:

points = ln(c + 1) / p * 10 + e * 2
points.round

Dieselbe Formel summieren die Club-Statistiken über eine Saison. Änderst du sie, ändert sich also auch die Saison-Rangliste.