Vollbildmodus bei Tkinter

Fragen zu Tkinter.
Antworten
kossi83
User
Beiträge: 2
Registriert: Donnerstag 28. Juli 2005, 11:03
Kontaktdaten:

Donnerstag 28. Juli 2005, 12:53

Hallo zusammen, gibt es eine Möglichkeit, die Fenster per Befehl in den
Vollbildmodus umzuschalten?

Thx im Vorraus. kossi83
mawe
Python-Forum Veteran
Beiträge: 1209
Registriert: Montag 29. September 2003, 17:18
Wohnort: Purkersdorf (bei Wien [Austria])

Donnerstag 28. Juli 2005, 13:57

Hi!

Code: Alles auswählen

from Tkinter import *

root = Tk()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry("%dx%d+0+0" % (w, h))
root.mainloop()
Gruß, mawe
kossi83
User
Beiträge: 2
Registriert: Donnerstag 28. Juli 2005, 11:03
Kontaktdaten:

Donnerstag 28. Juli 2005, 14:13

Super vielen Dank. Hatte schon in tausend Bücher nachgeguckt. Aber nirgendsgefunden.

Gruss kossi83 :D
langeLeitung
User
Beiträge: 16
Registriert: Donnerstag 14. April 2005, 12:53

Freitag 29. Juli 2005, 06:15

...und wie maximiere ich ein Fenster nur. Also so, dass es nicht die Taskleite verdeckt?

Code: Alles auswählen

hauptfenster = Tk()
hauptfenster.deiconify()
funktioniert nicht.

Wie muss ich deinconify benutzen?
mawe
Python-Forum Veteran
Beiträge: 1209
Registriert: Montag 29. September 2003, 17:18
Wohnort: Purkersdorf (bei Wien [Austria])

Freitag 29. Juli 2005, 06:42

Hi!
langeLeitung hat geschrieben: ...und wie maximiere ich ein Fenster nur. Also so, dass es nicht die Taskleite verdeckt?
Drück einfach auf das Maximier-Icon :)
Im Ernst, keine Ahnung. Du könntest vielleicht von der screenheight die Höhe der Taskleiste abziehen, aber die ist ja nicht überall gleich hoch ...

Zu deiconify: Das ist ja eigentlich nur zum Enticonifizieren (tolles Wort :)). Wenn Du vorher mit withdraw() das Fenster zu einem Icon geschrumpft hast, kannst Du's mit deiconify() wieder zurückholen.

Gruß, mawe
timm4444
User
Beiträge: 3
Registriert: Samstag 1. August 2020, 14:11

Samstag 1. August 2020, 14:23

Hallo kossi83

ich habe deinen Beitrag gelesen und mir ist eingefallen, dass ich mal ein Spiel programmiert habe, welches
im Vollbild ist:

Code: Alles auswählen

from itertools import cycle
from random import randrange
from tkinter import Canvas, Tk, messagebox, font

root = Tk()


w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.overrideredirect(1)
root.geometry("%dx%d+0+0" % (w, h))

canvas_width = w
canvas_height = h




c = Canvas(root, width=canvas_width, height=canvas_height, background="deep sky blue")
c.create_rectangle(-5, canvas_height - 300, canvas_width + 50, canvas_height + 50, fill="sea green", width=0)
c.create_oval(-80, -80, 120, 120, fill="orange", width=0)

c.pack()


ei_width = 45
ei_height = 55
global ei_punktzahl
ei_punktzahl = 10
global ei_tempo
ei_tempo = 500
global ei_intervall
ei_intervall = 4000
global schwierigkeit
schwierigkeit = 0.95
global verbleibende_leben

korb_color = "blue"
korb_width = 100
korb_height = 100
korb_start_x = canvas_width / 2 - korb_width / 2
korb_start_y = canvas_height - korb_height - 20
korb_start_x2 = korb_start_x + korb_width
korb_start_y2 = korb_start_y + korb_height

korb = c.create_arc(korb_start_x, korb_start_y, korb_start_x2, korb_start_y2, start=200, extent=140, style="arc", outline=korb_color, width=3)

game_font = font.nametofont("TkFixedFont")
game_font.config(size=18)

global punktzahl
punktzahl = 0
punktzahl_text = c.create_text(10, 10, anchor="nw", font=game_font, fill="darkblue", text="Punktzahl: " + str(punktzahl))


verbleibende_leben = 3
leben_text = c.create_text(canvas_width - 10, 10, anchor="ne", font=game_font, fill="darkblue", text="Leben " + str(verbleibende_leben))

eier = []
def create_ei():
	x = randrange(10, 1550)
	y = 40
	neues_ei = c.create_oval(x, y, x + ei_width, y + ei_height, fill="yellow", width=0)
	eier.append(neues_ei)
	root.after(ei_intervall, create_ei)

def move_eier():
	for ei in eier:
		(ei_x, ei_y, ei_x2, ei_y2,) = c.coords(ei)
		c.move(ei, 0, 10)
		if ei_y2 > canvas_height:
			ei_gefallen(ei)
	root.after(ei_tempo, move_eier)

def ei_gefallen(ei):
	eier.remove(ei)
	c.delete(ei)
	verliere_ein_leben()
	if verbleibende_leben == 0:
		messagebox.showinfo("Game Over", "Punkte: " + str(punktzahl))
		root.destroy()
		exit

