Button aktiviert sich automatisch

Fragen zu Tkinter.
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

@wuf: Fensterposition und -größe läßt man am besten automatisch ermitteln. Die Shebang-Zeile ist für Python3 falsch.

Code: Alles auswählen

from itertools import cycle
from functools import partial
import tkinter as tk
 
FENSTER_TITLE = "TickTackToe Spiel"
 
class TickTackToe(object):
    LEER = "leer.png"
    KREIS = "kreis.png"
    KREUZ = "kreuz.png"
    REIHEN = [
        [(0,0), (0,1), (0,2)],
        [(1,0), (1,1), (1,2)],
        [(2,0), (2,1), (2,2)],
        [(0,0), (1,0), (2,0)],
        [(0,1), (1,1), (2,1)],
        [(0,2), (1,2), (2,2)],
        [(0,0), (1,1), (2,2)],
        [(0,2), (1,1), (2,0)],
    ]
 
    def __init__(self, fenster):
        self.fenster = fenster
        self.images = {
            None: tk.PhotoImage(file=self.LEER),
            'Kreis': tk.PhotoImage(file=self.KREIS),
            'Kreuz': tk.PhotoImage(file=self.KREUZ),
        }
        self.knoepfe = dict()
        self.runden = 0
        self.spieler = cycle(['Kreis', 'Kreuz'])
        self.feld_bauen()
       
    def feld_bauen(self):
        self.melde_text_var = tk.StringVar(self.fenster, "Neues Spiel")
        meldung = tk.Label( self.fenster, textvariable=self.melde_text_var,
            font=('Helvetica', 12, 'bold'))
        meldung.pack(pady=4)
       
        knopf_rahmen = tk.Frame(self.fenster)
        knopf_rahmen.pack()
 
        for reihe in range(3):
            for zeile in range(3):
                knopf_widget = tk.Button(knopf_rahmen, image=self.images[None],
                    highlightthickness=0, bg='white', command=partial(self.knopf_druck, (reihe, zeile)))
                knopf_widget.grid(row=reihe, column=zeile)
                knopf_widget.status = None
                self.knoepfe[reihe, zeile] = knopf_widget
 
        self.reset_button = tk.Button(self.fenster, text="Wiederholung?",
            bg="yellow", command=self.reset_command)

    def set_status(self, key, status):
        self.knoepfe[key].status = status
        self.knoepfe[key]['image'] = self.images[status]
       
    def reset_command(self):
        for key in self.knoepfe:
            self.set_status(key, None)
        self.runden = 0
        self.melde_text_var.set("Neues Spiel")
        self.reset_button.pack_forget()
       
    def knopf_druck(self, knopf):
        print("Knopf gedrückt:", knopf)
        status = next(self.spieler)
        self.set_status(knopf, status)
        if any(all(self.knoepfe[r].status == status for r in row)
                for row in self.REIHEN):
            self.melde_text_var.set("Gewinner: {}".format(status))
            self.reset_button.pack(pady=4)
        else:
            self.runden += 1
            if self.runden==9:
                self.melde_text_var.set("Unentschieden")
                self.reset_button.pack(pady=4)
       
def main():
    fenster = tk.Tk()
    fenster.title(FENSTER_TITLE)
    app = TickTackToe(fenster)
    fenster.mainloop()
 
if __name__ == '__main__':
    main()
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Sirius3

Besten Dank für deine Hinweise und deine lehrreiche Optimierung des Skripts. Gehe ich richtig in der Annahme das die Shebang-Zeile:
#!/usr/bin/env python3
und die folgende Zeile:
# -*- coding: utf-8 -*-
in einem Python3-Skript nicht mehr benötigt sind?

Gruss wuf :wink:
Take it easy Mates!
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

@Sirius3: Bei deiner Variante habe ich festgestellt, dass sich ein Belegter Knopf während dem Spiel wieder belegen lässt. Steht ein Gewinner fest lassen sich Knöpfe weiterhin belegen bevor das Spiel mit der Schaltfläche Wiederholung für ein neues Spiel freigegeben wurde.

Meine optimierte Variante sieht wir folgt aus:

Code: Alles auswählen

from itertools import cycle
from functools import partial
import tkinter as tk
 
FENSTER_TITLE = "TickTackToe Spiel"

