Bauforum-Logo

Forum Bauen und Umwelt

log in | registrieren

zurück zum Forum
  Mix-Ansicht

Autopilot (Software)

verfasst von Martin Vogel Homepage E-Mail, Dortmund / Bochum, 03.07.2017, 23:28 Uhr

Da Menschen wohl nicht in der Lage sind, so ein perfektes Spiel hinzubekommen …

[image]

… habe ich mal einen Autopiloten in das Hamster-vs-Python-Spiel eingebaut, der mit Pos1 (Home) gestartet werden kann. Er reicht allerdings bei Weitem nicht an den Algorithmus aus dem GIF oben heran.

[image]

Verbesserungsvorschläge sind willkommen.

Download: [image]ZIP-Archiv_DTN3OFPF1.zip

#! /usr/bin/python3

from tkinter import Tk, Canvas, PhotoImage
from tkinter.messagebox import showinfo, askyesno
from tkinter.simpledialog import askstring
from random import randint, choice, sample
from getpass import getuser
from webbrowser import open as showpage
from urllib.parse import urlencode
from time import sleep, strftime

# Python vs. Hamster
# ~~~~~~~~~~~~~~~~~~
#
# Ein Beispielprogramm zur Auswertung der Daten von Eventhandlern für
# Tastendrücke und zur simplen Datenübermittlung an eine Webseite
#
# Version vom 1. Juli 2017
# Autor: Dipl.-Ing. Martin Vogel, Hochschule Bochum
# Lizenz: cc-by-3.0 https://creativecommons.org/licenses/by/3.0/de/
#
# Diese Abwandlung des klassischen Snake-Spiels lässt eine Schlange
# kleine blaue Java-Hamster fressen.
#
# Das Spielfeld ist 29×29 Felder groß und wird auf einer Canvas in 24-facher
# Größe wiedergegeben.
#
# Die Schlange wird mit den Cursortasten gesteuert.
#
# Jeder Hamster lässt die Schlange um 5 Segmente wachsen und 5% schneller
# werden. Für den ersten Hamster gibt es 100 Punkte, für den zweiten 200
# und so weiter. Für jeden Bewegungsschritt wird wieder ein Punkt abgezogen.
#
# Das Spiel endet, wenn die Schlange gegen die Wand stößt oder mit sich
# selbst kollidiert.
#
# Auf einer Webseite lässt sich durch das Programm ein Highscorewert eintragen.
url = "http://ruhrhochschule.de/hamsterscore.py"
#
# Diese Version des Spiels verfügt über einen Autopiloten, der mit "Pos1"
# gestartet, mit "End" in den Turbomodus geschaltet und mit den Cursortasten
# wieder beendet wird.
Algorithmusname = "Große Welle 5"
#
#
# Vorlage für den Hamsterkopf war das bekannte Motiv von Daniel Jasper und
# Dietrich Boles aus ihrem freien Programmierlernprogramm Hamster-Simulator.
#


class countdown:
    "Zählt von 3 bis 1 rückwärts."
    # Jede anfangs riesige bildfüllende Ziffer wird innerhalb einer Sekunde
    # immer kleiner.
    def __init__(self, event=None):
        # Mauszeiger für die Canvas abschalten
        C.config(cursor="none")
        # Bildschirm aufräumen
        C.delete("Schlange")
        Hamster.verstecken()
        # Anfangswert
        self.counter = 3
        # Ziffer rot und mittig anzeigen
        self.text = C.create_text(360, 360, fill="red")
        # Zähler starten
        self.count()
        

    def count(self):
        "Animationsschritt des Countdowns"
        # Das Attribut counter zählt Sekundenbruchteile; angezeigt werden nur
        # die ganzen Sekunden.
        n = int(self.counter) + 1
        # Die Zifferngröße in Pixeln entspricht dem Nachkommawert mal 1000
        s = (self.counter*1000) % 1000
        # Neue Größe setzen
        C.itemconfig(self.text, text=n, font="helvetica -%i bold" % s)
        # Alle 17 ms soll ein Animationsschritt erfolgen.
        self.counter -= 0.017
        if self.counter > 0 and not Schlange.Turbo:
            C.after(17, self.count)
        else:
            C.delete(self.text)
            Schlange.Start()