def verliere_ein_leben():
	global verbleibende_leben
	verbleibende_leben = verbleibende_leben - 1
	c.itemconfigure(leben_text, text="Leben: " + str(verbleibende_leben))

def check_fang():
	(korb_x, korb_y, korb_x2, korb_y2) = c.coords(korb)
	for ei in eier:
		(ei_x, ei_y, ei_x2, ei_y2,) = c.coords(ei)
		if korb_x < ei_x and ei_x2 < korb_x2 and korb_y2 - ei_y2 < 40:
			eier.remove(ei)
			c.delete(ei)
			erhoehe_punktzahl(ei_punktzahl)
	root.after(100, check_fang)

def erhoehe_punktzahl(points):
	global punktzahl, ei_tempo, ei_intervall
	punktzahl = punktzahl + 1
	ei_tempo = int(ei_tempo * schwierigkeit)
	ei_intervall = int(ei_intervall * schwierigkeit)
	c.itemconfigure(punktzahl_text, text="punktzahl: " + str(punktzahl))

def move_left(event):
	(x1, y1, x2, y2) = c.coords(korb)
	if x1 > 0:
		c.move(korb, -20, 0)

def move_right(event):
	(x1, y1, x2, y2) = c.coords(korb)
	if x2 < canvas_width:
		c.move(korb, 20, 0)

c.bind("<Left>", move_left)
c.bind("<Right>", move_right)
c.focus_set()


root.after(1000, create_ei)
root.after(1000, move_eier)
root.after(1000, check_fang)
root.mainloop()

Der Vollbildcode einzelnt ist wie folgt:

Code: Alles auswählen

from tkinter import *
root = Tk()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.overrideredirect(1)
root.geometry("%dx%d+0+0" % (w, h))
root.mainloop()
Da dein Beitrag schon älter ist glaube ich nicht, dass ich Dir damit helfen kann, aber so ist die Frage beantwortet. :-)
Benutzeravatar
__blackjack__
User
Beiträge: 6559
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Mittwoch 5. August 2020, 01:33

@timm4444: Da hättest Du vielleicht besser nur den Code für Vollbild gezeigt, denn der Rest ist ziemlich schlecht.

`override_redirect()` ist keine gute Idee, denn damit funktioniert das Programm nicht mehr unter Linux und MacOS, weil dort dann die Tastenereignisse nicht mehr funktionieren und man das Programm nicht mehr steuern kann.

`cycle` aus `itertools` wird importiert, aber nirgends verwendet.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

``global`` auf Modulebene hat keinerlei Wirkung. ``global`` sollte auch überhaupt nicht verwendet werden. Alles was Funktionen und Methoden ausser Konstanten benötigen sollten sie als Argument(e) übergeben bekommen.

Namen sollten nicht kryptisch abgekürzt werden, schon gar nicht nur mit einem einzigen Buchstaben. Wenn man `canvas` meint, sollte man nicht nur `c` schreiben. Noch sinnloser wird es wenn man erst `w` und `h` definiert und die dann noch mal an `canvas_width` und `canvas_height` bindet.

Zeichenkettenformatierung mit ``%`` würde ich nicht mehr machen ohne guten Grund. Es gibt f-Zeichenkettenliterale oder die `format()`-Methode auf Zeichenketten. Das sollte man auch statt ``+`` und `str()` verwenden, denn das ist eher BASIC denn Python.

`ei_punktzahl` wird nicht wirklich verwendet. Das wird zwar bei `erhoehe_punktzahl()` als Argument übergeben, die Funktion verwendet das Argument aber überhaupt nicht.

In `ei_gefallen()` wird `destroy()` aufgerufen statt die Tk-Hauptschleife einfach mit `quit()` zu verlassen. Das ist weniger brachial und man kann das auch auf anderen Widget-Objekten aufrufen, da braucht man nicht zwingend das `Tk`-Objekt für.

Das `exit` in der Funktion macht nichts und der Name dürfte eigentlich auch gar nicht existieren wenn man ihn nicht aus `sys` importiert. Die Funktion sollte man aber sowieso nur im Hauptprogramm verwenden und dort auch nur wenn man zumindest potentiell einen anderen Rückgabecode als 0 an den Aufrufer des Programms übermitteln will.

Die 1550 in `create_ei()` ist ziemlich magisch und ein Problem wenn man das Programm auf kleinen Displays laufen lässt. Der Wert sollte von der Breite des `Canvas`-Objekts abhängen.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
from functools import partial
from random import randrange
from tkinter import ARC, Canvas, IntVar, Tk, font, messagebox

SCHWIERIGKEIT = 0.95

EI_WIDTH = 45
EI_HEIGHT = 55
EI_TEMPO = 500
EI_INTERVALL = 4000

KORB_COLOR = "blue"
KORB_WIDTH = 100
KORB_HEIGHT = 100


def create_ei(canvas, canvas_width, eier, ei_intervall):
    x = randrange(10, canvas_width - EI_WIDTH - 10)
    y = 40
    eier.append(
        canvas.create_oval(
            x, y, x + EI_WIDTH, y + EI_HEIGHT, fill="yellow", width=0
        )
    )
    canvas.after(
        ei_intervall.get(), create_ei, canvas, canvas_width, eier, ei_intervall
    )


def verliere_ein_leben(canvas, leben_text, verbleibende_leben):
    verbleibende_leben.set(verbleibende_leben.get() - 1)
    canvas.itemconfigure(leben_text, text=f"Leben: {verbleibende_leben.get()}")