class WufTickTackToe(object):
    LEER = "leer.png"
    KREIS = "kreis.png"
    KREUZ = "kreuz.png"
    REIHEN = [
        [1,2,3], # 1. Horizontalreihe
        [4,5,6], # 2. Horizontalreihe
        [7,8,9], # 3. Horizontalreihe
        [1,4,7], # 1. Vertikalreihe
        [2,5,8], # 2. Vertikalreihe
        [3,6,9], # 3. Vertikalreihe
        [1,5,9], # 1. Diagonalreihe
        [3,5,7]  # 2. Diagonalreihe
        ]
    KNOPF_RAHMEN_BREITE = 70
    KNOPF_RAHMEN_HOEHE = 10
    
    def __init__(self, fenster):
        self.fenster = fenster
        self.images = {
            'Leer' : tk.PhotoImage(file=self.LEER),
            'Kreis': tk.PhotoImage(file=self.KREIS),
            'Kreuz': tk.PhotoImage(file=self.KREUZ),
        }
        self.runden = 1
        self.winner = None
        self.spieler = cycle(['Kreis', 'Kreuz'])
        self.knoepfe = list()
        self.feld_bauen()
        
    def feld_bauen(self):
        self.melde_text_var = tk.StringVar(self.fenster, "Neues Spiel")
        self.meldung = tk.Label( self.fenster, textvariable=self.melde_text_var,
            font=('Helvetica', 12, 'bold'))
        self.meldung.pack(pady=4)
        
        self.knopf_rahmen = tk.Frame(self.fenster)
        self.knopf_rahmen.pack(expand=True, padx=self.KNOPF_RAHMEN_BREITE,
            pady=self.KNOPF_RAHMEN_HOEHE)

        for reihe, positions in enumerate(self.REIHEN[0:3]):
            for zeile in range(len(positions)):
                knopf = positions[zeile]
                knopf_widget = tk.Button(self.knopf_rahmen,
                    image=self.images['Leer'], highlightthickness=0,
                    command=partial(self.knopf_druck, knopf))
                knopf_widget.status = None
                knopf_widget.grid(row=reihe, column=zeile)
                self.knoepfe.append(knopf_widget)

        self.reset_button = tk.Button(self.fenster, text="Wiederholung?",
            bg="yellow", command=self.reset_command)
        
    def knopf_druck(self, knopf):
        knopf_index = knopf-1
        if self.winner != None : return
        if self.knoepfe[knopf_index].status != None: return
        status = next(self.spieler)
        self.knoepfe[knopf_index].status = status
        self.knoepfe[knopf_index]['image'] = self.images[status]
        self.auswertung()
        
    def auswertung(self):
        for reihe in self.REIHEN:
            kreis_summe = 0
            kreuz_summe = 0
            for knopf in reihe:
                knopf_index = knopf-1
                status = self.knoepfe[knopf_index].status
                if status == 'Kreis':
                    kreis_summe += 1
                elif status == 'Kreuz':
                    kreuz_summe += 1
            if kreis_summe == 3 or kreuz_summe == 3:
                self.winner = status
                self.melde_text_var.set("Gewinner: {}".format(status))
                self.reset_button.pack(pady=4)
                return
            else:
                if self.runden >= 9:
                    self.melde_text_var.set("Unentschieden")
                    self.reset_button.pack(pady=4)
                else:
                    self.melde_text_var.set("Spielrunde: {}".format(self.runden))
        self.runden += 1            
                    
    def reset_command(self):                
        for knopf_index in range(len(self.knoepfe)):
            self.knoepfe[knopf_index].configure(image=self.images['Leer'])
            self.knoepfe[knopf_index].status = None
        self.runden = 1
        self.winner = None
        self.spieler = cycle(['Kreis', 'Kreuz'])
        self.melde_text_var.set("Neues Spiel")
        self.reset_button.pack_forget()
               
def main():
    fenster = tk.Tk()
    fenster.title(FENSTER_TITLE)
    app = WufTickTackToe(fenster)
    fenster.mainloop()
 
if __name__ == '__main__':
    main()
Gruss wuf :wink:
Take it easy Mates!
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

@wuf: an der Spielelogik wollte ich nicht ändern. Zeile 48 und 94 sind die berühmten Anti-Pattern, die Dir hier sicher schon öfter begegnet sind. Dass in Zeile 55 die Liste genau mit dem Index position-1 gefüllt wird, ist mehr als Zufall. Solche versteckten Abhängigkeiten will man nicht in Programmen haben. Die Auswerung ist deutlich umständlicher als mit any und all.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

@Sirius3: Danke für die Hinweise. Anti-Pattern ist ein neuer Begriff für mich. Habe die Zeilen wie folgt abgeändert:

Zeile 48
for zeile in range(len(positions)):
auf:
for zeile, knopf in enumerate(positions):

Zeile 94
for knopf_index in range(len(self.knoepfe)):
auf:
for knopf , knopf_widget in enumerate(self.knoepfe):

Um das position-1 zu neutralisieren habe ich:
REIHEN = [
[1,2,3], # 1. Horizontalreihe
[4,5,6], # 2. Horizontalreihe
[7,8,9], # 3. Horizontalreihe
[1,4,7], # 1. Vertikalreihe
[2,5,8], # 2. Vertikalreihe
[3,6,9], # 3. Vertikalreihe
[1,5,9], # 1. Diagonalreihe
[3,5,7] # 2. Diagonalreihe
]

auf:
REIHEN = [
[0,1,2], # 1. Horizontalreihe
[3,4,5], # 2. Horizontalreihe
[6,7,8], # 3. Horizontalreihe
[0,3,6], # 1. Vertikalreihe
[1,4,7], # 2. Vertikalreihe
[1,5,8], # 3. Vertikalreihe
[0,4,8], # 1. Diagonalreihe
[2,4,6] # 2. Diagonalreihe
]

geändert

Die Auswertung lasse ich so bestehen da mir die Zeile wie:
any(all(self.knoepfe[r].status == status for r in row) for row in self.REIHEN
mit any und all noch nicht so klar ist.

Gruss wuf :wink:
Take it easy Mates!
Antworten