def username():
    "Gibt den Spielernamen zurück"
    # Wurde der schon eingegeben?
    if hasattr(Hamster, "username"):
        un = Hamster.username
    else:
        # Fragen wir doch mal das Betriebssystem nach dem Anmeldenamen:
        un = getuser()
        # Dieser kann übernommen oder geändert werden.
        un = askstring("Hallo, äh … "+un+"?",
                       "Gib Deinen Namen ein:",
                       initialvalue=un)
        # Wurde die Eingabe mit "OK" bestätigt?
        if un:
            Hamster.username = un
        # Oder wurde der Eingabedialog abgebrochen?
        else:
            un = ""
    return un


class C_Hamster:
    """Der Hamster weiß, wo er ist, wie er aussieht und alles wichtige
       über den Spieler."""
    def __init__(self):
        # Das Hamsterbild (24×24 Pixel, GIF) muss im Verzeichnis des
        # Pythonprogramms liegen.
        try:
            # Wenn es da ist, merkt sich der Hamster sein Aussehen.
            self.bilddatei = PhotoImage(file="Hamster24.gif")
            self.bild_ID = C.create_image(-24, -24, image=self.bilddatei)
        except:
            # Falls nicht, gibt's 'nen schnöden Ersatzhamster: ⬤ \u2b24
            self.bild_ID = C.create_text(-24, -24, text="⬤",
                                         font="courier -24 bold",
                                         fill="blue")
        # Immer, wenn die persönliche Bestleistung übertroffen wird,
        # gibt es das Angebot, es in die Highscoreliste einzutragen.
        self.highscore = 0

        # Zu Beginn jeder Runde:
        self.reset()

    def reset(self):
        # Anzahl der Wiederbelebungen
        self.zähler = 0

        # Aktuell erreichte Punktzahl
        self.punkte = 0

        #
        # Philosophischer Einschub:
        #
        # F: „Wieso bekommt eigentlich der Hamster die Punkte und nicht die
        #    Schlange?“
        #
        # A: „Nun, wer glaubt, dass es in diesem Spiel darum geht, Hamster zu
        #    besiegen, hat die Fabel vom Hasen und Igel nicht verstanden.
        #    In Wirklichkeit gewinnt nämlich immer der Hamster die Spielrunden
        #    und die Schlange stirbt.“
        #
        # F: „…“
        #
        # A: „Schockierend. Ich weiß.“
        #

        #
        # F: „Mooment! Wieso gibt es dann umso mehr Punkte für den Hamster, je
        #    häufiger die Schlange gefüttert wird?“
        #
        # A: „*seufz* OK, das war vorhin doch nicht die ganze Wahrheit über das
        #    Spiel; in wirklicher Wirklichkeit gibt es die Punkte für die
        #    heimliche, weil illegale, Entsorgung der ganzen veganen
        #    Biodinkelriegel, die der Hamster versehentlich gehortet hat und
        #    die er nun an die Schlange verfüttert.“
        #
        # Jetzt aber weiter im Quelltext:
        #

    def umsetzen(self):
        """Setzt den Hamster auf ein zufälliges Feld, das noch nicht von
        der Schlange belegt ist."""
        
        # Liste der freien Koordinaten, die mindestens 2 Felder vom Rand
        # entfernt sind (der Hamster soll nicht am Rand oder in einer
        # Ecke hocken):
        Frei = [(x, y) for x in range(3, 28) for y in range(3, 28)
                if (x, y) not in Schlange.Felder]
        
        for Versuch in range(100, 0, -1):
            # irgendein freies Feld auswürfeln
            self.xy = choice(Frei)
            # gewünschter Abstand
            Abstand = Versuch//10 + 1
            # Wenn die Schlange zu dicht ist, ist das Feld tabu
            Tabu = False

            # Nachbarschaft scannen
            for x in range(self.xy[0]-Abstand, self.xy[0]+Abstand+1):
                for y in range(self.xy[1]-Abstand, self.xy[1]+Abstand+1):
                    if (x,y) in Schlange.Felder:
                        Tabu = True
                        break
                if Tabu: break
            
            if not Tabu: 
                # Freies Feld gefunden. Schleife abbrechen!
                break

        # Das Hamsterbild auf die neue Canvas-Position verschieben:
        x, y = self.xy
        C.coords(Hamster.bild_ID, 24*x, 24*y)

        # Titelzeile des Fensters aktualisieren
        if Schlange.benutzeAutopilot:
            s = Algorithmusname+" sucht den"
        else:
            s = "Füttere den Python mit dem"
        T.title(s+" %i. Java-Hamster!" %
                (self.zähler+1))

    def verstecken(self):
        "Setzt den Hamster auf eine nicht sichtbare Koordinate"
        C.coords(Hamster.bild_ID, -100, -100)


