Erstes Programm: Ein kleines Reaktionsspiel
@way2slow: Doch das funktioniert mit `after()`. Du sollst das nicht als `sleep()`-Ersatz benutzen, sondern für die zeitverzögerte Ausführung, also *nicht* die erste Variante aus der Dokumentation sondern die Zweite!
Danke für den Tipp. Mit after hat es tatsächlich geklappt. Ich hab eine Rekursion mit if-Abfrage benutzt, die quasi wie eine Schleife funktioniert. Hoffe, so hat es der Erfinder gedacht.
Aber jetzt ne ganz andere Frage: Ich habe in meinem kleinen Spiel eine Liste (laufband.laufbandliste), die am Anfang leer ist, dann mit Zufallszahlen erweitert wird, und wo gelegentlich Elemente mit pop entfernt werden. In Zeile 62 in der Funktion check_treffer prüfe ich, ob die Liste nicht leer ist (if laufband.laufbandliste:) und führe in der nächsten Zeile einen Vergleich durch. Trotzdem wirft der Interpreter den Fehler "IndexError: list index out of range", wenn man besonders schnell spielt: Pfeil nach oben/unten gedrückt halten sollte zum Nachvollziehen genügen, wenn die Tastaturverzögerung hinreichend kurz eingestellt ist.
Also, zum einen begreife ich nicht genau, wieso die Stelle mit dem Vergleich überhaupt erreicht wird, wenn die Liste leer ist. Zum anderen würde mich grundsätzlich interessieren, wie man mit sowas umgeht? Der Index-Fehler verhindert ja nicht, dass das Spiel so läuft, wie es soll. Einfach so lassen? Oder mit try behandeln? Vermutlich ist es am saubersten, wenn man Indexfehler von vorneherein gar nicht erst zulässt? Nur in diesem Fall: Wie?
Aber jetzt ne ganz andere Frage: Ich habe in meinem kleinen Spiel eine Liste (laufband.laufbandliste), die am Anfang leer ist, dann mit Zufallszahlen erweitert wird, und wo gelegentlich Elemente mit pop entfernt werden. In Zeile 62 in der Funktion check_treffer prüfe ich, ob die Liste nicht leer ist (if laufband.laufbandliste:) und führe in der nächsten Zeile einen Vergleich durch. Trotzdem wirft der Interpreter den Fehler "IndexError: list index out of range", wenn man besonders schnell spielt: Pfeil nach oben/unten gedrückt halten sollte zum Nachvollziehen genügen, wenn die Tastaturverzögerung hinreichend kurz eingestellt ist.
Also, zum einen begreife ich nicht genau, wieso die Stelle mit dem Vergleich überhaupt erreicht wird, wenn die Liste leer ist. Zum anderen würde mich grundsätzlich interessieren, wie man mit sowas umgeht? Der Index-Fehler verhindert ja nicht, dass das Spiel so läuft, wie es soll. Einfach so lassen? Oder mit try behandeln? Vermutlich ist es am saubersten, wenn man Indexfehler von vorneherein gar nicht erst zulässt? Nur in diesem Fall: Wie?
Code: Alles auswählen
import tkinter as tk
import random
import time
import pickle
import os
import collections
main=tk.Tk()
main.title("Zahlenlaufband")
main.resizable(width=0, height=0)
class Walze:
walzenliste=collections.deque([1, 2, 3, 4, 5, 6, 7, 8, 9])
ll_1=tk.Label(main, text=walzenliste[3], font=("Arial 20"), width=3,
relief="groove", borderwidth=2) #LabelLinks
ll_2=tk.Label(main, text=walzenliste[4], font=("Arial 30"), width=3,
relief="groove", borderwidth=2)
ll_3=tk.Label(main, text=walzenliste[5], font=("Arial 20"), width=3,
relief="groove", borderwidth=2)
def __init__(self, row, column):
self.ll_1.grid(row=row, column=column)
self.ll_2.grid(row=row+1, column=column)
self.ll_3.grid(row=row+2, column=column)
def update_walze(self):
self.ll_1.config(text=self.walzenliste[3])
self.ll_2.config(text=self.walzenliste[4])
self.ll_3.config(text=self.walzenliste[5])
def up(self, event):
self.walzenliste.rotate(1)
check_treffer(self.walzenliste, laufband.laufbandliste)
self.update_walze()
def down(self, event):
self.walzenliste.rotate(-1)
check_treffer(self.walzenliste, laufband.laufbandliste)
self.update_walze()
class Laufband:
laufbandliste=[]
lb=tk.Label(main, width=13, text="Drücke <Enter>", font=("Arial 22"),
relief="groove", borderwidth=2)
def __init__(self, row, column):
self.lb.grid(row=row, column=column)
def ermittle_zufallszahl(self):
zufallszahl=random.randint(1, 9)
while zufallszahl==walze.walzenliste[4]:
zufallszahl=random.randint(1, 9)
return zufallszahl
def spiel(self, event=None):
self.lb.config(anchor="e")
if len(self.laufbandliste)<9:
self.laufbandliste.append(self.ermittle_zufallszahl())
self.lb.config(text=self.laufbandliste)
self.lb.after(1000, self.spiel)
else:
self.lb.config(text="GAME OVER", anchor="center")
self.laufbandliste.clear()
def set_treffer(self):
self.laufbandliste.pop(0)
self.lb.config(text=self.laufbandliste)
def check_treffer(walzenliste, laufbandliste):
if laufband.laufbandliste:
while walzenliste[4]==laufbandliste[0]:
print("TREFFER")
laufband.set_treffer()
else: print("xxxxxxx")
walze=Walze(0, 0)
laufband=Laufband(1, 1)
main.bind("<KeyPress-Up>", walze.up)
main.bind("<KeyPress-Down>", walze.down)
main.bind("<Return>", laufband.spiel)
main.mainloop()
@way2slow: Du prüfst zwar *vor* der Schleife ob die Liste nicht leer ist, aber *in* der Schleife wird ``laufband.set_treffer()`` aufgerufen und darin wird ein Element entfernt. Falls das das letzte Element war, knallt's beim nächsten Test der Schleifenbedingung.
Da ist aber noch so einiges falsch. Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Du hast da das Hauptprogramm stehen, und dann auch noch die Klassendefinitionen mitten drin, und definierst Variablen auf die dann fröhlich aus Funktionen und Methoden zugegriffen wird. Das ist total unübersichtlich. Funktionen und Methoden sollten ausser Konstanten nichts verwenden was nicht als Argument übergeben wurde. Das Hauptprogramm gehört in eine Funktion. Die heisst üblicherweise `main()`. An der Stelle ist dann `main` als Name für das `Tk`-Exemplar etwas ungünstig.
`time`, `pickle`, und `os` werden importiert, aber nicht verwendet.
Du hast da einiges an Klassenattributen was dort nicht hingehört. Also *alles* um genau zu sein. Klassenattribute sind nur sehr selten überhaupt sinnvoll und dann fast immer als Konstanten.
Dein `Walzen`-Exemplar greift ”magisch” auf das `Laufband`-Exemplar zu und das `Laufband`-Exemplar auf das `Walzen`-Exemplar. Das lässt sich nicht wirklich sinnvoll auflösen, der Klassenentwurf ist also Murks. Das liesse sich beispielsweise durch eine Klasse lösen die nur die Spiellogik enthält und keinen GUI-Code. So ein Exemplar kann man dann beiden GUI-Klassen-Exemplaren übergeben.
Grunddatentypen haben in Namen nichts zu suchen. Insbesondere dann nicht wenn sie auch noch falsch sind weil es gar keine Liste ist, die an einen gebunden wird der mit `liste` endet.
Da ist aber noch so einiges falsch. Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Du hast da das Hauptprogramm stehen, und dann auch noch die Klassendefinitionen mitten drin, und definierst Variablen auf die dann fröhlich aus Funktionen und Methoden zugegriffen wird. Das ist total unübersichtlich. Funktionen und Methoden sollten ausser Konstanten nichts verwenden was nicht als Argument übergeben wurde. Das Hauptprogramm gehört in eine Funktion. Die heisst üblicherweise `main()`. An der Stelle ist dann `main` als Name für das `Tk`-Exemplar etwas ungünstig.
`time`, `pickle`, und `os` werden importiert, aber nicht verwendet.
Du hast da einiges an Klassenattributen was dort nicht hingehört. Also *alles* um genau zu sein. Klassenattribute sind nur sehr selten überhaupt sinnvoll und dann fast immer als Konstanten.
Dein `Walzen`-Exemplar greift ”magisch” auf das `Laufband`-Exemplar zu und das `Laufband`-Exemplar auf das `Walzen`-Exemplar. Das lässt sich nicht wirklich sinnvoll auflösen, der Klassenentwurf ist also Murks. Das liesse sich beispielsweise durch eine Klasse lösen die nur die Spiellogik enthält und keinen GUI-Code. So ein Exemplar kann man dann beiden GUI-Klassen-Exemplaren übergeben.
Grunddatentypen haben in Namen nichts zu suchen. Insbesondere dann nicht wenn sie auch noch falsch sind weil es gar keine Liste ist, die an einen gebunden wird der mit `liste` endet.
Hmm, ich habe 2 Listen (walzenliste und laufbandliste), die beides Listen sind. Verstehe daher nicht so recht, was du mit dem aller letzten Satz meinst.
Zum Index-Fehler: Jetzt hab ich verstanden, woher der Fehler kommt. Immerhin. Nur, wenn ich aus dem while in Zeile 63 ein if mache, dann räumt er nur eine Zahl ab, wenn mehrere Gleiche vorkommen. Muss ich mir mal Gedanke machen, wie man das am Geschicktesten löst.
Edit: Habe jetzt einfach am Ende der While-Schleife ein
if not laufband.laufbandliste: break
reingebastelt
Ansonsten: Mal wieder 1000 Dank für die schnelle Hilfe. Da hab ich ja wieder viel zu tun, bzw. erst mal zu verstehen
Zum Index-Fehler: Jetzt hab ich verstanden, woher der Fehler kommt. Immerhin. Nur, wenn ich aus dem while in Zeile 63 ein if mache, dann räumt er nur eine Zahl ab, wenn mehrere Gleiche vorkommen. Muss ich mir mal Gedanke machen, wie man das am Geschicktesten löst.
Edit: Habe jetzt einfach am Ende der While-Schleife ein
if not laufband.laufbandliste: break
reingebastelt
Ansonsten: Mal wieder 1000 Dank für die schnelle Hilfe. Da hab ich ja wieder viel zu tun, bzw. erst mal zu verstehen
@way2slow: `walzenliste` ist keine Liste sondern eine `collections.deque`. Datentypen ändern sich im Laufe der Entwicklung ab und zu weil man eine andere speziellere Datenstruktur verwendet, wie zum Beispiel in diesem Fall, und dann muss man entweder überall den Namen anpassen oder man hat falsche, irreführende Namen im Quelltext stehen. Das passiert besonders häufig bei Grunddatentypen, wo man beispielsweise mit einer Liste anfängt, dann aber irgendwann feststellt das man an der Stelle lieber einen Sequenzdatentyp mit anderen, spezielleren Eigenschaften hätte, beispielsweise eine `deque` oder ein selbst geschriebener Datentyp.
Mal ein Versuch von mir (Python 2):
Code: Alles auswählen
#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function
import Tkinter as tk
from collections import deque
from functools import partial
from random import randint
class Game(object):
def __init__(self):
self.current_digit = 4
self.digits = deque()
self.max_digit_count = 10
def clear_digits(self):
self.digits.clear()
def add_random_digit(self):
if len(self.digits) >= self.max_digit_count:
raise ValueError('max digits reached')
self.digits.append(randint(1, 9))
self.check_and_remove()
def change_current_digit(self, amount):
self.current_digit = (self.current_digit - 1 + amount) % 9 + 1
self.check_and_remove()
def check_and_remove(self):
while self.digits and self.digits[0] == self.current_digit:
self.digits.popleft()
class WheelUI(tk.Frame):
def __init__(self, parent, game):
tk.Frame.__init__(self, parent)
self.game = game
self.digit_labels = list()
for font in ['Arial 20', 'Arial 30', 'Arial 20']:
label = tk.Label(
self, font=font, width=3, relief=tk.GROOVE, borderwidth=2
)
label.pack(side=tk.TOP)
self.digit_labels.append(label)
self.update_display()
def update_display(self):
current_digit = self.game.current_digit
for digit, label in zip(
range(current_digit - 1, current_digit + 2), self.digit_labels
):
if digit == 0:
digit = 9
elif digit == 10:
digit = 1
label['text'] = digit
class GameUI(tk.Frame):
def __init__(self, parent, game):
tk.Frame.__init__(self, parent)
self.game = game
self.after_id = None
self.wheel = WheelUI(self, self.game)
self.wheel.pack(side=tk.LEFT)
self.scroller_label = tk.Label(
self, width=10, font='Arial 22', relief=tk.GROOVE, borderwidth=2
)
self.scroller_label.pack(side=tk.LEFT)
self.update_display()
self.scroller_label.config(text='Press Enter', anchor=tk.CENTER)
toplevel = self.winfo_toplevel()
toplevel.bind('<Return>', self.on_return)
toplevel.bind('<Up>', partial(self.on_change_wheel, -1))
toplevel.bind('<Down>', partial(self.on_change_wheel, 1))
def update_display(self):
self.wheel.update_display()
self.scroller_label.config(
text=''.join(str(d) for d in self.game.digits), anchor=tk.E
)
def _game_step(self):
try:
self.game.add_random_digit()
except ValueError:
self.scroller_label.config(text='Game Over', anchor=tk.CENTER)
self.after_id = None
else:
self.update_display()
self.after_id = self.after(1000, self._game_step)
def on_return(self, _event=None):
if not self.after_id:
self.game.clear_digits()
self._game_step()
def on_change_wheel(self, amount, _event=None):
if self.after_id:
self.game.change_current_digit(amount)
self.update_display()
def main():
root = tk.Tk()
root.title('Zahlenlaufband')
game = Game()
game_ui = GameUI(root, game)
game_ui.pack()
root.mainloop()
if __name__ == '__main__':
main()