Python-3-Spieletester gesucht :-) (Software)
Für einen kleinen Softwaretest, bei dem ich die Kommunikation eines lokalen Pythonprogramms mit einem serverbasierten Programm testen möchte, brauche ich mal eben ein paar Freiwillige.
Damit der Test nicht allzu langweilig abläuft, ist er mit einem kleinen Spiel verbunden, das mit einer Online-Highscoreliste gekoppelt ist. Wer es schafft, eine ansehnliche Punktzahl zu erreichen, ist herzlich eingeladen, seinen Spielernamen in die Highscoreliste einzutragen.
Das Programm benötigt eine existierende Python-3-Installation unter Windows, Linux oder Mac OS.
Zum Spielen einfach die ZIP-Datei herunterladen, auspacken und das Pythonprogramm ausführen.
Gestartet wird es mit der Eingabetaste, gesteuert mit den Pfeiltasten.
[Update: Neue Version]
Jetzt mit noch besserer Grafik, viel mehr erreichbaren Punkten und einer Belohnung für schnelle Auffassungsgabe!
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 jedoch wieder ein Punkt abgezogen. Umwege lohnen sich hier also nicht.
Das Spiel endet, wenn die Schlange gegen die Wand stößt oder mit sich selbst kollidiert.
Download: ZIP-Archiv_48FZMD6Q1.zip
#! /usr/bin/python3 from tkinter import Tk, Canvas, PhotoImage from tkinter.messagebox import askyesno as frage from tkinter.simpledialog import askstring as eingabe from random import randint, choice from getpass import getuser from webbrowser import open as showpage from urllib.parse import urlencode from time import sleep # 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 16. Januar 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" # # 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): self.counter = 3 # Ziffer rot und mittig anzeigen self.text = C.create_text(360, 360, fill="red") self.count() C.config(cursor="none") def count(self): # Der counter zählt Sekundenbruchteile, angezeigt werden nur # die ganzen Sekunden. n = int(self.counter) + 1 # 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: C.after(17, self.count) else: C.delete(self.text) Schlange.Start() def newcountdown(event=None): # Erzeugt ein neues countdown-Objekt der Canvas C.delete("Schlange") Hamster.verstecken() C.countdown = countdown() def username(): "Gibt den Spielernamen zurück" # Wurde der schon eingegeben? if hasattr(Hamster, "username"): un = Hamster.username else: # Fragen wir mal das Betriebssystem nach dem # Anmeldenamen. un = getuser() # Der kann übernommen oder geändert werden. un = eingabe("Hallo, äh … "+un+"?", "Gib Deinen Namen ein:", initialvalue=un) # Eingabe mit "OK" bestätigt? if un: Hamster.username = un # Oder Eingabedialog abgebrochen? else: un = "" return un class C_Hamster: "Der Hamster weiß, wo er ist, wie er aussieht und alles über den Spieler." def __init__(self): # Das Hamsterbild muss im Verzeichnis des Pythonprogramms liegen try: 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") # persönliche Bestleistung self.highscore = 0 self.reset() def reset(self): # Anzahl der Wiederbelebungen self.zähler = 0 # Aktuell erreichte Punktzahl self.punkte = 0 # # Philosophischer Einschub: # # Wieso bekommt eigentlich der Hamster die Punkte und nicht die # Schlange? # # 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 und die # Schlange stirbt. # # Schockierend. Ich weiß. # # # Mooment! Wieso gibt es dann umso mehr Punkte für den Hamster, je # häufiger die Schlange gefüttert wird? # # OK, das war vorhin doch nicht die ganze Wahrheit über das Spiel, # in wirklicher Wirklichkeit gibt es die Punkte für die heimliche # illegale Entsorgung der ganzen veganen Biodinkelriegel, die der # Hamster versehentlich gehortet hat. # # Jetzt aber weiter im Quelltext: # def umsetzen(self): """Setzt den Hamster auf ein zufälliges Feld, das noch nicht von der Schlange belegt ist.""" while True: # Irgendwelche Koordinaten 3 Felder vom Rand entfernt auswürfeln self.xy = randint(3, 27), randint(3, 27) # Die Felder rings um den Schlangenkopf sind tabu: umKopf = [(Schlange.Felder[0][0]+x, Schlange.Felder[0][1]+y) for x in [-1,0,1] for y in [-1,0,1]] # Ist das außerhalb der Schlange? if self.xy not in umKopf+Schlange.Felder: # Freies Feld gefunden, Endlosschleife abbrechen! break # Das Bild auf die neue Canvas-Position verschieben x, y = self.xy C.coords(Hamster.bild_ID, 24*x, 24*y) T.title("Füttere den Python mit dem %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" # 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 eine neue persönliche " "Bestleistung!nn" # Das wird der neue Highscore Hamster.highscore = Punkte elif Punkte: # Schlechter geworden? Jubel = "Beim nächsten Mal schaffst Du wieder mehr"+un+"!nn" else: # 0 Punkte? Jubel = "Tipp: Steuere den Python mit den Pfeiltasten!nn" # Mauszeiger wieder anzeigen C.config(cursor="arrow") # Dialog anzeigen if frage(Grund, "Du hast %i Hamsterpunkte!nn" % Punkte + Jubel + "Möchtest Du Dich in die Highscoreliste eintragen?"): showpage(url+"?"+urlencode((("n", username()), ("p", Punkte)))) T.title("Neustart mit der Eingabetaste!") else: T.title("Mache Dich bereit!") newcountdown() class C_Schlange: """Die Schlange merkt sich die durch sie belegten Felder und deren Canvas-Objekte sowie die letzte Hamstermahlzeit.""" def __init__(self): self.reset() def reset(self): "Startwerte der Schlange" # Zufällige Startpostition 10 Felder vom Rand entfernt self.x = randint(10, 20) self.dx = 0 self.y = randint(10, 20) self.dy = 0 # Zufällige Richtung: nach rechts, links, oben oder unten self.Richtung(choice("rlou")) # Die Elemente der Liste "Felder" bestehen aus Tupeln # (x-Position, y-Position). # Der Kopf der Schlange ist auf (self.x, self.y) self.Felder = [(self.x, self.y)] # Vier Rumpfsegmente entgegen der Bewegungsrichtung anhängen: for i in range(1, 5): 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 = [] 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) 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" # Schlange zeichnen: zuunterst liegt eine dicke blaue Linie. self.Körper = [C.create_line(self.Felder, fill="blue", smooth=1, tag="Schlange", joinstyle="round", capstyle="round", width=24), None] # Der Kopf – und später die Augen – werden hier nur vorbereitet, um die # Zeichenreihenfolge festzulegen; ihre Koordinaten erhalten die beiden # Canvas-Objekte erst in der Methode "zeichneKopf". self.Kopf = C.create_polygon(0, 0, 0, 0, width=4, fill="yellow", outline="blue", joinstyle="bevel", tag="Schlange") # 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=(6,13)) # Die Augen liegen zuoberst und bestehen aus einer einzigen # geschickt gestrichelten Linie: "* *". self.Augen = C.create_line(0, 0, 0, 0, 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 Schlange erhält stattdessen # einen veganen Biodinkelriegel.) 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. # 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() sleep(0.1) # Augenfarbe ändern C.itemconfig(self.Augen, fill=außenFarbe) C.update() sleep(0.1) # Kopfinneres umfärben C.itemconfig(self.Kopf, fill=innenFarbe) C.update() sleep(0.1) # Kopfrand umfärben C.itemconfig(self.Kopf, outline=außenFarbe) C.update() sleep(0.1) # Körperinneres umfärben C.itemconfig(self.Körper[1], fill=innenFarbe) C.update() 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() sleep(0.02) def Bewege(self): "Alle paar Millisekunden bewegt sich die Schlange ein Feld weiter" # Taste gedrückt? if self.Tastenpuffer: self.Richtung(self.Tastenpuffer.pop(0)) # Neue Kopfposition self.x += self.dx self.y += self.dy # Ist da der Hamster? if (self.x, self.y) == Hamster.xy: # Punktzahl erhöhen Hamster.zähler += 1 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) # Schlange und Hamster zurücksetzen self.reset() Hamster.reset() return # Ist gerade etwas furchtbar dummes passiert? selbstgebissen = False for Feld in self.Felder[:-1]: # Wenn an der neuen Kopfposition schon ein Schlangensegment ist: if (self.x, self.y) == Feld: selbstgebissen = True # Die Runde ist zu Ende, aber wir melden das erst gleich, wenn # die Schlange sich noch einen Schritt bewegt hat. break # 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("dummy") # 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 sind. for K in self.Körper: C.coords(K, [xy*24 for Feld in self.Felder for xy in Feld]) # War vorhin eigentlich was? if selbstgebissen: # 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 C.itemconfig(C.zähler, text="%05i" % Hamster.punkte) # I'll be back C.after(self.ms, 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): # rot- und blau-Anteil der Kachelfarbe sind veränderlich, # grün hat immer den Wert 0xff. rb = randint(210, 230) farbe = "#%0xff%0x" % (rb, 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>", newcountdown) # 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")) # Los geht’s countdown() T.mainloop()
--
Dipl.-Ing. Martin Vogel
Leiter des Bauforums
Bücher:
CAD mit BricsCAD
Bauinformatik mit Python