def Highscore(Grund, Punkte):
    "Zeigt den Highscore-Dialog an"

    # Mauszeiger für die Canvas wieder einschalten
    C.config(cursor="arrow")
    
    # Kennen wir uns?

    try:
        un = ", "+Hamster.username
    # nö.
    except AttributeError:
        un = ""

    # Wie laut müssen wir jubeln?

    if Punkte > Hamster.highscore:
        # Konfetti, Sekt, Fanfaren!
        Jubel = "Bravo"+un+", das ist Deine neue persönliche "\
                "Bestleistung!"
        # Das wird der neue Highscore
        Hamster.highscore = Punkte

    elif Punkte:
        # Schlechter geworden?
        Jubel = "Beim nächsten Mal schaffst Du wieder mehr"+un+"!"

    else:
        # 0 Punkte? Sei trotzdem nett zu dem N00b:
        Jubel = "Tipp: Steuere den Python mit den Pfeiltasten!"

    # Bei menschlichen Spielern Dialog anzeigen, bei Autopilot
    # nur protokollieren.
    if Schlange.benutzeAutopilot:
        # Datum und Uhrzeit für die Logdatei
        jetzt = strftime("%F %T")
        # Jedes einzelne Ergebnis wird in der großen Logdatei protokolliert
        with open(Algorithmusname+".log","a") as log:
            print(jetzt,Punkte,sep="\t",file=log)
        # Neuer Highscore?
        if Hamster.highscore == Punkte:
            # Kleine Logdatei nur für neue Highscores
            with open(Algorithmusname+"_highscore.log","a") as log:
                print(jetzt,Punkte,sep="\t",file=log)
            # Canvas als Bilddatei speichern    
            C.postscript(file=Algorithmusname+strftime(" %F %T ")
                         +str(Punkte)+".eps",
                         colormode='color')
            # Highscore auf der Website eintragen
            showpage(url+"?"+urlencode((("n", Algorithmusname),
                                        ("p", Punkte),
                                        ("b", 1))))
        T.title("Algorithmus \""+Algorithmusname+
                "\" wird fortgesetzt.")
        countdown()
    elif askyesno(Grund, "Du hast %i Hamsterpunkte!\n\n%s" % (Punkte, Jubel) +
                  "\n\nMöchtest Du Dich in die Highscoreliste eintragen?"):
        # Highscore auf der Website eintragen
        if Schlange.AutopilotWurdeVerwendet:
            showinfo(title="Problem",
                     message="Du hast den Autopiloten verwendet",
                     detail="Einträge in die Highscoreliste sind nur möglich, "
                     "wenn die Schlange während der ganzen Runde von Hand "
                     "gesteuert wurde.")
        else:
            showpage(url+"?"+urlencode((("n", username()),
                                        ("p", Punkte))))
        T.title("Neustart mit der Eingabetaste!")
    else:
        T.title("Mache Dich bereit!")
        sleep(1)
        countdown()