def ei_gefallen(canvas, leben_text, eier, punktzahl, verbleibende_leben, ei):
    eier.remove(ei)
    canvas.delete(ei)
    verliere_ein_leben(canvas, leben_text, verbleibende_leben)
    if verbleibende_leben.get() == 0:
        messagebox.showinfo("Game Over", f"Punkte: {punktzahl.get()}")
        canvas.quit()


def move_eier(
    canvas,
    canvas_height,
    leben_text,
    punktzahl,
    verbleibende_leben,
    eier,
    ei_tempo,
):
    for ei in eier:
        _, _, _, ei_y2 = canvas.coords(ei)
        canvas.move(ei, 0, 10)
        if ei_y2 > canvas_height:
            ei_gefallen(
                canvas, leben_text, eier, punktzahl, verbleibende_leben, ei
            )
    canvas.after(
        ei_tempo.get(),
        move_eier,
        canvas,
        canvas_height,
        leben_text,
        punktzahl,
        verbleibende_leben,
        eier,
        ei_tempo,
    )


def erhoehe_punktzahl(
    canvas, punktzahl_text, punktzahl, ei_tempo, ei_intervall
):
    punktzahl.set(punktzahl.get() + 1)
    ei_tempo.set(int(ei_tempo.get() * SCHWIERIGKEIT))
    ei_intervall.set(int(ei_intervall.get() * SCHWIERIGKEIT))
    canvas.itemconfigure(punktzahl_text, text=f"punktzahl: {punktzahl.get()}")


def check_fang(
    canvas, punktzahl_text, punktzahl, korb, eier, ei_tempo, ei_intervall
):
    korb_x, _, korb_x2, korb_y2 = canvas.coords(korb)
    for ei in eier:
        ei_x, _, ei_x2, ei_y2 = canvas.coords(ei)
        if korb_x < ei_x and ei_x2 < korb_x2 and korb_y2 - ei_y2 < 40:
            eier.remove(ei)
            canvas.delete(ei)
            erhoehe_punktzahl(
                canvas, punktzahl_text, punktzahl, ei_tempo, ei_intervall
            )
    canvas.after(
        100,
        check_fang,
        canvas,
        punktzahl_text,
        punktzahl,
        korb,
        eier,
        ei_tempo,
        ei_intervall,
    )


def move_left(canvas, korb, _event):
    x_1, _, _, _ = canvas.coords(korb)
    if x_1 > 0:
        canvas.move(korb, -20, 0)


def move_right(canvas, canvas_width, korb, _event):
    _, _, x_2, _ = canvas.coords(korb)
    if x_2 < canvas_width:
        canvas.move(korb, 20, 0)


def main():
    root = Tk()

    canvas_width, canvas_height = (
        root.winfo_screenwidth(),
        root.winfo_screenheight(),
    )
    root.attributes("-fullscreen", True)
    root.geometry(f"{canvas_width}x{canvas_height}+0+0")

    ei_tempo = IntVar(value=EI_TEMPO)
    ei_intervall = IntVar(value=EI_INTERVALL)
    punktzahl = IntVar(value=0)
    verbleibende_leben = IntVar(value=3)
    eier = list()

    canvas = Canvas(
        root,
        width=canvas_width,
        height=canvas_height,
        background="deep sky blue",
    )
    canvas.create_rectangle(
        -5,
        canvas_height - 300,
        canvas_width + 50,
        canvas_height + 50,
        fill="sea green",
        width=0,
    )
    canvas.create_oval(-80, -80, 120, 120, fill="orange", width=0)
    canvas.pack()

    korb_start_x = canvas_width / 2 - KORB_WIDTH / 2
    korb_start_y = canvas_height - KORB_HEIGHT - 20
    korb_start_x2 = korb_start_x + KORB_WIDTH
    korb_start_y2 = korb_start_y + KORB_HEIGHT
    korb = canvas.create_arc(
        korb_start_x,
        korb_start_y,
        korb_start_x2,
        korb_start_y2,
        start=200,
        extent=140,
        style=ARC,
        outline=KORB_COLOR,
        width=3,
    )

    game_font = font.nametofont("TkFixedFont")
    game_font.config(size=18)
    punktzahl_text = canvas.create_text(
        10,
        10,
        anchor="nw",
        font=game_font,
        fill="darkblue",
        text=f"Punktzahl: {punktzahl.get()}",
    )
    leben_text = canvas.create_text(
        canvas_width - 10,
        10,
        anchor="ne",
        font=game_font,
        fill="darkblue",
        text=f"Leben {verbleibende_leben.get()}",
    )

    root.after(1000, create_ei, canvas, canvas_width, eier, ei_intervall)
    root.after(
        1000,
        move_eier,
        canvas,
        canvas_height,
        leben_text,
        punktzahl,
        verbleibende_leben,
        eier,
        ei_tempo,
    )
    root.after(
        1000,
        check_fang,
        canvas,
        punktzahl_text,
        punktzahl,
        korb,
        eier,
        ei_tempo,
        ei_intervall,
    )
    canvas.bind("<Left>", partial(move_left, canvas, korb))
    canvas.bind("<Right>", partial(move_right, canvas, canvas_width, korb))
    canvas.focus_set()
    root.mainloop()


if __name__ == "__main__":
    main()
