[Anfänger] Tutorial: Wie Programmiere ich Snake ?

  • Tutorial: Snake für Anfänger


    Moin,


    Ich versuche mich mal daran, euch näher zu bringen, wie man trotz unvollkommener Programmierkünste dennoch recht schnell zu großem Erfolg kommen kann.


    Ich behaupte nicht, dass ich hier alles perfekt machen werde, gebe mir aber Mühe dies zu tun.


    (Theoretische) Vorraussetzungen für dieses Tutorial sind:
    - ein gewisses Grundkenntniss in Mathematik
    - der Umgang mit Funktionen und Returnwerten usw.
    - das Wissen was ein Array ist
    - ein Überblick über die GDI+ Funktionen
    - etwas Zeit, da sich ein Spiel nicht von selbst schreibt


    Bei den GDI+ Kenntnissen bietet es sich an bereits eines der zahlreichen Tutorials absolviert zu haben, damit man mit den Grundbegriffen und Funktionen etwas anfangen kann.


    GDI+ Tutorial


    1. Das Grundgerüst


    Was gehört zum Grundgerüst eines Spiels ?
    Man sollte sich Gedanken über die Anforderungen, die man dem Spiel stellt machen und daraus erschließen, was man im Rahmen von AutoIt und seiner Fähigkeiten überhaupt erreichen kann.


    Wir stecken uns das Ziel in eine gut erreichbare Höhe und stellen folgende Anforderungen:
    - Die Spielmechanik soll Funktionieren
    - Wir wollen einfache Grafiken verwenden
    - Es soll verschiedene Schwierigkeitsstufen geben
    - ein Einfacher Highscore muss her


    Um Grafiken zu nutzen ist es sinnvoll die GDIPlus.au3 zu includen, damit deren Grundfunktionen genutzt werden können.


    Um Schwierigkeiten und den Highscore kann man sich später immernoch kümmern. Zuerst muss das Spiel ansich funktionieren.


    Als Einstieg in das was das Programm den Nutzer Zeigt benutzen wir die _Main() Funktion ohne Parameter.


    Alles was bis zum Aufruf dieder Funktion gemacht wird (deklaration globaler Variablen, GUI, usw) wird überall im Programm verfügbar sein. Alles in dieser, oder in den durch sie aufgerufenen Funktionen sollte lokal bleiben, also keine Auswirkungen auf unabhängige Programmteile haben.


    Wir beginnen mit der Breite und der Höhe des Spiels. Damit es auf allen Bildschirmen läuft nehmen wir für die $Breite und $Hoehe 500 Pixel. Diese "Variablen" werden sich im Spielverlauf nicht ändern. Deshalb Deklarieren wir sie als Konstanten (Const)


    Anschließend erstellen wir ein Grafisches Benutzer Interface ($hGUI) mit den Parametern $Breite und $Hoehe. Den Namen darf sich jeder selbst aussuchen.


    Direkt dahinter kommt ein GUISetState(@SW_SHOW, $hGUI), Damit wir das Fenster auch bewundern können, was wir erstellen wollen.


    Nach ein Paar Leerzeilen kommt schließlich die _Main() Funktion, die wir direkt anschließend deklarieren und mit einer Endlosschleife befüllen.



    Das Programm lässt sich nicht schließen außer mit einem klick auf das Tray Icon. Deshalb wechseln wir in den OnEventMode. Desweiteren wollen wir nicht, dass sich das Fenster bei Escape einfach schließen könnte. Und wir wollen, dass tatsächlich alle Variablen richtig deklariert sind.


    An den Anfang unter die Includes kommt:
    Opt('GUICloseOnESC', 0)
    Opt('GUIOnEventMode', 1)
    Opt('MustDeclareVars', 1)
    Was uns ermöglicht diese Wünsche auf einfachstem Weg zu erfüllen.


    Um zu Beenden brauchen wir eine Funktion die aufgerufen wird um das Programm zu schileßen. ( z.B. _Exit() )


    Diese Funktion ist Später dazu bestimmt alle von uns verwendeten Ressourcen wieder frei zu geben, damit wir kein Speicherleck im Ram hinterlassen beim Schließen des Programms.


    Diese Funktion Setzten wir auf das $GUI_EVENT_CLOSE an. Ein Kurzer Blick in die GuiConstantsEx.au3 verrät, dass es sich dabei um den Wert -3 handelt. ( Den kann man sich merken, weil man sich dann das Includen spaaren kann ) GUISetOnEvent(-3, '_Exit', $hGUI)


    Die Exitfunktion muss bisher nur Beenden, also reicht eine Leere Funktion mit einem Exit am Ende.


    Jetzt steht unser Gerüst für ein Programm und wir müssen uns um das Gerüst des Spiels kümmern.



    Um zu Definieren in welchem Spielabschnitt wir uns befinden deklarieren wir die Variable $Modus = 0 oberhalb des Main Aufrufs. Dort werden in Zukunft sämtliche Globalen Variablen deklariert.


    Wir Unterteilen den $Modus in 3 Teile: Schwierigkeitswahl, Spiel und Highscore.


    Innerhalb der Main Funktion wird nun eine Selektion mittels Switch vorgenommen um die einzelnen Programmteile zu kennzeichnen. Für jeden Dieser Teile erstellen wir eine gleichnamige parameterlose Funktion und rufen diese in der Selektion auf.


    Wir überlegen nun wie wir Springen wollen nachdem ein Programmteil (z.B. die Schwierigkeitswahl) abgelaufen ist. Um zu Springen verändern wir den $Modus. Er steht zu Beginn auf 0 und leitet so direkt zur Wahl der Schwierigkeit. Anschließend soll es weiter zum Spiel gehen und nach einer Runde weiter zum Highscore. Anschließend geht alles wieder von Vorne los.



    2. Programmabschnitt: Schwierigkeitswahl


    Der $Schwierigkeit verpassen wir direkt auch eine Globale Variable. Zukünftig wollen wir "Leicht", "Mittel" und "Schwer" als Schwierigkeitsgrade haben. Der Einfachheit halber nutzen wir aber nur die Zahlen von 1 bis 3.


    Um eine Einstellung vorzunehmen könnte man jetzt die Standard InputBox nutzen, aber diese bietet und nicht das, was wir wollen, da man immer davon ausgehen muss, dass die Menschheit blöd ist und z.B. Buchstaben und sonstigen Mist in die InputBox schreibt. Deshalb erstellen wir uns eine Eigene kleine InputBox (Func _InputBox ) mit Buttons für jeden Schwierigkeitsgrad. Dann kann niemand etwas falsch machen. ( $Schwierigkeit = _InputBox() )


    Die Bearbeitung der InputBox ist ein leichtes, da sie kaum Funktionen haben muss.


    Wir brauchen ein 200x200 Großes GUI, Anschließend 3 Buttons und eine Endlosschleife in der Die Buttons abgefragt werden.


    Um GUIGetMsg() zu nutzen müssen wir den OnEventMode verlassen und nach der InputBox wieder zu ihm zurückkehren.


    Die InputBox muss als Returnwert den gewünschten Schwierigkeitswert ( 1 bis 3 ) haben. Wie man diese InputBox aufbaut bleibt jedem selbst überlassen. Anbei ist eine kleine Musterlösung wie man es machen könnte.



    Die Schwierigkeitswahl scheint somit abgeschlossen und kann zusammen mit der InputBox ganz nach unten verschoben werden, damit der Code nicht immer im Weg ist.


    3. Programmabschnitt: Spiel


    Als erstes kommt in die _Spiel() Funktion eine Endlosschleife mit Sleep(10), damit wir das Programm schon Mal testen können. ( Sonst erscheint immer die Schwierigkeitswahl. )


    Damit man schonmal etwas von seiner Arbeit zu Gesicht bekommt erstellen wir uns unter den Globalen Variablen eine gepufferte Zeichenfläche ( GDI+ Graphics incl Backbuffer ). Alle 3 dazu verwendeten Variablen müssen in der Exit Funktion wieder freigegeben werden. ( _GDIPlus_StartUp nicht vergessen )



    Jetzt können wir drauf los Zeichnen. Dazu machen wir uns die WM_PAINT Funktion die das Bitmap auf die Zeichenfläche bringt.


    Func WM_PAINT()
    _GDIPlus_GraphicsDrawImage($hGraphic, $hBitmap, 0, 0)
    EndFunc
    Diese rufen wir am Schluss der Endlosschleife in der _Spiel() Funktion auf.


    Noch sieht man aber nichts, da das was gezeichnet wird völlig transparent ist. ein Clear ( _GDIPlus_GraphicsClear($hBackbuffer, 0xFF000000) ) zu Beginn der Schleife behebt dieses Problem. Nach einer Schwierigkeitsabfrage wird das Fenster nun Schwarz.


    Wenn wir es außerhalb des Bildschirms bewegen stellen wir ernüchternd fest, dass komischerweise die Schwarze Farbe verschwindet. Dazu gibt es schnelle Abhilfe, wenn man die WM_PAINT Funktion registriert.


    Dazu ein GUIRegisterMsg(0xF, 'WM_PAINT') HINTER die GDI+ Variablen setzen und den Effekt betrachten.


    ( Bei manchen Computern macht das Zeichnen wirklich Probleme. Hier hilft oft ein WinSetTrans($hGUI, '', 255) direkt hinter das GUI )



    Als Schlange nutzen wir ein Array mit doppeltem Index ($Snake[1][2]). Dieses wird lokal an den Beginn der _Spiel() Funktion gesetzt.


    Die $Schrittlänge (Strecke die die Schlange pro Schritt zurücklegt) liegt bei 25 Pixeln. So haben wir eine Effektive Spielfeldgröße von 20x20 Feldern.


    Um die Schlange zu bewegen wird weiterhin eine $Richtung benötigt. Diese kann Links, Rechts, Oben oder Unten ( 1, 2, 3, 4 ) sein.


    Die Richtung kann mithilfe der _IsPressed Funktion aus der Misc.au3 ( #include <Misc.au3> ) geändert werden. Diese Funktion kann Tastendrücke abfangen und gibt in einer Variable zurück, ob eine Taste gedrückt wurde. Damit werden wir die Pfeiltasten Abfangen. $_Links, $_Rechts, $_Oben, $_Unten


    In jedem Schleifendurchlauf sollen alle 4 Tasten abgefragt werden.


    $_Links = _IsPressed('25')
    $_Rechts = _IsPressed('27')
    $_Oben = _IsPressed('26')
    $_Unten = _IsPressed('28')


    Um damit die Schlange zu bewegen ist es parktisch diese erstmal zu zeichnen, damit die Auswirkungen beobachtet werden können. Dazu verwenden wir vorerst ein kleines weißes Viereck.


    Es muss die Funktoin _ZeichneSchlange($Snake, $Schrittlaenge) erstellt werden. Diese soll zukünftig alle Glieder der Schlange zeichnen. Zu den GDI+ Variablen kommt nun eine Weiße $hBrush hinzu. (Das Disposen in der Exitfunc nicht vergessen !)


    In der Funktion _ZeichneSchlange legen wir erstmal die Variablen $x und $y an die uns verraten wohin das Viereck soll, und die Variable $UBound = UBound($Snake, 1) die verrät wie lang die Schlange ist.


    In einer For Schleife wird nun jedes Element der Schlange durchgegangen und mittels FillRect gezeichnet. $x ist jedes Mal $Snake[$i][0] und $y =$Snake[$i][1].


    Wenn alles geklappt hat ist jetzt ein kleines weißes Viereck in der Bildmitte zu sehen.



    Allerdings bewegt sich noch relativ wenig. Das wollen wir nun schnell ändern und legen die Funktion _BewegeSchlange(ByRef $Snake, $Schrittlaenge, $Richtung) an. Hier wird die Schlange immer je eine Schrittlänge in die gewünschte Richtung bewegt.


    Wie bei der Zeichenfunktion brauchen wir einen $UBound der Schlange und eine For Schleife gleichen Aufbaus. (ohne die $x und $y Variablen und ohne das DrawRect)


    Jetzt muss entschieden werden in Welche Richtung wir und bewegen. Dazu nutzen Wir Switch $Richtung mit Case 1 Bis Case 4. Für Jede Richtung ein Case.


    Jetzt müssen wir uns verinnerlichen was eine Bewegung richtung 0 (Links) bedeutet. Wir erkennen, dass dadurch die y Koordinate unbeeinflusst bleibt und x um eine Schrittlänge abnimmt. Gleiche Überlegung wird für die anderen Richtungen angestellt und die Konsequenz daraus gezogen.


    Func _BewegeSchlange(ByRef $Snake, $Schrittlaenge, $Richtung)


    Local $UBound = UBound($Snake, 1)


    For $i = 0 To $UBound - 1 Step 1


    Switch $Richtung
    Case 1
    $Snake[$i][0] -= $Schrittlaenge
    Case 2
    $Snake[$i][0] += $Schrittlaenge
    Case 3
    $Snake[$i][1] -= $Schrittlaenge
    Case 4
    $Snake[$i][1] += $Schrittlaenge
    EndSwitch


    Next


    EndFunc
    Ein Funktionsaufruf vor dem Zeichnen und Nach der Richtungsangabe wird zeigen, dass die Schlange schon sehr beweglich ist. Sie ist aber viel zu schnell aus dem Bild geflogen um Spaß mit ihr haben zu können.


    Deshalb erhöhen wir die Sleepzeit in der While Schleife der _Spiel Funktion auf 250. Die Schlange wird nun zwar auch ungehindert aus dem Bild laufen, aber dieses Mal gut sichtbar.


    Wir wollen, dass die Schlange aus der Gegenüberliegenden Wand heraus kommt, sobald sie durch eine der 4 Wände verschwindet. (so verlieren wir sie auch nie aus den Augen)


    Die Bewegung muss also ergänzt werden. In Richtung Rechts wird am ehesten die Rechte Wand getroffen. Erkennen können wir das durch eine abfrage der x Position. Ist diese großer als die $Breite muss die Schlange die Wand getroffen haben. ( If $Snake[$i][0] >= $Breite Then $Snake[$i][0] = 0 )


    Wieder die gleiche Überlegung für die verbleibenden Richtungen, und man kommt schnell auf das folgende Ergebnis.



    Lenken können wir die Schlange leider immernoch nicht...
    Mit den Tastaturabfragen in der Spielschleife kann allerdings schnell alles verändert werden. ( If $_Links Then $Richtung = 1 ) Analog hierzu kann direkt nach allen Abfragen durch _IsPressed die Richtung beeinflusst werden.


    Schnell merken wir, dass die Lenkung nicht unsere Ansprüche erfüllt. Ein kurzer Tastendruck bewirkt in vielen Fällen garnichts. Um wirklich zu lenken muss man die Tasten viel zu lange gedrückt halten.


    Das liegt daran, dass die Tasten nur alle 250ms abgefragt werden. d.h. Wenn man nur kurz drückt ist die Taste vor der nächsten Abfrage schon wieder oben und wird somit auch nicht bemerkt.


    Die Tasten müssen also wesentlich öfter abgefragt werden, als die Positionsänderung der Schlange von statten geht. Wir fürhen also einen $Counter ein, der erfasst wie oft die Schleife schon gelaufen ist und erhöhen die Schleifengeschwindigkeit indem wir die Sleepzeit auf 10 setzen. Der $Counter wird beim Beginn jedes Durchlaufs um 1 erhöht.


    Jetzt ist die Schlange wieder sehr Schnell, reagiert aber direkt. Um sie wieder zu verlangsamen fragen wir den $Counter ab. Alle 10 Durchläufe muss nur bewegt oder gezeichnet werden. Da uns jetzt der Clear in der Schleife beginnt zu stören schmeißen wir ihn raus und stecken ihn hinter den Draw in der WM_PAINT Funktion. ( et voilá, als kleiner Nebeneffekt ist unser Fenster beim Spielstart direkt schwarz )


    Unsere _Spiel Funktion sieht jetzt folgendermaßen aus


    Da es sehr schnell und einfach geht, bauen wir den Schwierigkeitsgrad, der nur aus einem Geschwindigkeitsunterschied besteht direkt mit ein. Bei der $Counter Abfrage wird davon ausgegangen, dass alle 10 Durchläufe gezeichnet und bewegt werden soll. Das entspricht dem Schwierigkeitsgrad Einfach (von der Geschwindigkeit her). Mittel wäre es z.B. wenn alle 8 Durchläufe, und Schwer wenn alle 6 Durchläufe bewegt und gezeichnet wird. ( IsInt($Counter/(10-($Schwierigkeit-1)*2)) )


    Da Jetzt Schwierigkeit und Grundlagen der Spielmechanik eingebaut sind kümmern wir nun um die (Bonus-)Punkte die die Schlange Futtern soll um zu wachsen. Auch hier nutzen wir erstmal Weiße Vierecke (wie kreativ).


    Ein $Bonus ist definiert durch Die Position, Breite und Höhe. Wir brauchen also ein Array welches Die Position beinhaltet, da die Breite und Höhe die Schrittlänge der Schlange ist. ( Local $Bonus[2] )


    Nun legen wir die Funktion _SetzeBonus(ByRef $Bonus, $Schrittlaenge) ein. Hier wird der Bonus Platziert. Der Aufruf kommt unmittelbar vor den Aufruf der _BewegeSchlange Funktion.


    Die Funktion soll prüfen, ob es einen Bonus gibt (also ob x und y Koordinaten vorhanden sind) und für den Fall, dass keiner existiert einen setzen. Dabei muss auf das Raster ($Schrittlaenge) geachtet werden. Anschließend können wir ihn auch direkt Zeichnen. Dann wird keine weitere Funktoin benötigt.



    Jetzt muss aber auch etwas passieren, wenn man den Bonus trifft. Dazu brauchen wir eine Funktion die zwei Vierecke auf Kollision überprüft. Das ist mathematisch eine ganz einfache Sache, die ich jetzt nicht ausführlich erklären will.


    Func _Kollision($x1, $y1, $b1, $h1, $x2, $y2, $b2, $h2)
    Return ($x1 + $b1 > $x2 And $y1 + $h1 > $y2 And $x1 < $x2 + $b2 And $y1 < $y2 + $h2)
    EndFunc


    Angegeben werden müssen, für jedes der beiden Vierecke, jeweils die x- und y Koordinaten, sowie Breite und Höhe.


    Begeben wir uns nun ans Ende der Schleife der Funktion _BewegeSchlange. Nachdem in Fahrtrichtung bewegt wurde muss überprüft werden, ob eventuell ein Bonus getroffen wurde. Wir nutzen die eben kopierte Funktion und füttern sie mit den Angaben des Schlangenkopfes und des Bonusvierecks. Damit das klappt muss der $Bonus per ByRef in die Funktion eingebaut werden. ( _BewegeSchlange(ByRef $Snake, $Schrittlaenge, $Richtung, ByRef $Bonus) ).


    Wir bauen eine weitere Lokale Variable ein, die Verrät, ob die Schlange einen Bonus $Getroffen hat oder nicht. Nach der Schleife können dann die Konsequenzen daraus gezogen werden.


    If _Kollision($Snake[$i][0], $Snake[$i][1], $Schrittlaenge, $Schrittlaenge, $Bonus[0], $Bonus[1], $Schrittlaenge, $Schrittlaenge) Then $Getroffen = True
    Die Konsequenz ist, dass die Schlange um ein Glied wächst und der Bonus neu Platziert wird.


    If $Getroffen Then


    ReDim $Snake[$UBound + 1][2]
    $Snake[$UBound][0] = 0
    $Snake[$UBound][1] = 0


    $Bonus[0] = ''


    EndIf


    Jetzt tritt das Phänomen auf, dass wir alle Schlangenteile Lenken. Im Prinzip wollten wir aber nur das Vorderste Lenken und die Anderen hinten anhängen. Wir nehmen also flux die Richtungsabfrage zusammen mit der Kollisionskontrolle aus der Schleife und setzen sie dahinter. Dann müssen alle $i durch eine 0 ersetzt werden. Jetzt lenken wir nur noch den Kopf der Schlange.


    Die Schleife (die nun leer sein sollte) muss nun rückwärts ablaufen und so die Positionen der Schlangenteile Aktualisieren. Dabei wird immer einfach die Position des weiter vorne befindlichen Schlangenteils übernommen.


    For $i = $UBound - 1 To 1 Step -1 ; Für jedes Element der Schlange


    $Snake[$i][0] = $Snake[$i-1][0]
    $Snake[$i][1] = $Snake[$i-1][1]


    Next
    Nun sollten wir schon im Besitz eines Spielbaren Prototyps von Snake sein. Er beinhaltet viele Schönheitsfehler und kann noch nicht Beendet werden, weil man noch nicht stribt, wenn man sich selbst trifft.


    Erstmal beseitigen wir den Schönheitsfehler immer wenn wir einen Bonus futtern, indem wir
    $Snake[$UBound][0] = 0
    $Snake[$UBound][1] = 0durch
    $Snake[$UBound][0] = $Snake[$UBound-1][0]
    $Snake[$UBound][1] = $Snake[$UBound-1][1]ersetzen.


    Jetzt fehlt nur noch die Kontrolle, ob der Schlangenkopf ein Schlangenteil trifft. Das kann man mit einer weiteren Schleife nach der Richtungsabfrage lösen. Wir brauchen jetzt eine Globale Variable die anzeigt ob das Spielende erreicht wurde. ( $Ende = False )


    Es Folgt eine Kollisionsabfrage mit dem Schlangenkopf und jedem Schlangenteil in der eben genannten Schleife die bei 1 beginnt und bei $UBound - 1 endet. Ist die Abfrage positiv wird $Ende auf True gesetzt.


    Um damit eine Auswirkung zu erzielen muss in der Funktion _Spiel() die While Schleife angepasst werden. ( While Sleep(10) And Not $Ende ). Nach dem Ende der Schleife muss $Ende wieder auf False gesetzt werden, damit die Ausgangssituation beim nächsten Spiel wieder identisch ist.


    Nach erfolgreichem Einbau der genannten Modifikationen ist man Besitzer eines schon gut Spielbaren Snake Spiels. Es Folgt nun nur noch der Ausbau, und das Beseitigen von kleinen Fehlern.



    Zu guter Letzt schmeißen wir alle Funktionen die wir nicht mehr brauchen ans Ende des Skripts, wo sie nicht im Weg liegen. (_Spiel, _SetzeBonus, _BewegeSchlange, _ZeichneSchlange, _Kollision)


    4: Programmabschnitt: Highscore


    Der Highscore wird in einer Datei gespeichert werden. Dazu ist ein Global verfügbarer $Pfad = @ScriptDir & '\' nötig. (geht auch Lokal, aber dann sind Pfadänderungen wesentlich schwieriger).


    Zuerst überlegen wir uns, wie der Highscore aufgebaut sein soll. Optimal ist eine Speicherung von einem vom Spieler ausgesuchten $Namen (Lokal) und der erreichten $Punktzahl (Global). Dazu Später.


    Schema:
    Berty,100;Bob,50;Dieter,20;Siggi,10


    Wir brauchen eine Funktion die uns einen "Standard" Highscore (mit vorgegebenen Werten) ausspuckt. Diese nennen wir einfach _GibHighscore() und füllen sie mit 10 beliebigen Namen und Punktzahlen.


    Func _GibHighscore()
    Local $b = ''
    $b &= 'Mars,200;'
    $b &= 'DerBub,180;'
    $b &= 'Siggi,170;'
    $b &= 'Bert,160;'
    $b &= 'MrX,140;'
    $b &= 'Mario,130;'
    $b &= 'Luigi,120;'
    $b &= 'Bowser,110;'
    $b &= 'Toad,100;'
    $b &= 'DerLappen,70'
    Return $b
    EndFunc
    Jetzt fehlen uns eine Namenseingabe und Punktevergabe.


    Die $Punktzahl ist eine weitere Globale Variable. Nach kurzer Suche finden wir die _BewegeSchlange Funktion. Nach der Abfrage von $Getroffen fügen wir eine Erhöhung der $Punktzahl ein. (Wie viel das ist kann jeder mit sich selbst ausmachen.). Anschließend setzen wir ein $Punktzahl = 0 an den Anfang der _Spiel Funktion, damit jeder immer bei 0 startet.


    Zur Namenseingabe erstellen wir zuerst die lokale Variable $Name in _Highscore(). Um den Namen zuzuweisen ist eine _Namenseingabe() Funktion gut geeignet, da der Nutzer eventuell falsche/zu viele Zeichen nutzt und so den Highscore zerstört.


    Für die Funktion kopieren wir uns den Inhalt der _InputBox und streichen ein Paar Elemente. Übrig Bleiben sollte ein leeres GUI das schließbar ist und die Maße von 260x110 (vorschlag!) besitzt.



    Die Rückgabe soll ein String sein der einen Namen enthält. Sonderzeichen sind teilweise erlaubt. (z.B. , oder ; dürfen nicht benutzt werden, da diese zum Aufbau des Highscores dienen).


    Gebraucht wird ein Button falls der spieler seinen Namen fertig eingegeben hat und ihn Bestätigen will, ein Label welches den Aktuell eingegebenen Namen (nach dem Entfernen aller unerwünschter Zeichen) anzeigt und ein Inputfeld wo man seine Wut auslassen kann.


    Das kann auch jeder bauen wie er lustig ist. meine Lösung sieht folgendermaßen aus:


    Sollte der Highscore aufgerufen werden muss zuerst ein eventuell vorhandener Highscore geöffnet werden um die Informationen zu erhalten, ob man einen Platz unter den TopTen geschafft hat oder nicht. Die Funktion _LeseHighscore wird zum Zweck erstellt erst einmal zu prüfen ob es einen Score gibt. Sollte keiner Vorhanden sein wird einer angelegt. Rückgabe ist ein String mit gelesenem Highscore.


    Func _LeseHighscore()
    If Not FileExists($Pfad) Then FileWrite($Pfad, _GibHighscore())
    Return FileRead($Pfad)
    EndFunc
    Die Rückgabe können wir nun per Stringsplit aufteilen um an die einzelnen Komponenten zu kommen. Der erste Stringsplit Teilt den Highscore in Datensätze die jeweils einen Namen und die dazugehörige Punktzahl enthalten. Die Ergebnisse speichern wir in einem lokalen Array. ( $Array_Highscore[11][2] ).


    Um zu einem Auswertbaren Highscore zu kommen muss alles ins Array geschrieben werden. Da man immer konstant 10 Einträge (TopTen) hat kann man einfach von 0 bis 9 in einer For Schleife die Ergebnisse des ersten StringSplit abarbeiten und weiter teilen.


    Ist das Array komplett wird per _ArraySort sortiert, sodass die höchste Punktzahl bei 0 ist. ( #include <Array.au3> ) Anschließend kann man seine erreichte Punktzahl mit der Niedrigsten Punktzahl vergleichen um festzustellen, ob man tatsächlich einen schon vorhandenen Score überbieten kann.


    Dann nutzen wir das bisher überflüssige 11te Teil des Arrays und füllen es mit unserem Namen und den Punkten. Nochmals ein _ArraySort und unser Score ist richtig einsortiert. Jetzt kann das Ende des Arrays was jetzt den schlechtesten Score (der, der wegfällt) beim zurückumwandeln in den Highscore Sting übergangen werden.



    Verwaltet wird der Highscore nun korrekt. Nur angezeigt wird er noch nicht. Das wird die nächste kleine Aufgabe. Als Ausgang benutzen wir wieder das Gerüst der InputBox und schmeißen alles raus. Dieses Mal soll unten in der Mitte ein Button übrig bleiben und die Größe 200x400 betragen. Benannt wird unsere neue Funktion _ZeigeHighscore($Array_Highscore). Ein Returnwert wird nicht benötigt.


    Eine Mögliche Lösung ist folgendes:


    5. Schönheitsfehler und Sonstige Kleinigkeiten


    Man könnte denken, dass jetzt Schluss ist, aber jetzt fängt der Spaß erst richtig an. Nach mehrmaligem spielen wird etwas entdeckt. Erstmal muss verhindert werden, dass der Bonus unter der Schlange entspringt. Wir erstellen unter den GDI+ Variablen eine Rote Brush. (Dispose!) $hBrush_Rot = _GDIPlus_BrushCreateSolid(0xFFFF0000)


    Die Funktion _SetzeBonus muss also umgestaltet werden. Als Erstes kann die Farbe auf Rot gesetzt werden um das Problem bei Bedarf leichter rekonnstruieren zu können. (Man erkennt dann besser ob kein Rot zu sehen ist. Dann ist der Bonus unter der Schlange).


    Mit einer lokalen Variable die beinhaltet, ob man $Nochmal versuchen muss einen Platz zu bekommen der nicht belegt ist, kann man das Problem lösen. In einer Do Until Not $Nochmal Schleife erstellt man Positionen für den Bonus und prüft anschließend auf Kollision mit jedem Schlangenteil incl Kopf. Wurde ein Teil getroffen setzt man $Nochmal auf True um eine Neue Position zu bekommen.


    Func _SetzeBonus(ByRef $Bonus, $Schrittlaenge, $Snake)


    Local $Nochmal


    If Not IsNumber($Bonus[0]) Or Not IsNumber($Bonus[1]) Then


    Do
    $Nochmal = False


    $Bonus[0] = Random(0, $Breite / $Schrittlaenge - 1, 1) * $Schrittlaenge
    $Bonus[1] = Random(0, $Hoehe / $Schrittlaenge - 1, 1) * $Schrittlaenge


    For $i = 0 To UBound($Snake, 1) - 1 Step 1
    If _Kollision($Snake[$i][0], $Snake[$i][1], $Schrittlaenge, $Schrittlaenge, $Bonus[0], $Bonus[1], $Schrittlaenge, $Schrittlaenge) Then $Nochmal = True
    Next


    Until Not $Nochmal


    EndIf


    _GDIPlus_GraphicsFillRect($hBackbuffer, $Bonus[0], $Bonus[1], $Schrittlaenge, $Schrittlaenge, $hBrush_Rot)


    EndFunc
    Uns stört auch, dass man stribt, wenn man entgegen seiner Richtung lenkt. Passen wir also die Lenkung in der _Spiel() Funktion dementsprechend an. Zu den Tastaturabfragen muss also noch hinzu kommen, dass die Zielrichtung nicht die Entgegengesetzte Richtung ist zu der zuletzt benutzen Richtung.



    Schon lässt sich alles Optimal lenken ohne unnötige Tode, wenn man sich mal vertippt.


    Nächstes Problem ist: Wie lang ist die Schlange überhaupt bzw. wie viele Punkte habe ich ?.


    Eine Methode wäre es bei jedem Bonus im Kopf die Länge auszurechnen. Das wird aber schnell anstrengend und nimmt die Lust am Spiel. Zum Glück haben wir die Variable $Punktzahl die uns die Arbeit schon abgenommen hat. Wir müssen sie nur noch "sichtbar" machen. Das klingt nach Arbeit für die DrawStringEx Funktion.


    Benötigt werden dazu ein Handle zu einer Schrift ($hFont). Dieses legen wir natürlich sofort bei den GDI+ Variablen an. An dieser Stelle merkt man, dass man ein Handle für die FontFamily benötigt. Wiederwillig können wir aber auch dies eine Zeile über unserem Fonthandle anlegen um einem Error vorzubeugen. Desweiteren wird ein $hFormat benötigt und ein gewisses $tLayout, welches wir aber vor Ort lokal anlegen werden.


    Global $hFamily = _GDIPlus_FontFamilyCreate('Arial')
    Global $hFont = _GDIPlus_FontCreate($hFamily, 12)
    Global $hFormat = _GDIPlus_StringFormatCreate()
    Wie versprochen lassen wir das $tLayout nicht im Stich. Lokal in der _Spiel() Funktion ist das Layout neben den anderen Variablen nicht so allein und erfreut sich guter Gesundheit. ( $tLayout = _GDIPlus_RectFCreate(10, 10, 150, 30) )


    Nach dem Aufruf von _ZeichneSchlange bietet sich ein ausgezeichneter Platz um per StringDrawEx die Punktzahl zu Zeichnen.


    Dann gehen wir davon aus, dass der Spieler nachdem er verloren hat keine Lust mehr hat weiter zu spielen. Im Highscore macht sich ein Button mit der Aufschrift "Ende" also nicht schlecht neben dem Fertig Button.



    6. Grafik


    Nachdem das Spiel Technisch ausgereift ist und alles so läuft wie man es gerne hätte können wir uns um die Grafik kümmern.


    Etwas Eindruck schinden kann man immer mit Farbübergängen statt einfarbiger Füllung. Dazu bietet sich die Funktion _GDIPlus_LineBrushCreate (Befindet sich NICHT in der GDIPlus.au3, sondern in der GDIP.au3 die nicht im Standard von AutoIt enthalten ist).



    Damit ersetzen wir erstmal ganz Oben bei den GDI+ Variablen die normale $hBrush durch _GDIPlus_LineBrushCreate(0,0, $Breite, $Hoehe, 0xFFFF4040, 0xFFFFFFFF)


    Für die Punkteanzeige benutzen wir auch eine Neue Brush. ( $hBrush_Punkte = _GDIPlus_LineBrushCreate(0,0, 150, 150, 0xFF40FF40, 0xFFFFFFFF) )


    Mit den folgenden 2 Zeilen, direkt nach den GDI+ Variablen lässt sich die Textqualität und die Anzeigequalität etwas erhöhen.
    _GDIPlus_GraphicsSetSmoothingMode($hBackbuffer, 2)
    DllCall($ghGDIPDll, 'uint', 'GdipSetTextRenderingHint', 'hwnd', $hBackbuffer, 'int', 4)
    Der Bonus soll auch eine neue Brush erhalten. $hBrush_Rot = _GDIPlus_LineBrushCreate(0,0, $Breite, $Hoehe, 0xFFFFFFFF, 0xFF4040FF) Das ist das zwar nicht mehr Rot, aber so muss keine neue Variable angelegt werden.


    Hat alles geklappt ?
    Dann solltest Du jetzt stolzer Besitzer eines Spiels sein, welches Du je nach C&P Wut zu einem großen Teil alleine geschrieben hast.


    Edit: Die Namenseingabe enthielt einen kleinen Fehler, sodass immer Statist Nummer xyz zurückgegeben wurde.


    lg
    Mars(i)


  • [Blockierte Grafik: http://i.imgur.com/oE7D7.png]


    lg
    Mars(i)

  • hmm...


    also, dass es hackt kann ich nicht bestätigen.


    Es hat den Anschein, wenn man mit einer 4 Punkte langen Schlange schnell dreht, aber das liegt daran, dass sich alle Teile ein Mal bewegen und das Endergebnis identisch aussieht. Wenn man den Kopf anderst färbt tritt diese illusion nicht auf.


    lg
    Mars(i)