class C_Schlange:
    """Die Schlange merkt sich die durch sie belegten Felder und deren
    Canvas-Objekte sowie die letzte Hamstermahlzeit."""

    def __init__(self):
        "Die Schlange schlüpft."
        # Sie bewegt sich noch nicht vom Fleck.
        self.dx = self.dy = 0
        self.benutzeAutopilot = False
        self.Turbo = False
        self.reset()

    def reset(self):
        "Startwerte der Schlange werden gesetzt."
        # Zufällige Startpostition 10 Felder vom Rand entfernt
        self.x = randint(10, 20)
        self.y = randint(10, 20)
        # Zufällige Richtung: nach rechts, links, oben oder unten.
        # Setzt dx und dy entsprechend auf -1, 0 oder +1.
        self.Richtung(choice("rlou"))
        # Die Elemente der Liste "Felder" bestehen aus (x,y)-Tupeln.
        # Der Kopf der Schlange ist auf (self.x, self.y).
        self.Felder = [(self.x, self.y)]
        # Fünf Rumpfsegmente entgegen der Bewegungsrichtung anhängen:
        for i in range(1, 6):
            self.Felder.append((self.x-i*self.dx, self.y-i*self.dy))
        # Wartezeit zwischen zwei Schritten: anfangs 100 Millisekunden,
        # später wird die Schlange immer schneller.
        self.ms = 100
        # Die Schlange braucht 5 Schritte, um eine Mahlzeit zu verdauen und
        # dabei zu wachsen. Jetzt ist der Magen gerade leer.
        self.Mageninhalt = 0
        # Falls pro Bewegungsschritt mehrere Tasten gedrückt werden,
        # kommen die in einen Puffer, sodass pro Schritt nur eine
        # Richtungsänderung vorgenommen wird.
        self.Tastenpuffer = []
        # Highscoreeinträge der Kategorie "menschlicher Spieler" sollen
        # nur möglich sein, wenn zwischendurch nicht nachgeholfen wurde.
        self.AutopilotWurdeVerwendet = self.benutzeAutopilot

    def Richtung(self, rlou):
        "Setzt den Delta-x- bzw. -y-Wert für die nächsten Bewegungsschritte"
        # Unmittelbar tödliche 180°-Kehren werden nicht zugelassen
        if rlou == "l":
            if not self.dx:
                self.dx = -1
        elif rlou == "r":
            if not self.dx:
                self.dx = 1
        else:
            self.dx = 0
        if rlou == "u":
            if not self.dy:
                self.dy = 1
        elif rlou == "o":
            if not self.dy:
                self.dy = -1
        else:
            self.dy = 0

    def Taste(self, key):
        "Hängt einen Tastendruck an den Eingabepuffer an."
        self.Tastenpuffer.append(key)
        if self.benutzeAutopilot:
            T.title("Autopilot ausgeschaltet! Manuelle Steuerung aktiv.")
            self.benutzeAutopilot = False
            self.Turbo = False

    def zeichneKopf(self):
        "Setzt den Kopf in richtiger Orientierung auf sein neues Feld."
        x, y = self.Felder[0]
        # Trapezförmigen Kopf je nach Bewegungsrichtung drehen:
        C.coords(self.Kopf,
                 24*x-10-2*self.dy, 24*y-10-2*self.dx,
                 24*x+10+2*self.dy, 24*y-10+2*self.dx,
                 24*x+10-2*self.dy, 24*y+10-2*self.dx,
                 24*x-10+2*self.dy, 24*y+10+2*self.dx)
        # Augenlinie (gestrichelt) von links nach rechts:
        C.coords(self.Augen,
                 24*x-2*self.dx-10*self.dy, 24*y-2*self.dy-10*self.dx,
                 24*x-2*self.dx+10*self.dy, 24*y-2*self.dy+10*self.dx)

    def Start(self, event=None):
        "Setzt den Hamster um und baut die Schlange neu auf"
        # Die tatsächlichen Koordinaten einiger Elemente werden
        # erst bei Bewegung der Schlange gesetzt. Sie werden außerhalb
        # der Canvas zwischengelagert.
        außerhalb = -99, -99, -99, -99
        # Schlange zeichnen: zuunterst liegt eine dicke blaue Linie.
        # Gleich kommt noch eine zweite Linie hinzu, für sie ist hier der
        # Platzhalter "None".
        self.Körper = [C.create_line(self.Felder,
                                     fill="blue", smooth=1, tag="Schlange",
                                     joinstyle="round", capstyle="round",
                                     width=24),
                       None]

        # Der Kopf der Schlange wird ein Trapez, dessen kurze Seite stets in
        # Bewegungsrichtung zeigen wird.
        self.Kopf = C.create_polygon(außerhalb, width=4, fill="yellow",
                                     outline="blue", joinstyle="bevel",
                                     tag="Schlange")

        # Auflösung des None-Rätsels von oben: Die gelbe Füllung des Körpers
        # liegt in der Zeichenreihefolge über dem Kopf, damit die "Nackenlinie"
        # nicht zu sehen ist. Beide Körperlinien werden in einer Liste
        # gespeichert, damit sie mittels einer Schleife modifiziert werden
        # können.
        self.Körper[1] = C.create_line(self.Felder,
                                       fill="yellow", smooth=1, tag="Schlange",
                                       joinstyle="round", capstyle="round",
                                       width=16, dash=(1,15))

        # Die Strichelung der gelben Körperlinie kann dazu führen, dass
        # ein großes Stück des Schwanzes blau bleibt. Hier tupfen wir ein
        # wenig Make-Up in Form einer Kreisscheibe nach.
        self.Schwanzspitze = C.create_oval(außerhalb, fill="yellow",
                                           outline="yellow", tag="Schlange")

        # Die Augen liegen zuoberst und bestehen aus einer einzigen
        # geschickt gestrichelten Linie: "*  *".
        self.Augen = C.create_line(außerhalb, width=8, fill="black",
                                   tag="Schlange", capstyle="round",
                                   dash=(5,11,5))
        
        # Hamster umsetzen
        # (In Wahrheit wird der nämlich gar nicht gefressen, sondern immer
        #  rechtzeitig weggebeamt. Die arme Schlange erhält stattdessen
        #  einen veganen Biodinkelriegel.)
        # (Ja, sowas erfährt man nur, wenn man Quelltextkommentare liest.)
        Hamster.umsetzen()
        # Go!
        C.after(self.ms, self.Bewege)

    def Stirb(self, innenFarbe, außenFarbe):
        "Animation zeigt durch Farbveränderung das Ende der Spielrunde an."
        # Die Schlange verändert innerhalb einer Sekunde ihre
        # Farbe vom Kopf bis zur Schwanzspitze. Der nach jedem Abschnitt
        # erfolgende Aufruf von „update_idletasks“ sorgt dafür, dass die
        # Änderung sofort angezeigt wird und nicht erst, wenn das Programm
        # wieder in der Mainloop ist.

        # Im Turbomodus entfällt diese hübsche Animation.
        if self.Turbo:
            return
        
        # Kopf heben und Augen weit aufreißen!
        C.tag_raise(self.Kopf)
        C.tag_raise(self.Augen)
        C.itemconfig(self.Augen, width=20)
        C.update_idletasks()
        sleep(0.1)

        # Augenfarbe ändern
        C.itemconfig(self.Augen, fill=außenFarbe)
        C.update_idletasks()
        sleep(0.1)

        # Kopfinneres umfärben
        C.itemconfig(self.Kopf, fill=innenFarbe)
        C.update_idletasks()
        sleep(0.1)

        # Kopfrand umfärben
        C.itemconfig(self.Kopf, outline=außenFarbe)
        C.update_idletasks()
        sleep(0.1)

        # Körperinneres umfärben
        C.itemconfig(self.Körper[1], fill=innenFarbe)
        C.itemconfig(self.Schwanzspitze, fill=innenFarbe, outline=innenFarbe)
        C.update_idletasks()
        sleep(0.1)

        # Augen schließen …
        C.itemconfig(self.Körper[0], fill=außenFarbe)
        for i in range(20,1,-1):
            C.itemconfig(self.Augen, width=i)
            C.update_idletasks()
            sleep(0.02)

        # R.I.P.
        pass
        

    def starteAutopilot(self):
        "Die Steuerung der Schlange wird von einem Algorithmus übernommen."
        self.benutzeAutopilot = True
        self.AutopilotWurdeVerwendet = True
        self.Turbo = False
        T.title("Autopilot aktiviert. "
                "Algorithmus \"%s\" arbeitet." % Algorithmusname)

    def starteTurbo(self):
        "Alle bremsenden Warteschleifen und Animationen deaktivieren"
        if self.benutzeAutopilot:
            self.Turbo = True
            T.title("Turbomodus aktiviert! Ende mit Pos1 oder Cursortasten")

    def Autopilot(self):
        "Setzt self.dx und self.dy auf Basis eines Algorithmus."
        # Algorithmus: "Große Welle 5"
        #
        # Strategie: Kürzesten Weg zum Hamster markieren, Inseln vermeiden
        #
        # Ausgehend vom Hamsterfeld werden wellenförmig Ringe aus den im
        # jeweils nächsten Schritt erreichbaren Feldern gebaut.
        # In die Felder wird die Anzahl der Schritte bis zum Hamster
        # eingetragen. Die Schlange muss nun nur noch in Richtung des Feldes
        # mit der niedrigsten Nummer gleiten.
        #       
        # 1. Matrix in Spielfeldgröße (29×29) anlegen, mit dem Wert 999 füllen.
        #    Schlangenfelder erhalten den Wert 1000+s, wobei s die Entfernung
        #    zum Kopf in Schlangensegmenten ist.
        #    Das Hamsterfeld erhält den Wert 0.
        #    Ringsum wird noch ein Rand mit Wert 9999 angeordnet, dadurch
        #    vermeiden wir Indexfehler beim Untersuchen der Nachbarfelder.
        # 2. Vom Hamsterfeld beginnend erhalten alle noch nicht bewerteten
        #    Nachbarfelder, die kein Schlangenfeld sind, den Wert n+1, wobei n
        #    die Entfernung des aktuellen Feldes zum Hamster ist.
        # 3. Schritt 2 wird für alle gefundenen Nachbarfelder wiederholt.
        # 4. Sobald die "Welle" den Schlangenkopf erreicht, ist der kürzeste
        #    Pfad gefunden.
        # 5. Die Schlange bewegt sich nun immer in Richtung des Feldes mit dem
        #    geringsten Wert. Falls die Möglichkeit besteht, durch
        #    Richtungswechsel zu einem gleichwertigen Feld am eigenen Körper
        #    entlang zu gleiten, wird das zur Vermeidung von Inseln ausgeführt.
        #    Stehen mehrere dieser Felder zur Auswahl, wird das mit den
        #    wenigsten Segmenten vom Schwanz entfernte Schlangensegment
        #    bevorzugt.
        #
        # Zu verbessern: Selbstmord durch Kurzsichtigkeit vermeiden!
        #
        # Es fehlt derzeit jede Rückzugstrategie. Die Schlange gleitet
        # gnadenlos in Sackgassen und verbaut sich selbst den Rückweg.
        # Die maximale Punktzahl dieser Strategie liegt bei etwa 450_000.
        #
        # So könnte eine optimale Lösung aussehen:
        # The Perfect Snake
        #   Mike James, 2013
        #   http://www.i-programmer.info/news/144-graphics-and-games/
        #          5754-the-perfect-snake.html
        #
        # Etwas simpler:
        #   https://www.youtube.com/watch?v=kTIPpbIbkos
        #
        # Sterbenslangweilig, aber deprimierend erfolgreich:
        #   http://www.datagenetics.com/blog/april42013/
        #
        # Literatur:
        # Snake: Artificial Intelligence Controller
        #   Patrick Merrill, 2011
        #   http://www.cs.unh.edu/~ruml/cs730/paper-examples/merrill-2011.pdf
        # Gaming is a hard job, but someone has to do it!
        #   Giovanni Viglietta, 2013
        #   https://arxiv.org/abs/1201.4995
        # A Knowledge-based Approach of Connect-Four
        #   The Game is Solved: White Wins
        #   Victor Allis, 1988
        #   http://www.informatik.uni-trier.de/~fernau/DSL0607/
        #          Masterthesis-Viergewinnt.pdf
        
        # Die Bewertungsmatrix
        self.A = [[999 for i in range(31)] for i in range(31)]
        
        # Randfelder
        for i in range(31):
            self.A[0][i] = 9999
            self.A[30][i] = 9999
            self.A[i][0] = 9999
            self.A[i][30] = 9999
            
        # Startfeld            
        xh, yh = Hamster.xy
        self.A[xh][yh] = 0

        # Die Segmente der Schlange, die vom Kopf nicht erreicht werden
        # können, müssen nicht eingetragen werden.
        
        # Kopfposition der Schlange
        xs, ys = self.Felder[0]
        # Alle Felder untersuchen
        for n, Feld in enumerate(self.Felder):
            x, y = Feld
            # Wenn der Kopf dieses Körpersegment in n Schritten
            # erreichen kann (unter der äußerst vereinfachenden Annahme,
            # dass der Weg dorthin frei sei):
            if abs(x-xs)+abs(y-ys) <= n + self.Mageninhalt:
                self.A[x][y] = 1000 + n

        # Die Liste "Aktuell" wird in jedem Schleifendurchlauf auf
        # neu zu untersuchende Nachbarn hin ausgewertet.
        # Diese landen zunächst in der Liste "Neu".
        Neu = [(xh,yh)]

        # Strecke bis zum Hamsterfeld
        n = 0

        # Wenn ein Pfad vom Schlangenkopf zum Hamster gefunden wurde,
        # ist der Suchalgorithmus fertig.
        fertig = False
        while not fertig:
            # Ein Schritt mehr zum Hamster
            n += 1
            # Die neue hinzugekommenen Felder aus dem letzten Durchlauf
            # werden zu den aktuell zu untersuchenden Feldern.
            Aktuell = Neu[:]
            # Die Liste "Neu" sammelt die Kandidaten für den nächsten
            # Schleifendurchlauf.
            Neu = []
            # Für alle Felder, die in diesem Durchlauf dran sind:
            for Ak in Aktuell:
                # Alle vier Nachbarfelder durchgehen:
                for x, y in ((Ak[0]-1,Ak[1]),(Ak[0]+1,Ak[1]),
                             (Ak[0],Ak[1]-1),(Ak[0],Ak[1]+1)):
                    # Ein freies Feld?
                    if self.A[x][y] == 999:
                        # Schrittzahl zum Hamster eintragen
                        self.A[x][y] = n
                        # Feld für den nächsten Durchlauf merken
                        Neu.append((x,y))
                    # Ein Schlangenfeld?
                    elif 9999 > self.A[x][y] >= 1000:
                        # Der Kopf?
                        if (x, y) == (xs, ys):
                            # Der Pfad vom Kopf zum Hamster ist gefunden!
                            fertig = True
                            break
                if fertig:
                    break
            # Wenn keine leeren Felder mehr erreichbar sind:
            if not Neu:
                # Suche ist abgeschlossen.
                fertig = True

        # "Impulserhaltung": die Schlange behält zunächst einmal ihre
        # Richtung bei.
        Zielwert = self.A[self.x+self.dx][self.y+self.dy]
        # Falls jedoch eines der in zufälliger Reihenfolge untersuchten
        # Nachbarfelder attraktiver ist oder gleichwertig, aber dem
        # schwanznächsten Schlangensegment benachbart ist …
        snSS = 1000
        for x, y in sample(((self.x-1, self.y),
                            (self.x+1, self.y),
                            (self.x, self.y-1),
                            (self.x, self.y+1)),4):
            # … weil es eine Abkürzung ist oder weil die Möglichkeit
            # besteht, am eigenen Körper entlangzugleiten und so Hohlräume
            # zu vermeiden …
            if self.A[x][y] < Zielwert or\
               self.A[x][y] == Zielwert and\
               9999 > self.A[x-self.dx][y-self.dy] >= snSS:
                # … dann wenden wir uns dem zu.
                Zielwert = self.A[x][y]
                self.dx = x-self.x
                self.dy = y-self.y
                # War das Nachbarfeld ein attraktiveres Schlangensegment?
                if 9999 > self.A[x-self.dx][y-self.dy] > snSS:
                    snSS = self.A[x-self.dx][y-self.dy]

    def Bewege(self):
        "Alle paar Millisekunden bewegt sich die Schlange ein Feld weiter"
        # Wurden zwischendurch eine oder mehrere Cursortasten gedrückt?
        if self.Tastenpuffer:
            # Hole den ältesten Tastendruck ab,
            # setze dx und dy entsprechend.
            self.Richtung(self.Tastenpuffer.pop(0))

        if self.benutzeAutopilot:
            # Lasse dx, dy automatisch setzen.
            self.Autopilot()

        # Neue Kopfposition: Nächstes Feld in Bewegungsrichtung
        self.x += self.dx
        self.y += self.dy

        # Ist da der Hamster?
        if (self.x, self.y) == Hamster.xy:
            # Punktzahl erhöhen.
            # Ja, der Hamster bekommt die Punkte!
            # Wer sagt, das Leben sei fair?

            # Zahl der Hamster (-umsetzungen …)
            Hamster.zähler += 1
            
            # Die zu addierende Punktzahl erhöht sich pro Hamster um 100.
            Hamster.punkte += 100 * Hamster.zähler
            
            # Ein Hamster verlängert die Schlange um 5 Felder.
            self.Mageninhalt = 5
            
            # Die Schlange wird 5% schneller.
            self.ms = int(0.95*self.ms)
            
            # Die nächste Mahlzeit materialisiert sich irgendwo.
            Hamster.umsetzen()

        # Spielfeldende erreicht?
        if self.x < 1 or self.x > 29 or self.y < 1 or self.y > 29:
            # Farbveränderung
            self.Stirb("black", "grey")

            # Auswertungsdialog
            Highscore("Der Python ist vor die Wand geprallt.", Hamster.punkte)
            # DER Python! Meine Güte, schau' in den Duden!

            # Schlange und Hamster zurücksetzen
            self.reset()
            Hamster.reset()
            return

        # Ist noch etwas vom letzten Hamster übrig?
        if self.Mageninhalt:
            # bisschen verdauen
            self.Mageninhalt -= 1
            # Die Schlange wächst nun. Weil gleich das letzte
            # Feld abgeschnitten wird, hängen wir ein Dummy-Feld an.
            self.Felder.append(self.Felder[-1])

        # Am Kopf ein neues Feld anfügen, Schwanzfeld entfernen.
        # Das Polygon wird als Trapez gezeichnet, dessen kurze Seite
        # in Bewegungsrichtung "schaut".
        self.Felder = [(self.x, self.y)] + self.Felder[:-1]
        self.zeichneKopf()
        
        # Die Schlange gleitet weiter. Die Schleife ist nötig, weil
        # der gelbe Schlangenkörper und der blaue Rand zwei eigene
        # Canvas-Objekte mit denselben Koordinaten sind.
        for K in self.Körper:
            C.coords(K, [xy*24 for Feld in self.Felder for xy in Feld])
            # Was da gerade zwischen den eckigen Klammern geschah:
            # Das Tupel "Feld" nahm nacheinander alle Werte der Tupel in
            # "self.Felder" an und jedesmal wurde die Variable "xy"
            # nacheinander auf den x- und den y-Wert dieses Tupels gesetzt,
            # mit 24 multipliziert und an eine Liste angehängt. Mit dieser
            # Liste von Koordinaten [x0, y0, x1, y1, … xn, yn] wurden dann
            # die beiden dicken Linien des Schlangenkörpers modifiziert.
        
        # Der gelbe Make-Up-Punkt für die Schwanzspitze:
        x, y = self.Felder[-1]
        C.coords(self.Schwanzspitze, x*24-7, y*24-7, x*24+7, y*24+7)

        # Ist gerade etwas furchtbar dummes passiert?
        if self.Felder[0] in self.Felder[1:]:
            # Wenn an der neuen Kopfposition schon ein Schlangensegment ist,
            # dann ist die Runde ist zu Ende, weil die Schlange sich selbst
            # gebissen hat.

            # Farbveränderung
            self.Stirb("red", "darkred")

            # Auswertungsdialog
            Highscore("Der Python hat sich selbst gebissen.",
                      Hamster.punkte)

            # Schlange und Hamster zurücksetzen
            self.reset()
            Hamster.reset()
            return
        
        # Punkteabzug wegen Trödelns:
        if Hamster.punkte:
            Hamster.punkte -= 1

        # Anzeige der Punktzahl aktualisieren
        C.itemconfig(C.zähler, text="%05i" % Hamster.punkte)
        
        # I'll be back
        if not self.Turbo:
            C.update_idletasks()
            C.after(self.ms, self.Bewege)
        else:
            C.after_idle(self.Bewege)