Die `main()` ist ein bisschen zu lang und es werden zu viele Argumente in der Gegend herum gereicht. Für jede nicht-triviale GUI braucht man deshalb objektorientierte Programmierung.

`check_fang()` macht mit `after()` eigentlich wenig Sinn. Die Funktion sollte einfach nach jedem Eier bewegen aufgerufen werden oder nach jedem Korb bewegen.
long long ago; /* in a galaxy far far away */
Benutzeravatar
__blackjack__
User
Beiträge: 6559
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Mittwoch 5. August 2020, 17:03

Die Namen mal alle auf Englisch und teilweise inhaltlich leicht angepasst. EI_TEMPO war beispielsweise falsch, denn Tempo bedeutet ja höherer Wert = schneller, was bei dieser Konstanten genau umgekehrt ist.

Und eine erste Klasse herausgezogen, die von `Canvas` abgeleitet ist, und die Anzeigefläche samt Punkte- und Lebenanzeige repräsentiert. Die Anzahl der Argumente hat an einigen Stellen dadurch schon abgenommen. `check_catch()` (ehemals `check_fang()`) reicht aber immer noch zu viel von Aufruf zu Aufruf.

Code: Alles auswählen

#!/usr/bin/env python3
from functools import partial
from random import randrange
from tkinter import ARC, Canvas, IntVar, Tk, font, messagebox, NE, NW

DIFFICULTY_FACTOR = 0.95

EGG_WIDTH = 45
EGG_HEIGHT = 55
EGG_MOVE_DELAY = 500
EGG_DROP_INTERVAL = 4000

BASKET_COLOR = "blue"
BASKET_WIDTH = BASKET_HEIGHT = 100


class Display(Canvas):
    def __init__(self, master, width, height):
        self.width = width
        self.height = height
        Canvas.__init__(
            self,
            master=master,
            width=self.width,
            height=self.height,
            background="deep sky blue",
        )
        #
        # Ground and sun.
        #
        # TODO Make sizes depend on display size.
        #
        self.create_rectangle(
            -5,
            self.height - 300,
            self.width + 5,
            self.height + 5,
            fill="sea green",
            width=0,
        )
        self.create_oval(-80, -80, 120, 120, fill="orange", width=0)

        game_font = font.nametofont("TkFixedFont")
        game_font.config(size=18)
        self._score_text_id = self.create_text(
            10, 10, anchor=NW, font=game_font, fill="darkblue",
        )
        self._life_count_text_id = self.create_text(
            self.width - 10, 10, anchor=NE, font=game_font, fill="darkblue",
        )
        self.set_score("-")
        self.set_life_count("-")

    def set_score(self, value):
        self.itemconfigure(self._score_text_id, text=f"Punktzahl: {value}")

    def set_life_count(self, value):
        self.itemconfigure(self._life_count_text_id, text=f"Leben {value}")


def create_egg(display, eggs, egg_drop_interval_var):
    x = randrange(10, display.width - EGG_WIDTH - 10)
    y = 40
    eggs.append(
        display.create_oval(
            x, y, x + EGG_WIDTH, y + EGG_HEIGHT, fill="yellow", width=0
        )
    )
    display.after(
        egg_drop_interval_var.get(),
        create_egg,
        display,
        eggs,
        egg_drop_interval_var,
    )


def lose_a_life(display, life_count_var):
    life_count_var.set(life_count_var.get() - 1)
    display.set_life_count(life_count_var.get())


def on_uncatched_egg(display, eggs, score_var, life_count_var, egg):
    eggs.remove(egg)
    display.delete(egg)
    lose_a_life(display, life_count_var)
    if life_count_var.get() == 0:
        #
        # FIXME While the message box is shown there is still a task running
        #   creating new eggs.
        #
        messagebox.showinfo("Game Over", f"Punkte: {score_var.get()}")
        display.quit()


def move_eggs(
    display, score_var, life_count_var, eggs, egg_move_delay_var,
):
    for egg in eggs:
        _, _, _, egg_y2 = display.coords(egg)
        display.move(egg, 0, 10)
        if egg_y2 > display.height:
            on_uncatched_egg(display, eggs, score_var, life_count_var, egg)
    display.after(
        egg_move_delay_var.get(),
        move_eggs,
        display,
        score_var,
        life_count_var,
        eggs,
        egg_move_delay_var,
    )


def increase_score(
    display, score_var, egg_move_delay_var, egg_drop_interval_var
):
    score_var.set(score_var.get() + 1)
    #
    # TODO Don't change the values of `egg_move_delay_var` and
    #   `egg_drop_interval_var` but calculate them from `score_var`.
    #
    egg_move_delay_var.set(int(egg_move_delay_var.get() * DIFFICULTY_FACTOR))
    egg_drop_interval_var.set(
        int(egg_drop_interval_var.get() * DIFFICULTY_FACTOR)
    )
    display.set_score(score_var.get())


def check_catch(
    display,
    score_var,
    basket,
    eggs,
    egg_move_delay_var,
    egg_drop_interval_var,
):
    basket_x, _, basket_x2, basket_y2 = display.coords(basket)
    for egg in eggs:
        egg_x, _, egg_x2, egg_y2 = display.coords(egg)
        if basket_x < egg_x and egg_x2 < basket_x2 and basket_y2 - egg_y2 < 40:
            eggs.remove(egg)
            display.delete(egg)
            increase_score(
                display, score_var, egg_move_delay_var, egg_drop_interval_var
            )
    display.after(
        100,
        check_catch,
        display,
        score_var,
        basket,
        eggs,
        egg_move_delay_var,
        egg_drop_interval_var,
    )


