Erstes Programm: Ein kleines Reaktionsspiel

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
BlackJack

@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!
way2slow
User
Beiträge: 19
Registriert: Sonntag 14. Februar 2016, 22:27
Wohnort: Hamburg

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?

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()
BlackJack

@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.
way2slow
User
Beiträge: 19
Registriert: Sonntag 14. Februar 2016, 22:27
Wohnort: Hamburg

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 :lol:

Ansonsten: Mal wieder 1000 Dank für die schnelle Hilfe. Da hab ich ja wieder viel zu tun, bzw. erst mal zu verstehen :-)
BlackJack

@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.
BlackJack

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()
way2slow
User
Beiträge: 19
Registriert: Sonntag 14. Februar 2016, 22:27
Wohnort: Hamburg

Wow! Völlig anders als meins. Das muss ich erst mal in Ruhe verarbeiten. Echt nett von Dir!
Antworten