[#_lesson_12_score_lives_and_game_over] == Lektion 12: Punkte, Leben und Game Over image::learn/12-score.png[Die fertige Lektion, 480] Dein Spiel zählt Punkte, nimmt Leben und sagt GAME OVER. Es ist fast ein Spiel. Drei Dinge fehlen noch. Es hat keine Eingangstür: Sobald es geladen ist, steckt es mitten in der Action. Es vergisst: Beende es, und die beste Punktzahl, die je jemand erreicht hat, geht mit. Und es merkt sich, was es gerade tut, mit zwei Wahr-oder-falsch-Variablen, `over` und `dying`, und das ist die Falle, vor der dich Lektion sieben gewarnt hat. Diese Lektion behebt alle drei. Eine Variable dafür, was das Spiel gerade tut, ein Titelbildschirm, von dem aus es losgeht, ein Highscore, der morgen noch da ist, und ein zweites Spiel, das genau wie das erste anfängt. [#_a_game_is_always_in_exactly_one_state] === Ein Spiel ist immer in genau einem Zustand Sieh irgendeinem Arcade-Automaten zwei Minuten lang zu. Er zeigt einen Titel und wartet auf jemanden. Jemand drückt Start, und er spielt. Dem Spieler gehen die Leben aus, und der Automat sagt das, dann wartet er wieder. Der Automat tut immer genau eins dieser Dinge. Dodge in Lektion sieben hat das in einer Variablen festgehalten, die ein Wort hielt: `"waiting"`, `"playing"` oder `"over"`. Das Spiel aus Lektion neun ist zu einem `over` zurückgekehrt, das nur wahr oder falsch sein kann, und Lektion zehn hat `dying` danebengestellt. Zähl, was zwei davon sagen können. Jede ist `true` oder `false`, also ergeben sie zusammen vier Kombinationen. Drei bedeuten etwas: Spielen, Sterben und Vorbei. Die vierte, beide auf einmal, ist Unsinn, und dein Code muss sorgfältig darauf achten, sie nie zu erzeugen. Er achtet darauf: Sieh dir `endGame` an, das `dying` zurück auf `false` setzt, bevor es `over` auf `true` setzt. Jetzt füg einen Titelbildschirm hinzu. Das ist ein drittes Flag, acht Kombinationen, und vier davon Unsinn. Das ist genau die Falle aus Lektion sieben, und hier gehen kleine Spiele sterben. Mach es also wie Dodge. Vier Zustände, eine Variable. Schreib die Namen der Zustände unter deine anderen Konstanten: [source,lua] ---- local TITLE = "title" local PLAYING = "playing" local DYING = "dying" local OVER = "over" ---- Dann nimm `over` und `dying` aus deinen Variablen heraus und setz eine Zeile an ihre Stelle: [source,lua] ---- local state = TITLE ---- Dodge hat die Wörter selbst benutzt. Namen in Großbuchstaben sind nicht schlauer, und sie fangen keinen Tippfehler ab: Lua behandelt einen falsch geschriebenen Namen als `nil` und ein falsch geschriebenes Wort als ein Wort, zu dem nichts passt, und beides gibt dir ein Spiel, das stillschweigend nicht mehr reagiert. Was dir die Namen einbringen, ist eine Liste. Jeder Zustand, in dem das Spiel sein kann, steht einmal aufgeschrieben oben in der Datei, wo du alle vier auf einen Blick siehst. Überall, wo das Spiel früher ein Flag gesetzt hat, setzt es jetzt den Zustand. In `updateRocks`, wo das letzte Leben verloren geht: [source,lua] ---- if lives <= 0 then state = DYING deathChannel = channel ---- In `endGame` werden aus zwei Zeilen eine, und die unsinnige Kombination ist weg, weil es nichts mehr zu kombinieren gibt: [source,lua] ---- function endGame() deathChannel = -1 state = OVER musicStop(theme, 1500) end ---- Und in `startGame` werden die beiden Zeilen, die beide Flags zurückgesetzt haben, zu `state = PLAYING`. [#_one_place_that_decides] === Eine Stelle, die entscheidet In jedem Frame trifft `onOverlayUpdate` die beiden Entscheidungen, auf die es ankommt: was sich bewegt und welche Wörter obendrauf kommen. Was sich bewegt, ist ein einziger Test: [source,lua] ---- if state == PLAYING then updateShip() updateShots() updateRocks() end ---- Das ersetzt `if not over and not dying then`, und es sagt, was es meint. Auf dem Titelbildschirm, während die letzte Explosion spielt, und wenn das Spiel vorbei ist, bewegt sich nichts. Das bekommst du, indem du die Funktionen nicht aufrufst, die Dinge bewegen. Ein Spiel anzuhalten ist kein besonderes Feature; es ist Weglassen. Die Welt wird trotzdem in jedem Frame gezeichnet, egal in welchem Zustand, genau wie Dodge sie gezeichnet hat. Nur die Wörter ändern sich. Ersetz die Zeile `drawHud()` hierdurch: [source,lua] ---- if state == TITLE then drawTitle() else drawHud() end ---- `drawTitle` ist neu, und es kommt nach `drawStars`: [source,lua] ---- function drawTitle() overlayPrint(25, 6, "R O C K S") overlayPrint(25, 9, "BEST " .. best) overlayPrint(22, 12, "PRESS 1 TO PLAY") end ---- `best` ist die bisher beste Punktzahl, und sie kommt im nächsten Abschnitt dazu; bis dahin läuft das Spiel nicht. Vor dem ersten Spiel ist die Liste der Felsen leer, also zeigt der Titelbildschirm die vorbeiziehenden Sterne und das Schiff, das unten wartet, und mehr braucht ein Titelbildschirm nicht. Eine Zeile muss weg, sonst siehst du den Titel nie. Lösch das `startGame()` ganz unten in der Datei. Das Spiel beginnt jetzt auf seinem Titelbildschirm, und *Neu anfangen* sagt, warum diese Zeile wichtig ist. `drawHud` stellt eine eigene Frage. Ändere sein `if over then` in: [source,lua] ---- if state == OVER then ---- Der Eingabe-Callback braucht dieselbe Art von Nachdenken. Hier ist er vollständig: [source,lua] ---- function onInputPressed(what) if what == SWITCH_LEFT then goLeft = true elseif what == SWITCH_RIGHT then goRight = true elseif what == SWITCH_BUTTON1 and state == PLAYING then shots[#shots + 1] = { x = shipX + shipWidth / 2 - shotWidth / 2, y = shipY } soundPlay(shootClip, 0, 35) elseif what == SWITCH_START1 and (state == TITLE or state == OVER) then soundPlay(clickClip) startGame() end end ---- Lies ihn von oben. Die Pfeiltasten werden in jedem Zustand festgehalten, denn sie sagen nur, welche Tasten unten sind, und nichts bewegt das Schiff, außer der Zustand ist `PLAYING`. Feuern bedeutet nur beim Spielen etwas. Und `1` startet ein Spiel vom Titelbildschirm oder vom Game-Over-Bildschirm aus, aber nicht, solange die letzte Explosion noch klingt: Das Spiel ist erst vorbei, wenn es das sagt. Die Klammern funktionieren wie beim Rechnen. Das `or` darin wird zuerst ausgerechnet, und dann verbindet das `and` es mit der Taste. [#_score_and_lives] === Punkte und Leben Beides hast du seit Lektion neun, und daran ändert sich kaum etwas. Neu ist die beste Punktzahl, und die fängt als gewöhnliche Variable an. Stell sie zu den anderen, unter `lives`: [source,lua] ---- local best = 0 ---- Zeig sie in der Zeile oben quer über den Bildschirm, zwischen den anderen beiden. In `drawHud`: [source,lua] ---- overlayPrint(1, 1, "SCORE " .. score) overlayPrint(25, 1, "BEST " .. best) overlayPrint(50, 1, "LIVES " .. lives) ---- Und gib `endGame` die Aufgabe, einen Rekord zu bemerken: [source,lua] ---- if score > best then best = score end ---- Starte es. Der Titel sagt `BEST 0`. Drück `1`, schieß etwas ab, verlier, und der Game-Over-Bildschirm zeigt deine Punktzahl als die beste. Drück wieder `1`, und die beste bleibt, während die Punkte von vorn anfangen. Jetzt beende es, starte es noch einmal und sieh dir den Titel an: `BEST 0`. Der Rekord hat genau so lange gelebt wie das Programm. Das ist die zweite Hälfte dieser Lektion. Wo du gerade bei den Leben bist: Lektion neun hat den Test als `lives <= 0` geschrieben, nicht als `lives == 0`, und dabei sollte es bleiben. Heute kann nichts zwei Leben auf einmal nehmen, also können die beiden nur übereinstimmen. An dem Tag, an dem du einen Felsen einbaust, der zwei kostet, steigt `== 0` glatt über die Null hinweg, und der Spieler spielt mit minus einem Leben weiter, während `<= 0` weiter funktioniert. Frag nach dem, was du meinst. [#_starting_again] === Neu anfangen Das `startGame()` unten in der Datei hast du schon gelöscht. In Lektion zehn hat diese Zeile das erste Spiel gestartet und das Drücken von `1` jedes spätere, und das sind zwei Wege hinein. Jetzt gibt es einen. Jedes Spiel, das erste eingeschlossen, beginnt, wenn jemand `1` drückt und `startGame` läuft, also fangen das erste und das fünfte Spiel genau gleich an. `startGame` muss alles zurücksetzen, was ein Spiel verändert: [source,lua] ---- function startGame() shipX = (screenWidth - shipWidth) / 2 score = 0 lives = START_LIVES state = PLAYING deathChannel = -1 shots = {} for i = 1, ROCK_COUNT do rocks[i] = {} newRock(rocks[i]) end musicSetVolume(50) musicPlay(theme, -1) end ---- Genauso wichtig ist zu wissen, was es in Ruhe lässt, und warum. `best` überlebt jedes Spiel; das ist seine ganze Aufgabe. Die Sterne sind Kulisse. Und `goLeft` und `goRight` gehören überhaupt nicht dem Spiel. Sie gehören der Tastatur, und nur die Tastatur sollte sie ändern. Das Letzte ist ein Fehler, den du gerade behoben hast, ohne es zu merken. Der Eingabe-Callback aus Lektion zehn fing mit `if over then` an, und solange das Spiel vorbei war, hat er jede Taste außer `1` weggeworfen. Drück die Pfeiltaste nach rechts, während GAME OVER zu sehen ist, halt sie gedrückt und drück `1`. Das neue Schiff rührt sich nicht: Das Drücken der Pfeiltaste wurde weggeworfen, das Loslassen ist noch nicht passiert, und so ist `goRight` gleich `false`, während die Taste eindeutig unten ist. Das Schiff bleibt stehen, bis du loslässt und noch einmal drückst. Der neue Callback hält die Pfeiltasten in jedem Zustand fest, also ist die gehaltene Taste immer noch gehalten, wenn das Spiel beginnt. Fehler beim Neuanfangen sind die häufigsten Fehler in jedem Spiel, und sie fühlen sich beim Spielen alle gleich an: Das zweite Spiel ist auf subtile Weise falsch, und das erste war in Ordnung. Wenn das passiert, lies `startGame` und stell zwei Fragen. Was hat das letzte Spiel geändert, das dieses nicht zurücksetzt? Und was setzt dieses zurück, das das Spiel nie hätte anfassen dürfen? [#_a_number_that_outlives_the_program] === Eine Zahl, die das Programm überlebt Alles, was dein Spiel bisher festgehalten hat -- `score`, `lives`, die Liste der Felsen -- lebt im Speicher, und der Speicher ist in dem Moment weg, in dem das Programm aufhört. Singe gibt jedem Spiel einen eigenen Spielstand: eine Menge von Namen mit Werten darunter, für dich auf die Festplatte geschrieben. Er ist ein weiterer Teil des Spiele-Kits aus Lektion elf, also dieselbe Art von Ding wie `collideRects`: schlichte Aufrufe, nichts zu laden oder freizugeben. [source,lua] ---- saveSet("highScore", 4200) ---- `saveSet` nimmt einen Namen -- den *Schlüssel* -- und einen Wert. Den Schlüssel suchst du dir aus, und er ändert sich nie; der Wert ist das, was du dir merken willst. Zahlen, Strings, `true` und `false` und ganze Tabellen davon passen alle hinein. Dinge, die nur Sinn ergeben, solange das Programm läuft, passen nicht. Ein Sprite-Handle ist nur eine Zahl, also lässt es sich speichern, und morgen ist es eine Zahl, die nichts bedeutet. Eine Funktion lässt sich überhaupt nicht aufschreiben, und der Versuch, eine zu speichern, hält das Spiel mit einem Fehler an, statt dich das erst nächste Woche herausfinden zu lassen. Das Zurücklesen ist die andere Hälfte. Ändere die Zeile, die du für `best` eingefügt hast: [source,lua] ---- local best = saveGet("highScore", 0) ---- Sie läuft einmal, wenn das Spiel startet. Das zweite Argument ist der *Standardwert*: was geantwortet wird, wenn unter diesem Schlüssel noch nie etwas gespeichert wurde. Gleich mehr dazu, denn darum dreht sich der ganze Fehler dieser Lektion. Und lass `endGame` den Rekord aufschreiben, wenn er fällt: [source,lua] ---- function endGame() deathChannel = -1 state = OVER musicStop(theme, 1500) if score > best then best = score saveSet("highScore", best) saveFlush() end end ---- Du musst Singe nicht bitten, die Datei zu schreiben. Es schreibt sie einmal am Ende jedes Frames, in dem sich etwas geändert hat, egal wie viele Schlüssel du setzt, und noch einmal, wenn das Spiel herunterfährt. `saveFlush` schreibt sie stattdessen sofort: Ein neuer Highscore ist einen eigenen Schreibvorgang wert, denn ein Arcade-Automat wird an der Steckdose ausgeschaltet, und ein Frame ist eine lange Zeit. Starte es, stell eine Punktzahl auf, beende es und starte es noch einmal. Der Titel erinnert sich. Der Abschnitt Spielstand in der Referenz listet den Rest der Familie auf. `saveDelete` vergisst einen Schlüssel, `saveClear` vergisst alles -- das ist das „Highscores zurücksetzen“ in einem Service-Menü -- und `saveGetAll` gibt dir eine Kopie von allem, und das ist nützlich, wenn du den ganzen Spielstand ausgeben willst, während du nach etwas suchst. [#_where_the_file_actually_goes] === Wo die Datei wirklich landet Sie landet nicht neben deinem Skript, und das ist kein Zufall. Ein fertiges Spiel liegt vielleicht auf einer Disc, auf einer Netzwerkfreigabe oder in einer einzigen gepackten Datei (Lektion dreißig), und auf nichts davon kann man schreiben. Ein Spiel, das neben seinem eigenen Skript speichert, funktioniert perfekt auf dem Rechner, auf dem du es geschrieben hast, und scheitert auf der Hälfte der Rechner, auf denen es installiert wird. Singe 2 hat es so gemacht, und das war falsch. Darum kommt alles, was Singe für ein Spiel schreibt -- der Spielstand, Screenshots, sein Log -- an einen Ort, und `singeGetDataPath` sagt dir, wohin: [source,lua] ---- debugPrint(singeGetDataPath()) ---- Schreib das kurz direkt unter die Zeile mit `dofile` und starte es. Es sagt `data/rocks/`: der Ordner `data`, der in deinem Arbeitsordner aufgetaucht ist, als du in Lektion eins zum allerersten Mal etwas gestartet hast, und darin ein Ordner mit demselben Namen wie der Ordner deines Spiels. Dort findest du `save.json`. Öffne die Datei in deinem Texteditor: Es ist reiner Text, und dein Highscore steht darin unter dem Namen, den du ihm gegeben hast. Das ist auch die Antwort, wann immer du eine eigene Datei brauchst. Bau den Namen aus `singeGetDataPath()` zusammen, und sie landet irgendwo, wo man schreiben kann; schreib in einen Namen, den du dir selbst ausgedacht hast, und früher oder später tut sie das nicht. [#_the_first_time_there_is_nothing] === Beim ersten Mal ist nichts da Wenn irgendjemand dein Spiel zum allerersten Mal startet, wurde noch nie ein Highscore gespeichert. `saveGet` hat nichts zurückzugeben, also gibt es den Standardwert zurück, den du ihm gegeben hast: [source,lua] ---- local best = saveGet("highScore", 0) ---- Nichts gespeichert, also ist `best` gleich `0`, der Titelbildschirm sagt `BEST 0`, und das erste Spiel, das irgendjemand spielt, stellt einen Rekord auf. Genau das willst du, und es kostet ein zusätzliches Argument. Lass die `0` weg, und `saveGet` hat keinen Standardwert, auf den es zurückgreifen kann, also antwortet es `nil` -- der Wert, mit dem Lua „hier ist nichts“ sagt und den du in Lektion drei kennengelernt hast. `nil` ist nicht null. Du kannst nichts dazuzählen, du kannst es nicht mit einer Zahl vergleichen, und du kannst es nicht an einen String kleben. Es geht im ersten Frame schief, jedes Mal, auf jedem Rechner, auf dem das Spiel noch nicht gespielt wurde -- und dazu gehört jeder Rechner, auf dem deine Spieler es gleich installieren, aber nicht der, auf dem du testest. Gib jedem `saveGet` einen Standardwert. Das ist die billigste Angewohnheit in diesem Buch. [#_what_just_happened_8] === Was gerade passiert ist [source,lua] ---- local TITLE = "title" ---- Eine Konstante: ein Name, den du einmal setzt und nie änderst, in Großbuchstaben, damit du auf einen Blick siehst, dass es eine ist, wie in Lektion sieben. Lua erzwingt das nicht -- nichts hindert dich daran, `TITLE` etwas zuzuweisen -- also sind die Großbuchstaben eine Nachricht an den Nächsten, der es liest, und das bist du in zwei Wochen. [source,lua] ---- if state == PLAYING then ---- Die Frage, die entscheidet, ob sich die Welt bewegt. Wenn du später einen fünften Zustand hinzufügst -- eine Pause oder eine Bonusrunde -- fragst du als Erstes, ob sich die Welt darin bewegt, und hierhin kommt die Antwort. [source,lua] ---- elseif what == SWITCH_START1 and (state == TITLE or state == OVER) then ---- Ohne die Klammern würde Lua zuerst das `and` verbinden und das als „die Taste ist `1` und der Zustand ist der Titel, oder der Zustand ist vorbei“ lesen. Dann würde jede Taste, die die Zweige darüber nicht abgefangen haben, vom Game-Over-Bildschirm aus ein Spiel starten, die Feuertaste eingeschlossen, und ein Spieler, der noch auf den Feuerknopf hämmert, wenn der letzte Felsen trifft, säße in dem Moment, in dem GAME OVER erscheint, in einem neuen Spiel. Die Klammern sorgen dafür, dass das `or` zuerst passiert. Wenn du `and` und `or` mischst, setz die Klammern, auch wenn du glaubst, die Reihenfolge zu kennen. Wer es als Nächstes liest, muss sie dann nicht kennen. [source,lua] ---- saveSet("highScore", best) saveFlush() ---- `saveSet` legt die Zahl in den Spielstand; `saveFlush` bringt den Spielstand jetzt auf die Festplatte statt erst am Ende des Frames. Beide stehen im `if`, also wird die Datei nur geschrieben, wenn der Rekord wirklich gebrochen wird, und das ist ein paarmal pro Sitzung statt sechzigmal pro Sekunde. [source,lua] ---- local best = saveGet("highScore", 0) ---- Einmal gelesen, beim Start, in eine gewöhnliche Variable. Der Rest des Spiels liest `best`, statt noch einmal `saveGet` aufzurufen, denn der Spielstand ist eine Datei, und eine Variable ist eine Variable. Schreib in beide, wenn der Rekord fällt. [#_try_it_12] === Probier es aus . *Geh zurück zum Titel.* Ein Arcade-Automat bleibt nicht ewig auf GAME OVER stehen. Schlag `timerAfter` im Abschnitt Timer der Referenz nach, einem weiteren Teil des Spiele-Kits, und benutz es in `endGame`, um den Zustand fünf Sekunden nach Spielende zurück auf `TITLE` zu setzen. Dann drück nach einem Spielende schnell `1` und sorg dafür, dass der Timer dein neues Spiel nicht nach drei Sekunden zurück auf den Titel wirft. . *Zeig die Leben als Schiffe.* Zeichne `shipSprite` mit `spriteDraw` einmal pro Leben in die rechte obere Ecke, statt die Zahl auszugeben. Eine `for`-Schleife aus Lektion vier, und `shipWidth` sagt dir, wie weit du sie auseinandersetzen musst. . *Speichere mehr als eine Zahl.* Füg `saveSet("gamesPlayed", saveGet("gamesPlayed", 0) + 1)` in `startGame` ein und gib es auf dem Titelbildschirm aus. Beende es, starte es noch einmal und sieh zu, wie es sich erinnert. . *Finde die Datei und mach sie kaputt.* Öffne `data/rocks/save.json` in deinem Editor, ändere den Highscore in etwas Absurdes und starte das Spiel. Dann lösch die Datei ganz und starte es noch einmal. Beides wird deinen Spielern passieren. . *Setz ihn aus dem Spiel heraus zurück.* Lass eine Taste, die du nie aus Versehen drücken würdest, `saveClear()` aufrufen, und sieh dir danach den Titelbildschirm an. Vorsicht: `best` ist noch im Speicher, und `saveClear` rührt es nicht an. Finde heraus, was sonst noch passieren muss. [#_break_it_on_purpose_11] === Mach es absichtlich kaputt Nimm den Standardwert heraus, sodass die Zeile so lautet: [source,lua] ---- local best = saveGet("highScore") ---- Wenn du schon einen Highscore aufgestellt hast, passiert überhaupt nichts, und genau das ist die Falle: Der Spielstand hat den Schlüssel, also gibt `saveGet` ihn zurück. Lösch zuerst `data/rocks/save.json` und starte es dann. Der Titelbildschirm erscheint nie, und du bekommst das hier: ---- Error executing function 'onOverlayUpdate': rocks/rocks.singe:101: attempt to concatenate a nil value (upvalue 'best') stack traceback: rocks/rocks.singe:101: in function 'drawTitle' rocks/rocks.singe:165: in function 'onOverlayUpdate' ---- Lies es so, wie Lektion acht es dir beigebracht hat. Datei und Zeile führen dich zu `overlayPrint(25, 9, "BEST " .. best)` in `drawTitle`, und die Beschwerde sagt, dass etwas, das `nil` war, an einen String geklebt wurde. `..` verbindet zwei Strings, und `nil` ist keiner. Wenn deine Zeilen anders angeordnet sind, sind es deine Zahlen auch. Die Zeile, die sie nennt, ist nicht die, in der der Fehler steckt. In Zeile 101 ist das Spiel schließlich darüber gestolpert; gemacht wurde der Fehler oben in der Datei, wo nach `best` gefragt wurde und nichts da war. Diese Lücke zwischen der Stelle, an der ein schlechter Wert entsteht, und der, an der er bemerkt wird, ist das Schwierigste an `nil`, und der Grund, warum ein Standardwert ein Argument kostet und einen Abend rettet. Setz die `0` wieder ein. [#_what_you_learned_12] === Was du gelernt hast * Ein Spiel ist immer in genau einem Zustand, und Wahr-oder-falsch-Flags, die zu sagen versuchen, in welchem, erzeugen Kombinationen, die nichts bedeuten. * Halte den Zustand in einer Variablen fest, mit seinen möglichen Werten einmal oben in der Datei benannt. * `onOverlayUpdate` entscheidet, was sich bewegt und was gesagt wird, indem es den Zustand fragt. * Ein Spiel anzuhalten heißt, die Dinge nicht aufzurufen, die es bewegen. * Starte jedes Spiel, das erste eingeschlossen, über eine einzige Funktion, und lass diese Funktion alles zurücksetzen, was ein Spiel verändert -- und sonst nichts. * Tasten gehören der Tastatur. Halte sie in jedem Zustand fest. * `saveSet` speichert einen Wert unter einem Schlüssel, und `saveGet` liest ihn bei einem späteren Lauf zurück. * `saveFlush` schreibt die Datei sofort, für die Dinge, die du auf keinen Fall verlieren willst. * Der Spielstand kommt in den eigenen Datenordner des Spiels, den `singeGetDataPath` nennt. Schreib nie neben dein Skript. * `saveGet` ohne Standardwert antwortet `nil`, wenn nichts gespeichert wurde, und `nil` macht den ersten Frame auf einem Rechner kaputt, auf dem dein Spiel noch nie lief. * Wenn du `and` und `or` mischst, setz Klammern. [#_next_time_12] === Beim nächsten Mal Dein Spiel ist jetzt ein Spiel, und es ist ein Ordner, von dem nur du weißt, wie man ihn startet. Lektion dreizehn gibt ihm eine `games.dat`, damit das eigene Menü der Engine es mit einem Titel und einem Bild auflistet und ein Spieler, der noch nie von dir gehört hat, es starten kann, ohne irgendetwas zu tippen.