def move_basket_left(display, basket, _event):
    x_1, _, _, _ = display.coords(basket)
    if x_1 > 0:
        display.move(basket, -20, 0)


def move_basket_right(display, basket, _event):
    _, _, x_2, _ = display.coords(basket)
    if x_2 < display.width:
        display.move(basket, 20, 0)


def main():
    root = Tk()

    display_width, display_height = (
        root.winfo_screenwidth(),
        root.winfo_screenheight(),
    )
    root.attributes("-fullscreen", True)
    root.geometry(f"{display_width}x{display_height}+0+0")

    egg_move_delay_var = IntVar(value=EGG_MOVE_DELAY)
    egg_drop_interval_var = IntVar(value=EGG_DROP_INTERVAL)
    score_var = IntVar(value=0)
    life_count_var = IntVar(value=3)
    eggs = list()

    display = Display(root, display_width, display_height)
    display.pack()
    display.set_score(score_var.get())
    display.set_life_count(life_count_var.get())

    basket_start_x = display.width / 2 - BASKET_WIDTH / 2
    basket_start_y = display.height - BASKET_HEIGHT - 20
    basket = display.create_arc(
        basket_start_x,
        basket_start_y,
        basket_start_x + BASKET_WIDTH,
        basket_start_y + BASKET_HEIGHT,
        start=200,
        extent=140,
        style=ARC,
        outline=BASKET_COLOR,
        width=3,
    )

    root.after(1000, create_egg, display, eggs, egg_drop_interval_var)
    root.after(
        1000,
        move_eggs,
        display,
        score_var,
        life_count_var,
        eggs,
        egg_move_delay_var,
    )
    #
    # TODO This should not be an indepedent asynchronous task but called each
    #   time the eggs and/or the basket moved.
    #
    root.after(
        1000,
        check_catch,
        display,
        score_var,
        basket,
        eggs,
        egg_move_delay_var,
        egg_drop_interval_var,
    )
    display.bind("<Left>", partial(move_basket_left, display, basket))
    display.bind("<Right>", partial(move_basket_right, display, basket))
    display.focus_set()
    root.mainloop()


if __name__ == "__main__":
    main()
Als nächstes würde vielleicht eine `Sprite`-Klasse Sinn machen, die das `Display`-Objekt und die ID von einem Ei und dem Korb kapselt. Und eine Hilfsklasse die so ähnlich wie `pygame.Rect` funktioniert und die Operationen auf den Koordinaten leichter lesbar und verständlich macht.
long long ago; /* in a galaxy far far away */
timm4444
User
Beiträge: 3
Registriert: Samstag 1. August 2020, 14:11

Donnerstag 6. August 2020, 19:29

@__blackjack__: Ja, du hast recht, es ist nicht das beste Programm. Das war einer meiner ersten Projekte und ich habe den Code nicht mehr übearbeitet. Ich hatte bis letzten Monat Propleme mit Pygame und konnte es deswegen so nicht machen.
Ich werde in Zukunft meine Codes nochmal auf sowas überprüfen bevor ich Codes ins Forum stelle. :-)
Benutzeravatar
__blackjack__
User
Beiträge: 6559
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Freitag 7. August 2020, 09:00

Eine `Box`-Klasse ähnlich `pygame.Rect` und eine `Sprite`-Klasse eingeführt:

Code: Alles auswählen

#!/usr/bin/env python3
from functools import partial, total_ordering
from random import randrange
from tkinter import ARC, NE, NW, Canvas, IntVar, Tk, font, messagebox

DIFFICULTY_FACTOR = 0.95

EGG_WIDTH = 45
EGG_HEIGHT = 55
EGG_MOVE_DELAY = 500
EGG_DROP_INTERVAL = 4000

BASKET_COLOR = "blue"
BASKET_WIDTH = BASKET_HEIGHT = 100


class Box:
    def __init__(self, left, top, right, bottom):
        self._left = left
        self._top = top
        self._right = right
        self._bottom = bottom

    def __repr__(self):
        return (
            f"{self.__class__.__name__}"
            f"{self.left, self.top, self.right, self.bottom}"
        )

    def __iter__(self):
        yield self.left
        yield self.top
        yield self.right
        yield self.bottom

    @property
    def left(self):
        return self._left

    @left.setter
    def left(self, value):
        self.move(value - self.left, 0)

    @property
    def top(self):
        return self._top

    @top.setter
    def top(self, value):
        self.move(0, value - self.top)

    @property
    def right(self):
        return self._right

    @right.setter
    def right(self, value):
        self.move(value - self.right, 0)

    @property
    def bottom(self):
        return self._bottom

    @bottom.setter
    def bottom(self, value):
        self.move(0, value - self.bottom)

    @property
    def width(self):
        return self.right - self.left

    @property
    def height(self):
        return self.bottom - self.top

    @property
    def center_x(self):
        return self.width // 2 + self.left

    @center_x.setter
    def center_x(self, value):
        self.move(value - self.center_x, 0)

    def move(self, x_delta, y_delta):
        self._left += x_delta
        self._top += y_delta
        self._right += x_delta
        self._bottom += y_delta

    @classmethod
    def from_position_and_size(cls, x, y, width, height):
        return cls(x, y, x + width, y + height)

    @classmethod
    def from_size(cls, width, height):
        return cls.from_position_and_size(0, 0, width, height)