# Programmfenster
T = Tk()
T.title("Python-Hamster: Steuerung mit Pfeiltasten")

# Leinwand
C = Canvas(width=720, height=720, bg="#e0ffe0", highlightthickness=0)
C.pack()

# Spielfeldrand
C.create_rectangle(6, 6, 714, 714, width=12, outline="#703030")

# Fugen im Spielfeldrand
for a in range(61):
    for b, c in ((-1, "#501000"), (0, "#905030")):
        C.create_line(b+a*12, 0, b+a*12, 719, fill=c)
        C.create_line(0, b+a*12, 719, b+a*12, fill=c)

# Bodenkacheln
for x in range(29):
    for y in range(29):
        # Der Rot- und Blauanteil der Kachelfarbe ist veränderlich,
        # jedoch pro Kachel gleich.
        # Der Grünanteil hat immer den Wert 255.
        rb = "%02x" % randint(210, 230)
        farbe = "#"+rb+"ff"+rb
        C.create_rectangle(24*x+12, 24*y+12,
                           24*x+35, 24*y+35,
                           fill=farbe, outline=farbe)

# Punktezähler
C.zähler = C.create_text(360, 360,
                         text="00000",
                         font="courier 96 bold",
                         fill="#d0efd0")

# Der Hamster ist ein Objekt der Klasse C_Hamster
Hamster = C_Hamster()

# Die Schlange ist ein Objekt der Klasse C_Schlange
Schlange = C_Schlange()
T.bind("<Return>", countdown)

# Pfeiltasten an zugehörige Funktionen binden
T.bind("<Left>", lambda event: Schlange.Taste("l"))         # ←
T.bind("<Right>", lambda event: Schlange.Taste("r"))        # →
T.bind("<Up>", lambda event: Schlange.Taste("o"))           # ↑ 
T.bind("<Down>", lambda event: Schlange.Taste("u"))         # ↓
T.bind("<Home>", lambda event: Schlange.starteAutopilot())  # Pos1
T.bind("<End>", lambda event: Schlange.starteTurbo())       # Ende

# Los geht’s
countdown()

T.mainloop()

--
Dipl.-Ing. Martin Vogel
Leiter des Bauforums

Heute schon programmiert? Einführung in Python 3 (PDF)

antworten
 



gesamter Thread:

zurück zum Forum
  Mix-Ansicht
Forum Bauen und Umwelt | Kontakt | Impressum
8433 Postings in 4038 Threads, 1094 registrierte User, 38 User online (0 reg., 38 Gäste)
powered by my little forum  RSS-Feed  ^
map | new