Autopilot (Software)
Da Menschen wohl nicht in der Lage sind, so ein perfektes Spiel hinzubekommen …
… 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.
Verbesserungsvorschläge sind willkommen.
Download: 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!nn%s" % (Punkte, Jubel) + "nnMö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
Bücher:
CAD mit BricsCAD
Bauinformatik mit Python
gesamter Thread:
- Python-3-Spieletester gesucht :-) -
Martin Vogel,
13.12.2016, 14:50
- Autopilot - Martin Vogel, 03.07.2017, 23:28