class Display(Canvas):
    def __init__(self, master, width, height):
        self.width = width
        self.height = height
        self._box = Box.from_size(self.width, self.height)
        Canvas.__init__(
            self,
            master=master,
            width=self.width,
            height=self.height,
            background="deep sky blue",
        )
        #
        # Ground and sun.
        #
        # TODO Let sizes depend on display size.
        #
        self.create_rectangle(
            -5,
            self.height - 300,
            self.width + 5,
            self.height + 5,
            fill="sea green",
            width=0,
        )
        #
        # TODO `Box.from_center_and_radius()`!?
        #
        self.create_oval(-80, -80, 120, 120, fill="orange", width=0)

        game_font = font.nametofont("TkFixedFont")
        game_font.config(size=18)
        self._score_text_id = self.create_text(
            10, 10, anchor=NW, font=game_font, fill="darkblue",
        )
        self._life_count_text_id = self.create_text(
            self.width - 10, 10, anchor=NE, font=game_font, fill="darkblue",
        )
        self.set_score("-")
        self.set_life_count("-")

    def get_box(self):
        return self._box

    def set_score(self, value):
        self.itemconfigure(self._score_text_id, text=f"Punktzahl: {value}")

    def set_life_count(self, value):
        self.itemconfigure(self._life_count_text_id, text=f"Leben {value}")


@total_ordering
class Sprite:
    def __init__(self, display, item_id):
        self.display = display
        self.id = item_id

    def __str__(self):
        return str(self.id)

    def __eq__(self, other):
        return self.id == other.id

    def __ne__(self, other):
        return not self == other

    def __lt__(self, other):
        return self.id < other.id

    def __hash__(self):
        return hash(self.id)

    def get_box(self):
        return Box(*self.display.coords(self))

    def move(self, x_delta, y_delta):
        self.display.move(self, x_delta, y_delta)

    def delete(self):
        self.display.delete(self)
        self.id = None

    @classmethod
    def new_arc(cls, display, box, **kwargs):
        return cls(display, display.create_arc(*box, **kwargs))

    @classmethod
    def new_oval(cls, display, box, **kwargs):
        return cls(display, display.create_oval(*box, **kwargs))


def create_egg(display, eggs, egg_drop_interval_var):
    eggs.append(
        Sprite.new_oval(
            display,
            Box.from_position_and_size(
                randrange(10, display.width - EGG_WIDTH - 10),
                40,
                EGG_WIDTH,
                EGG_HEIGHT,
            ),
            fill="yellow",
            width=0,
        )
    )
    display.after(
        egg_drop_interval_var.get(),
        create_egg,
        display,
        eggs,
        egg_drop_interval_var,
    )


def lose_a_life(display, life_count_var):
    life_count_var.set(life_count_var.get() - 1)
    display.set_life_count(life_count_var.get())


def on_uncatched_egg(display, eggs, score_var, life_count_var, egg):
    eggs.remove(egg)
    egg.delete()
    lose_a_life(display, life_count_var)
    if life_count_var.get() == 0:
        #
        # FIXME While the message box is shown there is still a task running
        #   creating new eggs.
        #
        messagebox.showinfo("Game Over", f"Punkte: {score_var.get()}")
        display.quit()


def move_eggs(
    display, score_var, life_count_var, eggs, egg_move_delay_var,
):
    for egg in eggs:
        egg.move(0, 10)
        if egg.get_box().bottom > display.height:
            on_uncatched_egg(display, eggs, score_var, life_count_var, egg)
    display.after(
        egg_move_delay_var.get(),
        move_eggs,
        display,
        score_var,
        life_count_var,
        eggs,
        egg_move_delay_var,
    )


def increase_score(
    display, score_var, egg_move_delay_var, egg_drop_interval_var
):
    score_var.set(score_var.get() + 1)
    #
    # TODO Don't change the values of `egg_move_delay_var` and
    #   `egg_drop_interval_var` but calculate them from `score_var`.
    #
    egg_move_delay_var.set(int(egg_move_delay_var.get() * DIFFICULTY_FACTOR))
    egg_drop_interval_var.set(
        int(egg_drop_interval_var.get() * DIFFICULTY_FACTOR)
    )
    display.set_score(score_var.get())


def check_catch(
    display,
    score_var,
    basket,
    eggs,
    egg_move_delay_var,
    egg_drop_interval_var,
):
    basket_box = basket.get_box()
    for egg in eggs:
        egg_box = egg.get_box()
        if (
            basket_box.left < egg_box.left
            and basket_box.right > egg_box.right
            and basket_box.bottom - egg_box.bottom < 40
        ):
            eggs.remove(egg)
            egg.delete()
            increase_score(
                display, score_var, egg_move_delay_var, egg_drop_interval_var
            )
    display.after(
        100,
        check_catch,
        display,
        score_var,
        basket,
        eggs,
        egg_move_delay_var,
        egg_drop_interval_var,
    )


def move_basket_left(basket, _event):
    if basket.get_box().left > 0:
        basket.move(-20, 0)


def move_basket_right(display, basket, _event):
    if basket.get_box().right < display.width:
        basket.move(20, 0)


def main():
    root = Tk()

    display_width, display_height = (
        root.winfo_screenwidth(),
        root.winfo_screenheight(),
    )
    root.attributes("-fullscreen", True)
    root.geometry(f"{display_width}x{display_height}+0+0")

    egg_move_delay_var = IntVar(value=EGG_MOVE_DELAY)
    egg_drop_interval_var = IntVar(value=EGG_DROP_INTERVAL)
    score_var = IntVar(value=0)
    life_count_var = IntVar(value=3)
    eggs = list()

    display = Display(root, display_width, display_height)
    display.pack()
    display.set_score(score_var.get())
    display.set_life_count(life_count_var.get())

    display_box = display.get_box()
    basket_box = Box.from_size(BASKET_WIDTH, BASKET_HEIGHT)
    basket_box.center_x = display_box.center_x
    basket_box.bottom = display_box.bottom - 20
    basket = Sprite.new_arc(
        display,
        basket_box,
        start=200,
        extent=140,
        style=ARC,
        outline=BASKET_COLOR,
        width=3,
    )

    root.after(1000, create_egg, display, eggs, egg_drop_interval_var)
    root.after(
        1000,
        move_eggs,
        display,
        score_var,
        life_count_var,
        eggs,
        egg_move_delay_var,
    )
    #
    # TODO This should not be an indepedent asynchronous task but called each
    #   time the eggs and/or the basket moved.
    #
    root.after(
        1000,
        check_catch,
        display,
        score_var,
        basket,
        eggs,
        egg_move_delay_var,
        egg_drop_interval_var,
    )
    display.bind("<Left>", partial(move_basket_left, basket))
    display.bind("<Right>", partial(move_basket_right, display, basket))
    display.focus_set()
    root.mainloop()


if __name__ == "__main__":
    main()
Als nächstes könnte man eine Klasse einführen, die die `eggs`-Liste durch ein Objekt ersetzt, welches sich um die Eier kümmert.
long long ago; /* in a galaxy far far away */
Benutzeravatar
__blackjack__
User
Beiträge: 6559
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Samstag 8. August 2020, 08:00

Eine Klasse zur Verwaltung der Eier:

Code: Alles auswählen

#!/usr/bin/env python3
from functools import partial, total_ordering
from random import randrange
from tkinter import ARC, NE, NW, Canvas, IntVar, Tk, font, messagebox

DIFFICULTY_FACTOR = 0.95

EGG_WIDTH = 45
EGG_HEIGHT = 55
EGG_MOVE_DELAY = 500
EGG_DROP_INTERVAL = 4000

BASKET_COLOR = "blue"
BASKET_WIDTH = BASKET_HEIGHT = 100


class Box:
    def __init__(self, left, top, right, bottom):
        self._left = left
        self._top = top
        self._right = right
        self._bottom = bottom

    def __repr__(self):
        return (
            f"{self.__class__.__name__}"
            f"{self.left, self.top, self.right, self.bottom}"
        )

    def __iter__(self):
        yield self.left
        yield self.top
        yield self.right
        yield self.bottom

    @property
    def left(self):
        return self._left

    @left.setter
    def left(self, value):
        self.move(value - self.left, 0)

    @property
    def top(self):
        return self._top

    @top.setter
    def top(self, value):
        self.move(0, value - self.top)

    @property
    def right(self):
        return self._right

    @right.setter
    def right(self, value):
        self.move(value - self.right, 0)

    @property
    def bottom(self):
        return self._bottom

    @bottom.setter
    def bottom(self, value):
        self.move(0, value - self.bottom)

    @property
    def width(self):
        return self.right - self.left

    @property
    def height(self):
        return self.bottom - self.top

    @property
    def center_x(self):
        return self.width // 2 + self.left

    @center_x.setter
    def center_x(self, value):
        self.move(value - self.center_x, 0)

    def move(self, x_delta, y_delta):
        self._left += x_delta
        self._top += y_delta
        self._right += x_delta
        self._bottom += y_delta

    @classmethod
    def from_position_and_size(cls, x, y, width, height):
        return cls(x, y, x + width, y + height)

    @classmethod
    def from_size(cls, width, height):
        return cls.from_position_and_size(0, 0, width, height)


class Display(Canvas):
    def __init__(self, master, width, height):
        self.width = width
        self.height = height
        self._box = Box.from_size(self.width, self.height)
        Canvas.__init__(
            self,
            master=master,
            width=self.width,
            height=self.height,
            background="deep sky blue",
        )
        #
        # Ground and sun.
        #
        # TODO Let sizes depend on display size.
        #
        self.create_rectangle(
            -5,
            self.height - 300,
            self.width + 5,
            self.height + 5,
            fill="sea green",
            width=0,
        )
        #
        # TODO `Box.from_center_and_radius()`!?
        #
        self.create_oval(-80, -80, 120, 120, fill="orange", width=0)

        game_font = font.nametofont("TkFixedFont")
        game_font.config(size=18)
        self._score_text_id = self.create_text(
            10, 10, anchor=NW, font=game_font, fill="darkblue",
        )
        self._life_count_text_id = self.create_text(
            self.width - 10, 10, anchor=NE, font=game_font, fill="darkblue",
        )
        self.set_score("-")
        self.set_life_count("-")

    def get_box(self):
        return self._box

    def set_score(self, value):
        self.itemconfigure(self._score_text_id, text=f"Punktzahl: {value}")

    def set_life_count(self, value):
        self.itemconfigure(self._life_count_text_id, text=f"Leben {value}")


@total_ordering
class Sprite:
    def __init__(self, display, item_id):
        self.display = display
        self.id = item_id

    def __str__(self):
        return str(self.id)

    def __eq__(self, other):
        return self.id == other.id

    def __ne__(self, other):
        return not self == other

    def __lt__(self, other):
        return self.id < other.id

    def __hash__(self):
        return hash(self.id)

    def get_box(self):
        return Box(*self.display.coords(self))

    def move(self, x_delta, y_delta):
        self.display.move(self, x_delta, y_delta)

    def delete(self):
        self.display.delete(self)
        self.id = None

    @classmethod
    def new_arc(cls, display, box, **kwargs):
        return cls(display, display.create_arc(*box, **kwargs))

    @classmethod
    def new_oval(cls, display, box, **kwargs):
        return cls(display, display.create_oval(*box, **kwargs))


class Eggs:
    def __init__(self, display, move_delay, drop_interval):
        self._display = display
        self.move_delay = move_delay
        self.drop_interval = drop_interval
        self._items = list()

    def create_egg(self):
        self._items.append(
            Sprite.new_oval(
                self._display,
                Box.from_position_and_size(
                    randrange(10, self._display.width - EGG_WIDTH - 10),
                    40,
                    EGG_WIDTH,
                    EGG_HEIGHT,
                ),
                fill="yellow",
                width=0,
            )
        )

    def _apply_filter_action(self, action):
        remaining_eggs = list()
        for egg in self._items:
            if action(egg):
                remaining_eggs.append(egg)
            else:
                egg.delete()

        deleted_egg_count = len(self._items) - len(remaining_eggs)
        self._items = remaining_eggs
        return deleted_egg_count

    def move(self):
        def action(egg):
            egg.move(0, 10)
            return egg.get_box().bottom <= self._display.height

        return self._apply_filter_action(action)

    def check_catch(self, basket):
        basket_box = basket.get_box()

        def action(egg):
            egg_box = egg.get_box()
            return not (
                basket_box.left < egg_box.left
                and basket_box.right > egg_box.right
                and basket_box.bottom - egg_box.bottom < 40
            )

        return self._apply_filter_action(action)


def create_egg(widget, eggs):
    eggs.create_egg()
    widget.after(eggs.drop_interval, create_egg, widget, eggs)


def move_eggs(display, score_var, life_count_var, eggs):
    dropped_egg_count = eggs.move()
    if dropped_egg_count:
        life_count_var.set(life_count_var.get() - dropped_egg_count)
        display.set_life_count(life_count_var.get())
        if life_count_var.get() <= 0:
            #
            # FIXME While the message box is shown there is still a task running
            #   creating new eggs.
            #
            messagebox.showinfo("Game Over", f"Punkte: {score_var.get()}")
            display.quit()

    display.after(
        eggs.move_delay, move_eggs, display, score_var, life_count_var, eggs
    )


def check_catch(display, score_var, basket, eggs):
    caught_egg_count = eggs.check_catch(basket)
    if caught_egg_count:
        score_var.set(score_var.get() + caught_egg_count)
        eggs.move_delay = int(
            EGG_MOVE_DELAY * DIFFICULTY_FACTOR ** score_var.get()
        )
        eggs.drop_interval = int(
            EGG_DROP_INTERVAL * DIFFICULTY_FACTOR ** score_var.get()
        )
        display.set_score(score_var.get())

    display.after(100, check_catch, display, score_var, basket, eggs)


def move_basket_left(basket, _event):
    if basket.get_box().left > 0:
        basket.move(-20, 0)


def move_basket_right(display, basket, _event):
    if basket.get_box().right < display.width:
        basket.move(20, 0)


def main():
    root = Tk()

    display_width, display_height = (
        root.winfo_screenwidth(),
        root.winfo_screenheight(),
    )
    root.attributes("-fullscreen", True)
    root.geometry(f"{display_width}x{display_height}+0+0")

    score_var = IntVar(value=0)
    life_count_var = IntVar(value=3)

    display = Display(root, display_width, display_height)
    display.pack()
    display.set_score(score_var.get())
    display.set_life_count(life_count_var.get())

    eggs = Eggs(display, EGG_MOVE_DELAY, EGG_DROP_INTERVAL)

    display_box = display.get_box()
    basket_box = Box.from_size(BASKET_WIDTH, BASKET_HEIGHT)
    basket_box.center_x = display_box.center_x
    basket_box.bottom = display_box.bottom - 20
    basket = Sprite.new_arc(
        display,
        basket_box,
        start=200,
        extent=140,
        style=ARC,
        outline=BASKET_COLOR,
        width=3,
    )

    root.after(1000, create_egg, root, eggs)
    root.after(1000, move_eggs, display, score_var, life_count_var, eggs)
    #
    # TODO This should not be an indepedent asynchronous task but called each
    #   time the eggs and/or the basket moved.
    #
    root.after(1000, check_catch, display, score_var, basket, eggs)

    display.bind("<Left>", partial(move_basket_left, basket))
    display.bind("<Right>", partial(move_basket_right, display, basket))
    display.focus_set()
    root.mainloop()


if __name__ == "__main__":
    main()
long long ago; /* in a galaxy far far away */
Antworten